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 : };
|