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 142535 : return myFormatter->getType() == OutputFormatterType::XML;
163 : }
164 :
165 : void setFormatter(OutputFormatter* formatter) {
166 74 : delete myFormatter;
167 74 : 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 53609 : 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 148070 : void lf() {
232 148070 : getOStream() << "\n";
233 148070 : }
234 :
235 : /** @brief writes a named attribute
236 : *
237 : * @param[in] attr The attribute (name)
238 : * @param[in] val The attribute value
239 : * @param[in] isNull Whether the value should be represented as None / null in output formats which support it
240 : * @return The OutputDevice for further processing
241 : */
242 : template <typename T, class ATTR_TYPE>
243 57138020 : OutputDevice& writeAttr(const ATTR_TYPE& attr, const T& val, const bool isNull = false) {
244 57138020 : if (myFormatter->getType() == OutputFormatterType::XML) {
245 85405267 : PlainXMLFormatter::writeAttr(getOStream(), attr, val);
246 : #ifdef HAVE_PARQUET
247 121648 : } else if (myFormatter->getType() == OutputFormatterType::PARQUET) {
248 114024 : static_cast<ParquetFormatter*>(myFormatter)->writeAttr(getOStream(), attr, val, isNull);
249 : #endif
250 : } else {
251 113976 : static_cast<CSVFormatter*>(myFormatter)->writeAttr(getOStream(), attr, val, isNull);
252 : }
253 57138020 : return *this;
254 : }
255 :
256 : /** @brief Parses a list of strings for attribute names and sets the relevant bits in the returned mask.
257 : *
258 : * It honors the special value "all" to set all bits and other special values for predefined bit sets given as parameter
259 : *
260 : * @param[in] attrList The attribute names and special values
261 : * @param[in] desc A descriptive string for the error message if the attribute is unknown
262 : * @param[in] special special values for predefined bitsets
263 : * @return The corresponding mask of bits being set
264 : */
265 : static const SumoXMLAttrMask parseWrittenAttributes(const std::vector<std::string>& attrList, const std::string& desc,
266 : const std::map<std::string, SumoXMLAttrMask>& special = std::map<std::string, SumoXMLAttrMask>());
267 :
268 : /** @brief writes a named attribute unless filtered
269 : *
270 : * @param[in] attr The attribute (name)
271 : * @param[in] val The attribute value
272 : * @param[in] attributeMask The filter that specifies whether the attribute shall be written
273 : * @param[in] isNull Whether the value should be represented as None / null in output formats which support it
274 : * @return The OutputDevice for further processing
275 : */
276 : template <typename T>
277 72718170 : OutputDevice& writeOptionalAttr(const SumoXMLAttr attr, const T& val, const SumoXMLAttrMask& attributeMask, const bool isNull = false) {
278 : assert((int)attr <= (int)attributeMask.size());
279 72718170 : if (attributeMask.none() || attributeMask.test(attr)) {
280 32376804 : if (myFormatter->getType() == OutputFormatterType::XML) {
281 32368720 : if (!isNull) {
282 26391061 : PlainXMLFormatter::writeAttr(getOStream(), attr, val);
283 : }
284 : #ifdef HAVE_PARQUET
285 8084 : } else if (myFormatter->getType() == OutputFormatterType::PARQUET) {
286 4114 : static_cast<ParquetFormatter*>(myFormatter)->writeAttr(getOStream(), attr, val, isNull);
287 : #endif
288 : } else {
289 3970 : static_cast<CSVFormatter*>(myFormatter)->writeAttr(getOStream(), attr, val, isNull);
290 : }
291 : }
292 72718170 : return *this;
293 : }
294 :
295 : template <typename Func>
296 163031695 : OutputDevice& writeFuncAttr(const SumoXMLAttr attr, const Func& valFunc, const SumoXMLAttrMask& attributeMask, const bool isNull = false) {
297 : assert((int)attr <= (int)attributeMask.size());
298 163031695 : if (attributeMask.none() || attributeMask.test(attr)) {
299 38049333 : if (myFormatter->getType() == OutputFormatterType::XML) {
300 38037203 : if (!isNull) {
301 45876490 : PlainXMLFormatter::writeAttr(getOStream(), attr, valFunc());
302 : }
303 : #ifdef HAVE_PARQUET
304 12130 : } else if (myFormatter->getType() == OutputFormatterType::PARQUET) {
305 8698 : static_cast<ParquetFormatter*>(myFormatter)->writeAttr(getOStream(), attr, valFunc(), isNull);
306 : #endif
307 : } else {
308 8556 : static_cast<CSVFormatter*>(myFormatter)->writeAttr(getOStream(), attr, valFunc(), isNull);
309 : }
310 : }
311 163031695 : return *this;
312 : }
313 :
314 : /** @brief writes a string attribute only if it is not the empty string and not the string "default"
315 : *
316 : * @param[in] attr The attribute (name)
317 : * @param[in] val The attribute value
318 : * @return The OutputDevice for further processing
319 : */
320 290913 : OutputDevice& writeNonEmptyAttr(const SumoXMLAttr attr, const std::string& val) {
321 290913 : if (val != "" && val != "default") {
322 290883 : writeAttr(attr, val);
323 : }
324 290913 : return *this;
325 : }
326 :
327 : OutputDevice& writeTime(const SumoXMLAttr attr, const SUMOTime val) {
328 4489831 : myFormatter->writeTime(getOStream(), attr, val);
329 3837361 : return *this;
330 : }
331 :
332 : /** @brief writes a preformatted tag to the device but ensures that any
333 : * pending tags are closed
334 : * @param[in] val The preformatted data
335 : * @return The OutputDevice for further processing
336 : */
337 : OutputDevice& writePreformattedTag(const std::string& val) {
338 1503 : myFormatter->writePreformattedTag(getOStream(), val);
339 1455 : return *this;
340 : }
341 :
342 : /// @brief writes padding (ignored for binary output)
343 : OutputDevice& writePadding(const std::string& val) {
344 130753 : myFormatter->writePadding(getOStream(), val);
345 130753 : return *this;
346 : }
347 :
348 : /** @brief Retrieves a message to this device.
349 : *
350 : * Implementation of the MessageRetriever interface. Writes the given message to the output device.
351 : *
352 : * @param[in] msg The msg to write to the device
353 : */
354 : void inform(const std::string& msg, const bool progress = false);
355 :
356 :
357 : /** @brief Abstract output operator
358 : * @return The OutputDevice for further processing
359 : */
360 : template <class T>
361 3659279 : OutputDevice& operator<<(const T& t) {
362 3659279 : getOStream() << t;
363 3659279 : postWriteHook();
364 3659279 : return *this;
365 : }
366 :
367 : void flush() {
368 16896312 : getOStream().flush();
369 16896312 : }
370 :
371 : bool wroteHeader() const {
372 262180 : return myFormatter->wroteHeader();
373 : }
374 :
375 : void setExpectedAttributes(const SumoXMLAttrMask& expected, const int depth = 2) {
376 29232 : myFormatter->setExpectedAttributes(expected, depth);
377 4253 : }
378 :
379 : protected:
380 : /// @brief Returns the associated ostream
381 : virtual std::ostream& getOStream() = 0;
382 :
383 : /** @brief Called after every write access.
384 : *
385 : * Default implementation does nothing.
386 : */
387 : virtual void postWriteHook();
388 :
389 :
390 : private:
391 : /// @brief map from names to output devices
392 : static std::map<std::string, OutputDevice*> myOutputDevices;
393 :
394 : /// @brief old console code page to restore after ending
395 : static int myPrevConsoleCP;
396 :
397 : protected:
398 : const std::string myFilename;
399 :
400 : bool myWriteMetadata;
401 :
402 : /// @brief The formatter for XML, CSV or Parquet
403 : OutputFormatter* myFormatter;
404 :
405 : private:
406 : /// @brief Invalidated copy constructor.
407 : OutputDevice(const OutputDevice&) = delete;
408 :
409 : /// @brief Invalidated assignment operator.
410 : OutputDevice& operator=(const OutputDevice&) = delete;
411 :
412 : };
|