Line data Source code
1 : /****************************************************************************/
2 : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3 : // Copyright (C) 2004-2025 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 : * @return Whether a device was built (the option was set)
98 : * @exception IOError If the output could not be built for any reason (error message is supplied)
99 : */
100 : static bool createDeviceByOption(const std::string& optionName,
101 : const std::string& rootElement = "",
102 : const std::string& schemaFile = "");
103 :
104 :
105 : /** @brief Returns the device described by the option
106 : *
107 : * Returns the device named by the option. If the option is unknown, unset
108 : * or the device was not created before, InvalidArgument is thrown.
109 : *
110 : * Please note, that we do not have to consider the "application base" herein.
111 : *
112 : * @param[in] name The name of the option to use for retrieving the output definition
113 : * @return The corresponding (built or existing) device
114 : * @exception IOError If the output could not be built for any reason (error message is supplied)
115 : * @exception InvalidArgument If the option with the given name does not exist
116 : */
117 : static OutputDevice& getDeviceByOption(const std::string& name);
118 :
119 : /** Flushes all registered devices
120 : */
121 : static void flushAll();
122 :
123 : /** Closes all registered devices
124 : */
125 : static void closeAll(bool keepErrorRetrievers = false);
126 : /// @}
127 :
128 : public:
129 : /// @name OutputDevice member methods
130 : /// @{
131 :
132 : /// @brief Constructor
133 : OutputDevice(const int defaultIndentation = 0, const std::string& filename = "");
134 :
135 :
136 : /// @brief Destructor
137 : virtual ~OutputDevice();
138 :
139 :
140 : /** @brief returns the information whether one can write into the device
141 : * @return Whether the device can be used (stream is good)
142 : */
143 : virtual bool ok();
144 :
145 : /** @brief returns the information whether the device will discard all output
146 : * @return Whether the device redirects to /dev/null
147 : */
148 0 : virtual bool isNull() {
149 0 : return false;
150 : }
151 :
152 : /// @brief get filename or suitable description of this device
153 : const std::string& getFilename();
154 :
155 : /** @brief Closes the device and removes it from the dictionary
156 : */
157 : void close();
158 :
159 : void setFormatter(OutputFormatter* formatter) {
160 30 : delete myFormatter;
161 30 : myFormatter = formatter;
162 : }
163 :
164 : /** @brief Sets the precision or resets it to default
165 : * @param[in] precision The accuracy (number of digits behind '.') to set
166 : */
167 : void setPrecision(int precision = gPrecision);
168 :
169 : /** @brief Returns the precision of the underlying stream
170 : */
171 : int getPrecision() {
172 53300 : return (int)getOStream().precision();
173 : }
174 :
175 : /** @brief Writes an XML header with optional configuration
176 : *
177 : * If something has been written (myXMLStack is not empty), nothing
178 : * is written and false returned.
179 : *
180 : * @param[in] rootElement The root element to use
181 : * @param[in] schemaFile The basename of the schema file to use
182 : * @param[in] attrs Additional attributes to save within the rootElement
183 : * @return Whether the header could be written (stack was empty)
184 : * @todo Describe what is saved
185 : */
186 : bool writeXMLHeader(const std::string& rootElement,
187 : const std::string& schemaFile,
188 : std::map<SumoXMLAttr, std::string> attrs = std::map<SumoXMLAttr, std::string>(),
189 : bool includeConfig = true);
190 :
191 : /** @brief Opens an XML tag
192 : *
193 : * An indentation, depending on the current xml-element-stack size, is written followed
194 : * by the given xml element ("<" + xmlElement)
195 : * The xml element is added to the stack, then.
196 : *
197 : * @param[in] xmlElement Name of element to open
198 : * @return The OutputDevice for further processing
199 : */
200 : OutputDevice& openTag(const std::string& xmlElement);
201 :
202 : /** @brief Opens an XML tag
203 : *
204 : * Helper method which finds the correct string before calling openTag.
205 : *
206 : * @param[in] xmlElement Id of the element to open
207 : * @return The OutputDevice for further processing
208 : */
209 : OutputDevice& openTag(const SumoXMLTag& xmlElement);
210 :
211 : /** @brief Closes the most recently opened tag and optionally adds a comment
212 : *
213 : * The topmost xml-element from the stack is written into the stream
214 : * as a closing element. Depending on the formatter used
215 : * this may be something like "</" + element + ">" or "/>" or
216 : * nothing at all.
217 : *
218 : * @return Whether a further element existed in the stack and could be closed
219 : * @todo it is not verified that the topmost element was closed
220 : */
221 : bool closeTag(const std::string& comment = "");
222 :
223 : /** @brief writes a line feed if applicable
224 : */
225 143326 : void lf() {
226 143326 : getOStream() << "\n";
227 143326 : }
228 :
229 : /** @brief writes a named attribute
230 : *
231 : * @param[in] attr The attribute (name)
232 : * @param[in] val The attribute value
233 : * @return The OutputDevice for further processing
234 : */
235 : template <typename T>
236 34757954 : OutputDevice& writeAttr(const SumoXMLAttr attr, const T& val) {
237 34757954 : if (myFormatter->getType() == OutputFormatterType::XML) {
238 34756514 : PlainXMLFormatter::writeAttr(getOStream(), attr, val);
239 : #ifdef HAVE_PARQUET
240 1440 : } else if (myFormatter->getType() == OutputFormatterType::PARQUET) {
241 744 : static_cast<ParquetFormatter*>(myFormatter)->writeAttr(getOStream(), attr, val);
242 : #endif
243 : } else {
244 696 : static_cast<CSVFormatter*>(myFormatter)->writeAttr(getOStream(), attr, val);
245 : }
246 34757954 : return *this;
247 : }
248 :
249 : /** @brief Parses a list of strings for attribute names and sets the relevant bits in the returned mask.
250 : *
251 : * It honors the special value "all" to set all bits and other special values for predefined bit sets given as parameter
252 : *
253 : * @param[in] attrList The attribute names and special values
254 : * @param[in] desc A descriptive string for the error message if the attribute is unknown
255 : * @param[in] special special values for predefined bitsets
256 : * @return The corresponding mask of bits being set
257 : */
258 : static const SumoXMLAttrMask parseWrittenAttributes(const std::vector<std::string>& attrList, const std::string& desc,
259 : const std::map<std::string, SumoXMLAttrMask>& special = std::map<std::string, SumoXMLAttrMask>());
260 :
261 : /** @brief writes a named attribute unless filtered
262 : *
263 : * @param[in] attr The attribute (name)
264 : * @param[in] val The attribute value
265 : * @param[in] attributeMask The filter that specifies whether the attribute shall be written
266 : * @return The OutputDevice for further processing
267 : */
268 : template <typename T>
269 31410042 : OutputDevice& writeOptionalAttr(const SumoXMLAttr attr, const T& val, const SumoXMLAttrMask& attributeMask, const bool isNull = false) {
270 : assert((int)attr <= (int)attributeMask.size());
271 31410042 : if (attributeMask.none() || attributeMask.test(attr)) {
272 23095119 : if (myFormatter->getType() == OutputFormatterType::XML) {
273 23092191 : if (!isNull) {
274 17825785 : PlainXMLFormatter::writeAttr(getOStream(), attr, val);
275 : }
276 : #ifdef HAVE_PARQUET
277 2928 : } else if (myFormatter->getType() == OutputFormatterType::PARQUET) {
278 1536 : static_cast<ParquetFormatter*>(myFormatter)->writeAttr(getOStream(), attr, val, isNull);
279 : #endif
280 : } else {
281 1392 : if (isNull) {
282 0 : static_cast<CSVFormatter*>(myFormatter)->writeNull(getOStream(), attr);
283 : } else {
284 1392 : static_cast<CSVFormatter*>(myFormatter)->writeAttr(getOStream(), attr, val);
285 : }
286 : }
287 : }
288 31410042 : return *this;
289 : }
290 :
291 : template <typename Func>
292 43817494 : OutputDevice& writeFuncAttr(const SumoXMLAttr attr, const Func& valFunc, const SumoXMLAttrMask& attributeMask, const bool isNull = false) {
293 : assert((int)attr <= (int)attributeMask.size());
294 43817494 : if (attributeMask.none() || attributeMask.test(attr)) {
295 7439081 : if (myFormatter->getType() == OutputFormatterType::XML) {
296 7429231 : if (!isNull) {
297 9622576 : PlainXMLFormatter::writeAttr(getOStream(), attr, valFunc());
298 : }
299 : #ifdef HAVE_PARQUET
300 9850 : } else if (myFormatter->getType() == OutputFormatterType::PARQUET) {
301 7102 : static_cast<ParquetFormatter*>(myFormatter)->writeAttr(getOStream(), attr, valFunc(), isNull);
302 : #endif
303 : } else {
304 4872 : if (isNull) {
305 696 : static_cast<CSVFormatter*>(myFormatter)->writeNull(getOStream(), attr);
306 : } else {
307 5568 : static_cast<CSVFormatter*>(myFormatter)->writeAttr(getOStream(), attr, valFunc());
308 : }
309 : }
310 : }
311 43817494 : return *this;
312 : }
313 :
314 : /** @brief writes an arbitrary attribute
315 : *
316 : * @param[in] attr The attribute (name)
317 : * @param[in] val The attribute value
318 : * @return The OutputDevice for further processing
319 : */
320 : template <typename T>
321 19096754 : OutputDevice& writeAttr(const std::string& attr, const T& val) {
322 19096754 : if (myFormatter->getType() == OutputFormatterType::XML) {
323 19096754 : PlainXMLFormatter::writeAttr(getOStream(), attr, val);
324 : #ifdef HAVE_PARQUET
325 0 : } else if (myFormatter->getType() == OutputFormatterType::PARQUET) {
326 0 : static_cast<ParquetFormatter*>(myFormatter)->writeAttr(getOStream(), attr, val);
327 : #endif
328 : } else {
329 0 : static_cast<CSVFormatter*>(myFormatter)->writeAttr(getOStream(), attr, val);
330 : }
331 19096754 : return *this;
332 : }
333 :
334 : /** @brief writes a string attribute only if it is not the empty string and not the string "default"
335 : *
336 : * @param[in] attr The attribute (name)
337 : * @param[in] val The attribute value
338 : * @return The OutputDevice for further processing
339 : */
340 290113 : OutputDevice& writeNonEmptyAttr(const SumoXMLAttr attr, const std::string& val) {
341 290113 : if (val != "" && val != "default") {
342 290083 : writeAttr(attr, val);
343 : }
344 290113 : return *this;
345 : }
346 :
347 : OutputDevice& writeTime(const SumoXMLAttr attr, const SUMOTime val) {
348 2048607 : myFormatter->writeTime(getOStream(), attr, val);
349 2048607 : return *this;
350 : }
351 :
352 : /** @brief writes a preformatted tag to the device but ensures that any
353 : * pending tags are closed
354 : * @param[in] val The preformatted data
355 : * @return The OutputDevice for further processing
356 : */
357 : OutputDevice& writePreformattedTag(const std::string& val) {
358 1497 : myFormatter->writePreformattedTag(getOStream(), val);
359 1449 : return *this;
360 : }
361 :
362 : /// @brief writes padding (ignored for binary output)
363 : OutputDevice& writePadding(const std::string& val) {
364 129148 : myFormatter->writePadding(getOStream(), val);
365 129148 : return *this;
366 : }
367 :
368 : /** @brief Retrieves a message to this device.
369 : *
370 : * Implementation of the MessageRetriever interface. Writes the given message to the output device.
371 : *
372 : * @param[in] msg The msg to write to the device
373 : */
374 : void inform(const std::string& msg, const bool progress = false);
375 :
376 :
377 : /** @brief Abstract output operator
378 : * @return The OutputDevice for further processing
379 : */
380 : template <class T>
381 36761206 : OutputDevice& operator<<(const T& t) {
382 36761206 : getOStream() << t;
383 36761206 : postWriteHook();
384 36761206 : return *this;
385 : }
386 :
387 : void flush() {
388 11873602 : getOStream().flush();
389 11873602 : }
390 :
391 : bool wroteHeader() const {
392 241993 : return myFormatter->wroteHeader();
393 : }
394 :
395 : void setExpectedAttributes(const SumoXMLAttrMask& expected, const int depth = 2) {
396 3050 : myFormatter->setExpectedAttributes(expected, depth);
397 3050 : }
398 :
399 : protected:
400 : /// @brief Returns the associated ostream
401 : virtual std::ostream& getOStream() = 0;
402 :
403 : /** @brief Called after every write access.
404 : *
405 : * Default implementation does nothing.
406 : */
407 : virtual void postWriteHook();
408 :
409 :
410 : private:
411 : /// @brief map from names to output devices
412 : static std::map<std::string, OutputDevice*> myOutputDevices;
413 :
414 : /// @brief old console code page to restore after ending
415 : static int myPrevConsoleCP;
416 :
417 : protected:
418 : const std::string myFilename;
419 :
420 : bool myWriteMetadata;
421 :
422 : /// @brief The formatter for XML, CSV or Parquet
423 : OutputFormatter* myFormatter;
424 :
425 : private:
426 : /// @brief Invalidated copy constructor.
427 : OutputDevice(const OutputDevice&) = delete;
428 :
429 : /// @brief Invalidated assignment operator.
430 : OutputDevice& operator=(const OutputDevice&) = delete;
431 :
432 : };
|