LCOV - code coverage report
Current view: top level - src/utils/iodevices - OutputDevice.h (source / functions) Coverage Total Hit
Test: lcov.info Lines: 96.0 % 50 48
Test Date: 2026-07-25 16:16:11 Functions: 85.7 % 315 270

            Line data    Source code
       1              : /****************************************************************************/
       2              : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
       3              : // Copyright (C) 2004-2026 German Aerospace Center (DLR) and others.
       4              : // This program and the accompanying materials are made available under the
       5              : // terms of the Eclipse Public License 2.0 which is available at
       6              : // https://www.eclipse.org/legal/epl-2.0/
       7              : // This Source Code may also be made available under the following Secondary
       8              : // Licenses when the conditions for such availability set forth in the Eclipse
       9              : // Public License 2.0 are satisfied: GNU General Public License, version 2
      10              : // or later which is available at
      11              : // https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
      12              : // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
      13              : /****************************************************************************/
      14              : /// @file    OutputDevice.h
      15              : /// @author  Daniel Krajzewicz
      16              : /// @author  Jakob Erdmann
      17              : /// @author  Michael Behrisch
      18              : /// @author  Mario Krumnow
      19              : /// @date    2004
      20              : ///
      21              : // Static storage of an output device and its base (abstract) implementation
      22              : /****************************************************************************/
      23              : #pragma once
      24              : #include <config.h>
      25              : 
      26              : #include <string>
      27              : #include <map>
      28              : #include <cassert>
      29              : #include <utils/common/ToString.h>
      30              : #include <utils/xml/SUMOXMLDefinitions.h>
      31              : #include "CSVFormatter.h"
      32              : #ifdef HAVE_PARQUET
      33              : #include "ParquetFormatter.h"
      34              : #endif
      35              : #include "PlainXMLFormatter.h"
      36              : 
      37              : // ===========================================================================
      38              : // class definitions
      39              : // ===========================================================================
      40              : /**
      41              :  * @class OutputDevice
      42              :  * @brief Static storage of an output device and its base (abstract) implementation
      43              :  *
      44              :  * OutputDevices are basically a capsule around an std::ostream, which give a
      45              :  *  unified access to sockets, files and stdout.
      46              :  *
      47              :  * Usually, an application builds as many output devices as needed. Each
      48              :  *  output device may also be used to save outputs from several sources
      49              :  *  (several detectors, for example). Building is done using OutputDevice::getDevice()
      50              :  *  what also parses the given output description in order to decide
      51              :  *  what kind of an OutputDevice shall be built. OutputDevices are
      52              :  *  closed via OutputDevice::closeAll(), normally called at the application's
      53              :  *  end.
      54              :  *
      55              :  * Although everything that can be written to a stream can also be written
      56              :  *  to an OutputDevice, there is special support for XML tags (remembering
      57              :  *  all open tags to close them at the end). OutputDevices are still lacking
      58              :  *  support for function pointers with the '<<' operator (no endl, use '\n').
      59              :  *  The most important method to implement in subclasses is getOStream,
      60              :  *  the most used part of the interface is the '<<' operator.
      61              :  *
      62              :  * The Boolean markers are used rarely and might get removed in future versions.
      63              :  */
      64              : class OutputDevice {
      65              : public:
      66              :     /// @name static access methods to OutputDevices
      67              :     /// @{
      68              : 
      69              :     /** @brief Returns the described OutputDevice
      70              :      *
      71              :      * Creates and returns the named device. "stdout" and "stderr" refer to the relevant console streams,
      72              :      * "hostname:port" initiates socket connection. Otherwise a filename
      73              :      * is assumed (where "nul" and "/dev/null" do what you would expect on both platforms).
      74              :      * If there already is a device with the same name this one is returned.
      75              :      *
      76              :      * @param[in] name The description of the output name/port/whatever
      77              :      * @return The corresponding (built or existing) device
      78              :      * @exception IOError If the output could not be built for any reason (error message is supplied)
      79              :      */
      80              :     static OutputDevice& getDevice(const std::string& name, bool usePrefix = true);
      81              : 
      82              : 
      83              :     /** @brief Creates the device using the output definition stored in the named option
      84              :      *
      85              :      * Creates and returns the device named by the option. Asks whether the option
      86              :      *  and retrieves the name from the option if so. Optionally the XML header
      87              :      *  gets written as well. Returns whether a device was created (option was set).
      88              :      *
      89              :      * Please note, that we do not have to consider the "application base" herein,
      90              :      *  because this call is only used to get file names of files referenced
      91              :      *  within XML-declarations of structures which paths already is aware of the
      92              :      *  cwd.
      93              :      *
      94              :      * @param[in] optionName   The name of the option to use for retrieving the output definition
      95              :      * @param[in] rootElement  The root element to use (XML-output)
      96              :      * @param[in] schemaFile   The basename of the schema file to use (XML-output)
      97              :      * @param[in] maximumDepth The expected maximum nested depth (Parquet output)
      98              :      * @return Whether a device was built (the option was set)
      99              :      * @exception IOError If the output could not be built for any reason (error message is supplied)
     100              :      */
     101              :     static bool createDeviceByOption(const std::string& optionName,
     102              :                                      const std::string& rootElement = "",
     103              :                                      const std::string& schemaFile = "",
     104              :                                      const int maximumDepth = 2);
     105              : 
     106              : 
     107              :     /** @brief Returns the device described by the option
     108              :      *
     109              :      * Returns the device named by the option. If the option is unknown, unset
     110              :      * or the device was not created before, InvalidArgument is thrown.
     111              :      *
     112              :      * Please note, that we do not have to consider the "application base" herein.
     113              :      *
     114              :      * @param[in] name The name of the option to use for retrieving the output definition
     115              :      * @return The corresponding (built or existing) device
     116              :      * @exception IOError If the output could not be built for any reason (error message is supplied)
     117              :      * @exception InvalidArgument If the option with the given name does not exist
     118              :      */
     119              :     static OutputDevice& getDeviceByOption(const std::string& name);
     120              : 
     121              :     /**  Flushes all registered devices
     122              :      */
     123              :     static void flushAll();
     124              : 
     125              :     /**  Closes all registered devices
     126              :      */
     127              :     static void closeAll(bool keepErrorRetrievers = false);
     128              :     /// @}
     129              : 
     130              : public:
     131              :     /// @name OutputDevice member methods
     132              :     /// @{
     133              : 
     134              :     /// @brief Constructor
     135              :     OutputDevice(const int defaultIndentation = 0, const std::string& filename = "");
     136              : 
     137              : 
     138              :     /// @brief Destructor
     139              :     virtual ~OutputDevice();
     140              : 
     141              : 
     142              :     /** @brief returns the information whether one can write into the device
     143              :      * @return Whether the device can be used (stream is good)
     144              :      */
     145              :     virtual bool ok();
     146              : 
     147              :     /** @brief returns the information whether the device will discard all output
     148              :      * @return Whether the device redirects to /dev/null
     149              :      */
     150            0 :     virtual bool isNull() {
     151            0 :         return false;
     152              :     }
     153              : 
     154              :     /// @brief get filename or suitable description of this device
     155              :     const std::string& getFilename();
     156              : 
     157              :     /** @brief Closes the device and removes it from the dictionary
     158              :      */
     159              :     void close();
     160              : 
     161              :     bool isXML() const {
     162       142025 :         return myFormatter->getType() == OutputFormatterType::XML;
     163              :     }
     164              : 
     165              :     void setFormatter(OutputFormatter* formatter) {
     166          100 :         delete myFormatter;
     167          100 :         myFormatter = formatter;
     168              :     }
     169              : 
     170              :     /** @brief Sets the precision or resets it to default
     171              :      * @param[in] precision The accuracy (number of digits behind '.') to set
     172              :      */
     173              :     void setPrecision(int precision = gPrecision);
     174              : 
     175              :     /** @brief Returns the precision of the underlying stream
     176              :      */
     177              :     int getPrecision() {
     178        52724 :         return (int)getOStream().precision();
     179              :     }
     180              : 
     181              :     /** @brief Writes an XML header with optional configuration
     182              :      *
     183              :      * If something has been written (myXMLStack is not empty), nothing
     184              :      *  is written and false returned.
     185              :      *
     186              :      * @param[in] rootElement The root element to use
     187              :      * @param[in] schemaFile  The basename of the schema file to use
     188              :      * @param[in] attrs Additional attributes to save within the rootElement
     189              :      * @return Whether the header could be written (stack was empty)
     190              :      * @todo Describe what is saved
     191              :      */
     192              :     bool writeXMLHeader(const std::string& rootElement,
     193              :                         const std::string& schemaFile,
     194              :                         std::map<SumoXMLAttr, std::string> attrs = std::map<SumoXMLAttr, std::string>(),
     195              :                         bool includeConfig = true);
     196              : 
     197              :     /** @brief Opens an XML tag
     198              :      *
     199              :      * An indentation, depending on the current xml-element-stack size, is written followed
     200              :      *  by the given xml element ("<" + xmlElement)
     201              :      * The xml element is added to the stack, then.
     202              :      *
     203              :      * @param[in] xmlElement Name of element to open
     204              :      * @return The OutputDevice for further processing
     205              :      */
     206              :     OutputDevice& openTag(const std::string& xmlElement);
     207              : 
     208              :     /** @brief Opens an XML tag
     209              :      *
     210              :      * Helper method which finds the correct string before calling openTag.
     211              :      *
     212              :      * @param[in] xmlElement Id of the element to open
     213              :      * @return The OutputDevice for further processing
     214              :      */
     215              :     OutputDevice& openTag(const SumoXMLTag& xmlElement);
     216              : 
     217              :     /** @brief Closes the most recently opened tag and optionally adds a comment
     218              :      *
     219              :      * The topmost xml-element from the stack is written into the stream
     220              :      *  as a closing element. Depending on the formatter used
     221              :      *  this may be something like "</" + element + ">" or "/>" or
     222              :      *  nothing at all.
     223              :      *
     224              :      * @return Whether a further element existed in the stack and could be closed
     225              :      * @todo it is not verified that the topmost element was closed
     226              :      */
     227              :     bool closeTag(const std::string& comment = "");
     228              : 
     229              :     /** @brief writes a line feed if applicable
     230              :      */
     231       154135 :     void lf() {
     232       154135 :         if (myFormatter->getType() == OutputFormatterType::XML) {
     233       150935 :             getOStream() << "\n";
     234              :         }
     235       154135 :     }
     236              : 
     237              :     /** @brief writes a named attribute
     238              :      *
     239              :      * The attribute is always written even with XML output. The isNull flag is only for tabular outputs.
     240              :      *
     241              :      * @param[in] attr The attribute (name)
     242              :      * @param[in] val The attribute value
     243              :      * @param[in] isNull Whether the value should be represented as None / null in output formats which support it
     244              :      * @param[in] escape Whether the value should be processed by the escaping mechanism to filter invalid characters
     245              :      * @return The OutputDevice for further processing
     246              :      */
     247              :     template <typename T, class ATTR_TYPE>
     248    100826197 :     OutputDevice& writeAttr(const ATTR_TYPE& attr, const T& val, const bool isNull = false, const bool escape = false) {
     249    100826197 :         if (myFormatter->getType() == OutputFormatterType::XML) {
     250    128937054 :             PlainXMLFormatter::writeAttr(getOStream(), attr, val, escape);
     251              : #ifdef HAVE_PARQUET
     252       435940 :         } else if (myFormatter->getType() == OutputFormatterType::PARQUET) {
     253       271242 :             static_cast<ParquetFormatter*>(myFormatter)->writeAttr(getOStream(), attr, val, isNull, escape);
     254              : #endif
     255              :         } else {
     256       271050 :             static_cast<CSVFormatter*>(myFormatter)->writeAttr(getOStream(), attr, val, isNull, escape);
     257              :         }
     258    100826197 :         return *this;
     259              :     }
     260              : 
     261              :     /** @brief Parses a list of strings for attribute names and sets the relevant bits in the returned mask.
     262              :      *
     263              :      * It honors the special value "all" to set all bits and other special values for predefined bit sets given as parameter
     264              :      *
     265              :      * @param[in] attrList The attribute names and special values
     266              :      * @param[in] desc A descriptive string for the error message if the attribute is unknown
     267              :      * @param[in] special special values for predefined bitsets
     268              :      * @return The corresponding mask of bits being set
     269              :      */
     270              :     static const SumoXMLAttrMask parseWrittenAttributes(const std::vector<std::string>& attrList, const std::string& desc,
     271              :             const std::map<std::string, SumoXMLAttrMask>& special = std::map<std::string, SumoXMLAttrMask>());
     272              : 
     273              :     /** @brief writes a named attribute unless null
     274              :      *
     275              :      * If isNull is true the attribute is not written with XML output. For tabular outputs the behavior is the same as writeAttr.
     276              :      *
     277              :      * @param[in] attr The attribute (name)
     278              :      * @param[in] val The attribute value
     279              :      * @param[in] isNull Whether the value should be filtered or represented as None / null in output formats which support it
     280              :      * @param[in] escape Whether the value should be processed by the escaping mechanism to filter invalid characters
     281              :      * @return The OutputDevice for further processing
     282              :      */
     283              :     template <typename T>
     284              :     OutputDevice& writeOptionalAttr(const SumoXMLAttr attr, const T& val, const bool isNull = false, const bool escape = false) {
     285     50608926 :         if (myFormatter->getType() == OutputFormatterType::XML && isNull) {
     286              :             return *this;
     287              :         }
     288     40390617 :         return writeAttr(attr, val, isNull, escape);
     289              :     }
     290              : 
     291              :     /** @brief writes a named attribute unless filtered
     292              :      *
     293              :      * @param[in] attr The attribute (name)
     294              :      * @param[in] val The attribute value
     295              :      * @param[in] attributeMask The filter that specifies whether the attribute shall be written
     296              :      * @param[in] isNull Whether the value should be represented as None / null in output formats which support it
     297              :      * @param[in] escape Whether the value should be processed by the escaping mechanism to filter invalid characters
     298              :      * @return The OutputDevice for further processing
     299              :      */
     300              :     template <typename T>
     301    101372042 :     OutputDevice& writeOptionalAttr(const SumoXMLAttr attr, const T& val, const SumoXMLAttrMask& attributeMask,
     302              :                                     const bool isNull = false, const bool escape = false) {
     303              :         assert(attributeMask.none() || (int)attr <= (int)attributeMask.size());
     304    101372042 :         if (attributeMask.none() || attributeMask.test(attr)) {
     305     48156843 :             return writeOptionalAttr(attr, val, isNull, escape);
     306              :         }
     307              :         return *this;
     308              :     }
     309              : 
     310              :     /** @brief writes a named attribute with a generating function unless filtered
     311              :      *
     312              :      * This function should be used if generating the value needs an expensive function call
     313              :      * which will not be executed if the value is filtered anyway.
     314              :      *
     315              :      * @param[in] attr The attribute (name)
     316              :      * @param[in] valFunc The function to generate the attribute value
     317              :      * @param[in] attributeMask The filter that specifies whether the attribute shall be written
     318              :      * @param[in] isNull Whether the value should be represented as None / null in output formats which support it
     319              :      * @param[in] escape Whether the value should be processed by the escaping mechanism to filter invalid characters
     320              :      * @return The OutputDevice for further processing
     321              :      */
     322              :     template <typename Func>
     323    190069926 :     OutputDevice& writeFuncAttr(const SumoXMLAttr attr, const Func& valFunc, const SumoXMLAttrMask& attributeMask,
     324              :                                 const bool isNull = false, const bool escape = false) {
     325              :         assert((int)attr <= (int)attributeMask.size());
     326    190069926 :         if (attributeMask.none() || attributeMask.test(attr)) {
     327     32059692 :             if (myFormatter->getType() == OutputFormatterType::XML) {
     328     32047562 :                 if (!isNull) {
     329     40011400 :                     PlainXMLFormatter::writeAttr(getOStream(), attr, valFunc(), escape);
     330              :                 }
     331              : #ifdef HAVE_PARQUET
     332        12130 :             } else if (myFormatter->getType() == OutputFormatterType::PARQUET) {
     333         8698 :                 static_cast<ParquetFormatter*>(myFormatter)->writeAttr(getOStream(), attr, valFunc(), isNull, escape);
     334              : #endif
     335              :             } else {
     336         8556 :                 static_cast<CSVFormatter*>(myFormatter)->writeAttr(getOStream(), attr, valFunc(), isNull, escape);
     337              :             }
     338              :         }
     339    190069926 :         return *this;
     340              :     }
     341              : 
     342              :     /** @brief writes a string attribute only if it is not the empty string and not the string "default"
     343              :      *
     344              :      * @param[in] attr The attribute (name)
     345              :      * @param[in] val The attribute value
     346              :      * @return The OutputDevice for further processing
     347              :      */
     348       288102 :     OutputDevice& writeNonEmptyAttr(const SumoXMLAttr attr, const std::string& val) {
     349       288102 :         if (val != "" && val != "default") {
     350       288072 :             writeAttr(attr, val);
     351              :         }
     352       288102 :         return *this;
     353              :     }
     354              : 
     355              :     OutputDevice& writeTime(const SumoXMLAttr attr, const SUMOTime val) {
     356      7589012 :         myFormatter->writeTime(getOStream(), attr, val);
     357      6944582 :         return *this;
     358              :     }
     359              : 
     360              :     /** @brief writes a preformatted tag to the device but ensures that any
     361              :      * pending tags are closed
     362              :      * @param[in] val The preformatted data
     363              :      * @return The OutputDevice for further processing
     364              :      */
     365              :     OutputDevice& writePreformattedTag(const std::string& val) {
     366         1503 :         myFormatter->writePreformattedTag(getOStream(), val);
     367         1455 :         return *this;
     368              :     }
     369              : 
     370              :     /// @brief writes padding (ignored for binary output)
     371              :     OutputDevice& writePadding(const std::string& val) {
     372       138813 :         myFormatter->writePadding(getOStream(), val);
     373       138813 :         return *this;
     374              :     }
     375              : 
     376              :     /** @brief Retrieves a message to this device.
     377              :      *
     378              :      * Implementation of the MessageRetriever interface. Writes the given message to the output device.
     379              :      *
     380              :      * @param[in] msg The msg to write to the device
     381              :      */
     382              :     void inform(const std::string& msg, const bool progress = false);
     383              : 
     384              : 
     385              :     /** @brief Abstract output operator
     386              :      * @return The OutputDevice for further processing
     387              :      */
     388              :     template <class T>
     389      3666637 :     OutputDevice& operator<<(const T& t) {
     390      3666637 :         getOStream() << t;
     391      3666637 :         postWriteHook();
     392      3666637 :         return *this;
     393              :     }
     394              : 
     395              :     void flush() {
     396     17458944 :         getOStream().flush();
     397     17458944 :     }
     398              : 
     399              :     bool wroteHeader() const {
     400       271990 :         return myFormatter->wroteHeader();
     401              :     }
     402              : 
     403              :     void setExpectedAttributes(const SumoXMLAttrMask& expected, const int depth) {
     404        30284 :         myFormatter->setExpectedAttributes(expected, depth);
     405         4314 :     }
     406              : 
     407              : protected:
     408              :     /// @brief Returns the associated ostream
     409              :     virtual std::ostream& getOStream() = 0;
     410              : 
     411              :     /** @brief Called after every write access.
     412              :      *
     413              :      * Default implementation does nothing.
     414              :      */
     415              :     virtual void postWriteHook();
     416              : 
     417              : 
     418              : private:
     419              :     /// @brief map from names to output devices
     420              :     static std::map<std::string, OutputDevice*> myOutputDevices;
     421              : 
     422              :     /// @brief old console code page to restore after ending
     423              :     static int myPrevConsoleCP;
     424              : 
     425              : protected:
     426              :     const std::string myFilename;
     427              : 
     428              :     bool myWriteMetadata;
     429              : 
     430              :     /// @brief The formatter for XML, CSV or Parquet
     431              :     OutputFormatter* myFormatter;
     432              : 
     433              : private:
     434              :     /// @brief Invalidated copy constructor.
     435              :     OutputDevice(const OutputDevice&) = delete;
     436              : 
     437              :     /// @brief Invalidated assignment operator.
     438              :     OutputDevice& operator=(const OutputDevice&) = delete;
     439              : 
     440              : };
        

Generated by: LCOV version 2.0-1