/****************************************************************************/1// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo2// Copyright (C) 2004-2025 German Aerospace Center (DLR) and others.3// This program and the accompanying materials are made available under the4// terms of the Eclipse Public License 2.0 which is available at5// https://www.eclipse.org/legal/epl-2.0/6// This Source Code may also be made available under the following Secondary7// Licenses when the conditions for such availability set forth in the Eclipse8// Public License 2.0 are satisfied: GNU General Public License, version 29// or later which is available at10// https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html11// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later12/****************************************************************************/13/// @file OutputDevice.h14/// @author Daniel Krajzewicz15/// @author Jakob Erdmann16/// @author Michael Behrisch17/// @author Mario Krumnow18/// @date 200419///20// Static storage of an output device and its base (abstract) implementation21/****************************************************************************/22#pragma once23#include <config.h>2425#include <string>26#include <map>27#include <cassert>28#include <utils/common/ToString.h>29#include <utils/xml/SUMOXMLDefinitions.h>30#include "CSVFormatter.h"31#ifdef HAVE_PARQUET32#include "ParquetFormatter.h"33#endif34#include "PlainXMLFormatter.h"3536// ===========================================================================37// class definitions38// ===========================================================================39/**40* @class OutputDevice41* @brief Static storage of an output device and its base (abstract) implementation42*43* OutputDevices are basically a capsule around an std::ostream, which give a44* unified access to sockets, files and stdout.45*46* Usually, an application builds as many output devices as needed. Each47* output device may also be used to save outputs from several sources48* (several detectors, for example). Building is done using OutputDevice::getDevice()49* what also parses the given output description in order to decide50* what kind of an OutputDevice shall be built. OutputDevices are51* closed via OutputDevice::closeAll(), normally called at the application's52* end.53*54* Although everything that can be written to a stream can also be written55* to an OutputDevice, there is special support for XML tags (remembering56* all open tags to close them at the end). OutputDevices are still lacking57* support for function pointers with the '<<' operator (no endl, use '\n').58* The most important method to implement in subclasses is getOStream,59* the most used part of the interface is the '<<' operator.60*61* The Boolean markers are used rarely and might get removed in future versions.62*/63class OutputDevice {64public:65/// @name static access methods to OutputDevices66/// @{6768/** @brief Returns the described OutputDevice69*70* Creates and returns the named device. "stdout" and "stderr" refer to the relevant console streams,71* "hostname:port" initiates socket connection. Otherwise a filename72* is assumed (where "nul" and "/dev/null" do what you would expect on both platforms).73* If there already is a device with the same name this one is returned.74*75* @param[in] name The description of the output name/port/whatever76* @return The corresponding (built or existing) device77* @exception IOError If the output could not be built for any reason (error message is supplied)78*/79static OutputDevice& getDevice(const std::string& name, bool usePrefix = true);808182/** @brief Creates the device using the output definition stored in the named option83*84* Creates and returns the device named by the option. Asks whether the option85* and retrieves the name from the option if so. Optionally the XML header86* gets written as well. Returns whether a device was created (option was set).87*88* Please note, that we do not have to consider the "application base" herein,89* because this call is only used to get file names of files referenced90* within XML-declarations of structures which paths already is aware of the91* cwd.92*93* @param[in] optionName The name of the option to use for retrieving the output definition94* @param[in] rootElement The root element to use (XML-output)95* @param[in] schemaFile The basename of the schema file to use (XML-output)96* @return Whether a device was built (the option was set)97* @exception IOError If the output could not be built for any reason (error message is supplied)98*/99static bool createDeviceByOption(const std::string& optionName,100const std::string& rootElement = "",101const std::string& schemaFile = "");102103104/** @brief Returns the device described by the option105*106* Returns the device named by the option. If the option is unknown, unset107* or the device was not created before, InvalidArgument is thrown.108*109* Please note, that we do not have to consider the "application base" herein.110*111* @param[in] name The name of the option to use for retrieving the output definition112* @return The corresponding (built or existing) device113* @exception IOError If the output could not be built for any reason (error message is supplied)114* @exception InvalidArgument If the option with the given name does not exist115*/116static OutputDevice& getDeviceByOption(const std::string& name);117118/** Flushes all registered devices119*/120static void flushAll();121122/** Closes all registered devices123*/124static void closeAll(bool keepErrorRetrievers = false);125/// @}126127public:128/// @name OutputDevice member methods129/// @{130131/// @brief Constructor132OutputDevice(const int defaultIndentation = 0, const std::string& filename = "");133134135/// @brief Destructor136virtual ~OutputDevice();137138139/** @brief returns the information whether one can write into the device140* @return Whether the device can be used (stream is good)141*/142virtual bool ok();143144/** @brief returns the information whether the device will discard all output145* @return Whether the device redirects to /dev/null146*/147virtual bool isNull() {148return false;149}150151/// @brief get filename or suitable description of this device152const std::string& getFilename();153154/** @brief Closes the device and removes it from the dictionary155*/156void close();157158void setFormatter(OutputFormatter* formatter) {159delete myFormatter;160myFormatter = formatter;161}162163/** @brief Sets the precision or resets it to default164* @param[in] precision The accuracy (number of digits behind '.') to set165*/166void setPrecision(int precision = gPrecision);167168/** @brief Returns the precision of the underlying stream169*/170int getPrecision() {171return (int)getOStream().precision();172}173174/** @brief Writes an XML header with optional configuration175*176* If something has been written (myXMLStack is not empty), nothing177* is written and false returned.178*179* @param[in] rootElement The root element to use180* @param[in] schemaFile The basename of the schema file to use181* @param[in] attrs Additional attributes to save within the rootElement182* @return Whether the header could be written (stack was empty)183* @todo Describe what is saved184*/185bool writeXMLHeader(const std::string& rootElement,186const std::string& schemaFile,187std::map<SumoXMLAttr, std::string> attrs = std::map<SumoXMLAttr, std::string>(),188bool includeConfig = true);189190/** @brief Opens an XML tag191*192* An indentation, depending on the current xml-element-stack size, is written followed193* by the given xml element ("<" + xmlElement)194* The xml element is added to the stack, then.195*196* @param[in] xmlElement Name of element to open197* @return The OutputDevice for further processing198*/199OutputDevice& openTag(const std::string& xmlElement);200201/** @brief Opens an XML tag202*203* Helper method which finds the correct string before calling openTag.204*205* @param[in] xmlElement Id of the element to open206* @return The OutputDevice for further processing207*/208OutputDevice& openTag(const SumoXMLTag& xmlElement);209210/** @brief Closes the most recently opened tag and optionally adds a comment211*212* The topmost xml-element from the stack is written into the stream213* as a closing element. Depending on the formatter used214* this may be something like "</" + element + ">" or "/>" or215* nothing at all.216*217* @return Whether a further element existed in the stack and could be closed218* @todo it is not verified that the topmost element was closed219*/220bool closeTag(const std::string& comment = "");221222/** @brief writes a line feed if applicable223*/224void lf() {225getOStream() << "\n";226}227228/** @brief writes a named attribute229*230* @param[in] attr The attribute (name)231* @param[in] val The attribute value232* @return The OutputDevice for further processing233*/234template <typename T>235OutputDevice& writeAttr(const SumoXMLAttr attr, const T& val) {236if (myFormatter->getType() == OutputFormatterType::XML) {237PlainXMLFormatter::writeAttr(getOStream(), attr, val);238#ifdef HAVE_PARQUET239} else if (myFormatter->getType() == OutputFormatterType::PARQUET) {240static_cast<ParquetFormatter*>(myFormatter)->writeAttr(getOStream(), attr, val);241#endif242} else {243static_cast<CSVFormatter*>(myFormatter)->writeAttr(getOStream(), attr, val);244}245return *this;246}247248/** @brief Parses a list of strings for attribute names and sets the relevant bits in the returned mask.249*250* It honors the special value "all" to set all bits and other special values for predefined bit sets given as parameter251*252* @param[in] attrList The attribute names and special values253* @param[in] desc A descriptive string for the error message if the attribute is unknown254* @param[in] special special values for predefined bitsets255* @return The corresponding mask of bits being set256*/257static const SumoXMLAttrMask parseWrittenAttributes(const std::vector<std::string>& attrList, const std::string& desc,258const std::map<std::string, SumoXMLAttrMask>& special = std::map<std::string, SumoXMLAttrMask>());259260/** @brief writes a named attribute unless filtered261*262* @param[in] attr The attribute (name)263* @param[in] val The attribute value264* @param[in] attributeMask The filter that specifies whether the attribute shall be written265* @return The OutputDevice for further processing266*/267template <typename T>268OutputDevice& writeOptionalAttr(const SumoXMLAttr attr, const T& val, const SumoXMLAttrMask& attributeMask, const bool isNull = false) {269assert((int)attr <= (int)attributeMask.size());270if (attributeMask.none() || attributeMask.test(attr)) {271if (myFormatter->getType() == OutputFormatterType::XML) {272if (!isNull) {273PlainXMLFormatter::writeAttr(getOStream(), attr, val);274}275#ifdef HAVE_PARQUET276} else if (myFormatter->getType() == OutputFormatterType::PARQUET) {277static_cast<ParquetFormatter*>(myFormatter)->writeAttr(getOStream(), attr, val, isNull);278#endif279} else {280if (isNull) {281static_cast<CSVFormatter*>(myFormatter)->writeNull(getOStream(), attr);282} else {283static_cast<CSVFormatter*>(myFormatter)->writeAttr(getOStream(), attr, val);284}285}286}287return *this;288}289290template <typename Func>291OutputDevice& writeFuncAttr(const SumoXMLAttr attr, const Func& valFunc, const SumoXMLAttrMask& attributeMask, const bool isNull = false) {292assert((int)attr <= (int)attributeMask.size());293if (attributeMask.none() || attributeMask.test(attr)) {294if (myFormatter->getType() == OutputFormatterType::XML) {295if (!isNull) {296PlainXMLFormatter::writeAttr(getOStream(), attr, valFunc());297}298#ifdef HAVE_PARQUET299} else if (myFormatter->getType() == OutputFormatterType::PARQUET) {300static_cast<ParquetFormatter*>(myFormatter)->writeAttr(getOStream(), attr, valFunc(), isNull);301#endif302} else {303if (isNull) {304static_cast<CSVFormatter*>(myFormatter)->writeNull(getOStream(), attr);305} else {306static_cast<CSVFormatter*>(myFormatter)->writeAttr(getOStream(), attr, valFunc());307}308}309}310return *this;311}312313/** @brief writes an arbitrary attribute314*315* @param[in] attr The attribute (name)316* @param[in] val The attribute value317* @return The OutputDevice for further processing318*/319template <typename T>320OutputDevice& writeAttr(const std::string& attr, const T& val) {321if (myFormatter->getType() == OutputFormatterType::XML) {322PlainXMLFormatter::writeAttr(getOStream(), attr, val);323#ifdef HAVE_PARQUET324} else if (myFormatter->getType() == OutputFormatterType::PARQUET) {325static_cast<ParquetFormatter*>(myFormatter)->writeAttr(getOStream(), attr, val);326#endif327} else {328static_cast<CSVFormatter*>(myFormatter)->writeAttr(getOStream(), attr, val);329}330return *this;331}332333/** @brief writes a string attribute only if it is not the empty string and not the string "default"334*335* @param[in] attr The attribute (name)336* @param[in] val The attribute value337* @return The OutputDevice for further processing338*/339OutputDevice& writeNonEmptyAttr(const SumoXMLAttr attr, const std::string& val) {340if (val != "" && val != "default") {341writeAttr(attr, val);342}343return *this;344}345346OutputDevice& writeTime(const SumoXMLAttr attr, const SUMOTime val) {347myFormatter->writeTime(getOStream(), attr, val);348return *this;349}350351/** @brief writes a preformatted tag to the device but ensures that any352* pending tags are closed353* @param[in] val The preformatted data354* @return The OutputDevice for further processing355*/356OutputDevice& writePreformattedTag(const std::string& val) {357myFormatter->writePreformattedTag(getOStream(), val);358return *this;359}360361/// @brief writes padding (ignored for binary output)362OutputDevice& writePadding(const std::string& val) {363myFormatter->writePadding(getOStream(), val);364return *this;365}366367/** @brief Retrieves a message to this device.368*369* Implementation of the MessageRetriever interface. Writes the given message to the output device.370*371* @param[in] msg The msg to write to the device372*/373void inform(const std::string& msg, const bool progress = false);374375376/** @brief Abstract output operator377* @return The OutputDevice for further processing378*/379template <class T>380OutputDevice& operator<<(const T& t) {381getOStream() << t;382postWriteHook();383return *this;384}385386void flush() {387getOStream().flush();388}389390bool wroteHeader() const {391return myFormatter->wroteHeader();392}393394void setExpectedAttributes(const SumoXMLAttrMask& expected, const int depth = 2) {395myFormatter->setExpectedAttributes(expected, depth);396}397398protected:399/// @brief Returns the associated ostream400virtual std::ostream& getOStream() = 0;401402/** @brief Called after every write access.403*404* Default implementation does nothing.405*/406virtual void postWriteHook();407408409private:410/// @brief map from names to output devices411static std::map<std::string, OutputDevice*> myOutputDevices;412413/// @brief old console code page to restore after ending414static int myPrevConsoleCP;415416protected:417const std::string myFilename;418419bool myWriteMetadata;420421/// @brief The formatter for XML, CSV or Parquet422OutputFormatter* myFormatter;423424private:425/// @brief Invalidated copy constructor.426OutputDevice(const OutputDevice&) = delete;427428/// @brief Invalidated assignment operator.429OutputDevice& operator=(const OutputDevice&) = delete;430431};432433434