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.cpp
15 : /// @author Daniel Krajzewicz
16 : /// @author Jakob Erdmann
17 : /// @author Michael Behrisch
18 : /// @date 2004
19 : ///
20 : // Static storage of an output device and its base (abstract) implementation
21 : /****************************************************************************/
22 : #include <config.h>
23 :
24 : #include <map>
25 : #include <fstream>
26 : #include <sstream>
27 : #include <string>
28 : #include <iomanip>
29 : #ifdef WIN32
30 : #define NOMINMAX
31 : #include <windows.h>
32 : #undef NOMINMAX
33 : #endif
34 : #include "OutputDevice.h"
35 : #include "OutputDevice_File.h"
36 : #include "OutputDevice_COUT.h"
37 : #include "OutputDevice_CERR.h"
38 : #include "OutputDevice_Network.h"
39 : #include "PlainXMLFormatter.h"
40 : #include <utils/common/StringUtils.h>
41 : #include <utils/common/UtilExceptions.h>
42 : #include <utils/common/FileHelpers.h>
43 : #include <utils/common/ToString.h>
44 : #include <utils/common/MsgHandler.h>
45 : #include <utils/options/OptionsCont.h>
46 : #include <utils/options/OptionsIO.h>
47 :
48 :
49 : // ===========================================================================
50 : // static member definitions
51 : // ===========================================================================
52 : std::map<std::string, OutputDevice*> OutputDevice::myOutputDevices;
53 : int OutputDevice::myPrevConsoleCP = -1;
54 :
55 :
56 : // ===========================================================================
57 : // static method definitions
58 : // ===========================================================================
59 : OutputDevice&
60 25780165 : OutputDevice::getDevice(const std::string& name, bool usePrefix) {
61 : #ifdef WIN32
62 : // fix the windows console output on first call
63 : if (myPrevConsoleCP == -1) {
64 : myPrevConsoleCP = GetConsoleOutputCP();
65 : SetConsoleOutputCP(CP_UTF8);
66 : }
67 : #endif
68 : // check whether the device has already been aqcuired
69 25780165 : if (myOutputDevices.find(name) != myOutputDevices.end()) {
70 25475257 : return *myOutputDevices[name];
71 : }
72 : // build the device
73 304908 : const OptionsCont& oc = OptionsCont::getOptions();
74 304908 : const int len = (int)name.length();
75 1301780 : bool isCSV = (oc.exists("output.format") && oc.getString("output.format") == "csv") || (len > 4 && name.substr(len - 4) == ".csv") || (len > 7 && name.substr(len - 7) == ".csv.gz");
76 974033 : bool isParquet = (oc.exists("output.format") && oc.getString("output.format") == "parquet") || (len > 8 && name.substr(len - 8) == ".parquet");
77 : #ifndef HAVE_PARQUET
78 : if (isParquet) {
79 : WRITE_WARNING("Compiled without Parquet support, falling back to XML.")
80 : isParquet = false;
81 : }
82 : #endif
83 : OutputDevice* dev = nullptr;
84 : // check whether the device shall print to stdout
85 304908 : if (name == "stdout") {
86 85466 : dev = OutputDevice_COUT::getDevice();
87 : isCSV = isParquet = false;
88 219442 : } else if (name == "stderr") {
89 130992 : dev = OutputDevice_CERR::getDevice();
90 : isCSV = isParquet = false;
91 88450 : } else if (FileHelpers::isSocket(name)) {
92 : try {
93 3 : const bool ipv6 = name[0] == '['; // IPv6 addresses may be written like '[::1]:8000'
94 3 : const size_t sepIndex = name.find(":", ipv6 ? name.find("]") : 0);
95 3 : const int port = StringUtils::toInt(name.substr(sepIndex + 1));
96 6 : dev = new OutputDevice_Network(ipv6 ? name.substr(1, sepIndex - 2) : name.substr(0, sepIndex), port);
97 0 : } catch (NumberFormatException&) {
98 0 : throw IOError("Given port number '" + name.substr(name.find(":") + 1) + "' is not numeric.");
99 0 : } catch (EmptyData&) {
100 0 : throw IOError(TL("No port number given."));
101 0 : }
102 : } else {
103 88447 : std::string name2 = (name == "nul" || name == "NUL") ? "/dev/null" : name;
104 176900 : if (usePrefix && oc.isSet("output-prefix") && name2 != "/dev/null") {
105 2192 : std::string prefix = oc.getString("output-prefix");
106 : const std::string::size_type metaTimeIndex = prefix.find("TIME");
107 1096 : if (metaTimeIndex != std::string::npos) {
108 0 : const time_t rawtime = std::chrono::system_clock::to_time_t(OptionsIO::getLoadTime());
109 : char buffer [80];
110 0 : struct tm* timeinfo = localtime(&rawtime);
111 0 : strftime(buffer, 80, "%Y-%m-%d-%H-%M-%S", timeinfo);
112 0 : prefix.replace(metaTimeIndex, 4, buffer);
113 : }
114 2192 : name2 = FileHelpers::prependToLastPathComponent(prefix, name);
115 : }
116 176894 : if (usePrefix && oc.isSet("output-suffix") && name2 != "/dev/null") {
117 216 : std::string suffix = oc.getString("output-suffix");
118 : const std::string::size_type metaTimeIndex = suffix.find("TIME");
119 108 : if (metaTimeIndex != std::string::npos) {
120 0 : const time_t rawtime = std::chrono::system_clock::to_time_t(OptionsIO::getLoadTime());
121 : char buffer [80];
122 0 : struct tm* timeinfo = localtime(&rawtime);
123 0 : strftime(buffer, 80, "%Y-%m-%d-%H-%M-%S", timeinfo);
124 0 : suffix.replace(metaTimeIndex, 4, buffer);
125 : }
126 216 : name2 = FileHelpers::appendBeforeExtension(name2, suffix);
127 : }
128 88531 : name2 = StringUtils::substituteEnvironment(name2, &OptionsIO::getLoadTime());
129 88447 : dev = new OutputDevice_File(name2, isParquet);
130 : }
131 304824 : if (isCSV) {
132 123 : dev->setFormatter(new CSVFormatter(oc.getString("output.column-header"), oc.getString("output.column-separator")[0]));
133 : }
134 : #ifdef HAVE_PARQUET
135 304824 : if (isParquet) {
136 177 : dev->setFormatter(new ParquetFormatter(oc.getString("output.column-header"), oc.getString("output.compression")));
137 : }
138 : #endif
139 304824 : dev->setPrecision();
140 304824 : dev->getOStream() << std::setiosflags(std::ios::fixed);
141 609262 : dev->myWriteMetadata = oc.exists("write-metadata") && oc.getBool("write-metadata");
142 304824 : myOutputDevices[name] = dev;
143 304824 : return *dev;
144 : }
145 :
146 :
147 : bool
148 992135 : OutputDevice::createDeviceByOption(const std::string& optionName,
149 : const std::string& rootElement,
150 : const std::string& schemaFile,
151 : const int maximumDepth) {
152 992135 : if (!OptionsCont::getOptions().isSet(optionName)) {
153 : return false;
154 : }
155 51984 : OutputDevice& dev = OutputDevice::getDevice(OptionsCont::getOptions().getString(optionName));
156 25970 : dev.setExpectedAttributes(0, maximumDepth);
157 25970 : if (rootElement != "") {
158 51938 : dev.writeXMLHeader(rootElement, schemaFile);
159 : }
160 : return true;
161 : }
162 :
163 :
164 : OutputDevice&
165 25135761 : OutputDevice::getDeviceByOption(const std::string& optionName) {
166 25135761 : std::string devName = OptionsCont::getOptions().getString(optionName);
167 25135761 : if (myOutputDevices.find(devName) == myOutputDevices.end()) {
168 0 : throw InvalidArgument("Output device '" + devName + "' for option '" + optionName + "' has not been created.");
169 : }
170 50271522 : return OutputDevice::getDevice(devName);
171 : }
172 :
173 :
174 : void
175 0 : OutputDevice::flushAll() {
176 0 : for (auto item : myOutputDevices) {
177 : item.second->flush();
178 : }
179 0 : }
180 :
181 :
182 : void
183 173416 : OutputDevice::closeAll(bool keepErrorRetrievers) {
184 : std::vector<OutputDevice*> errorDevices;
185 : std::vector<OutputDevice*> nonErrorDevices;
186 511307 : for (std::map<std::string, OutputDevice*>::iterator i = myOutputDevices.begin(); i != myOutputDevices.end(); ++i) {
187 337891 : if (MsgHandler::getErrorInstance()->isRetriever(i->second)) {
188 173836 : errorDevices.push_back(i->second);
189 : } else {
190 164055 : nonErrorDevices.push_back(i->second);
191 : }
192 : }
193 337471 : for (OutputDevice* const dev : nonErrorDevices) {
194 : try {
195 164055 : dev->close();
196 0 : } catch (const IOError& e) {
197 0 : WRITE_ERROR(TL("Error on closing output devices."));
198 0 : WRITE_ERROR(e.what());
199 0 : }
200 : }
201 173416 : if (!keepErrorRetrievers) {
202 262207 : for (OutputDevice* const dev : errorDevices) {
203 : try {
204 131245 : dev->close();
205 0 : } catch (const IOError& e) {
206 : std::cerr << "Error on closing error output devices." << std::endl;
207 0 : std::cerr << e.what() << std::endl;
208 0 : }
209 : }
210 : #ifdef WIN32
211 : if (myPrevConsoleCP != -1) {
212 : SetConsoleOutputCP(myPrevConsoleCP);
213 : }
214 : #endif
215 : }
216 173416 : }
217 :
218 :
219 : // ===========================================================================
220 : // member method definitions
221 : // ===========================================================================
222 1833507 : OutputDevice::OutputDevice(const int defaultIndentation, const std::string& filename) :
223 1833507 : myFilename(filename), myFormatter(new PlainXMLFormatter(defaultIndentation)) {
224 1833507 : }
225 :
226 :
227 1833184 : OutputDevice::~OutputDevice() {
228 1833184 : delete myFormatter;
229 1833184 : }
230 :
231 :
232 : bool
233 0 : OutputDevice::ok() {
234 0 : return getOStream().good();
235 : }
236 :
237 :
238 : const std::string&
239 0 : OutputDevice::getFilename() {
240 0 : return myFilename;
241 : }
242 :
243 : void
244 304732 : OutputDevice::close() {
245 696487 : while (closeTag()) {}
246 412076 : for (std::map<std::string, OutputDevice*>::iterator i = myOutputDevices.begin(); i != myOutputDevices.end(); ++i) {
247 412076 : if (i->second == this) {
248 : myOutputDevices.erase(i);
249 : break;
250 : }
251 : }
252 304732 : MsgHandler::removeRetrieverFromAllInstances(this);
253 304732 : delete this;
254 304732 : }
255 :
256 :
257 : void
258 13461098 : OutputDevice::setPrecision(int precision) {
259 13461098 : getOStream() << std::setprecision(precision);
260 13461098 : }
261 :
262 :
263 : bool
264 99679 : OutputDevice::writeXMLHeader(const std::string& rootElement,
265 : const std::string& schemaFile,
266 : std::map<SumoXMLAttr, std::string> attrs,
267 : bool includeConfig) {
268 99679 : if (schemaFile != "") {
269 95401 : attrs[SUMO_ATTR_XMLNS] = "http://www.w3.org/2001/XMLSchema-instance";
270 95401 : attrs[SUMO_ATTR_SCHEMA_LOCATION] = "http://sumo.dlr.de/xsd/" + schemaFile;
271 : }
272 99679 : return myFormatter->writeXMLHeader(getOStream(), rootElement, attrs, myWriteMetadata, includeConfig);
273 : }
274 :
275 :
276 : OutputDevice&
277 9440351 : OutputDevice::openTag(const std::string& xmlElement) {
278 9440351 : myFormatter->openTag(getOStream(), xmlElement);
279 9440351 : return *this;
280 : }
281 :
282 :
283 : OutputDevice&
284 16145767 : OutputDevice::openTag(const SumoXMLTag& xmlElement) {
285 16145767 : myFormatter->openTag(getOStream(), xmlElement);
286 16145767 : return *this;
287 : }
288 :
289 :
290 : bool
291 25978416 : OutputDevice::closeTag(const std::string& comment) {
292 25978416 : if (myFormatter->closeTag(getOStream(), comment)) {
293 25673684 : postWriteHook();
294 25673683 : return true;
295 : }
296 : return false;
297 : }
298 :
299 :
300 : void
301 29344214 : OutputDevice::postWriteHook() {}
302 :
303 :
304 : void
305 6917741 : OutputDevice::inform(const std::string& msg, const bool progress) {
306 6917741 : if (progress) {
307 33877 : getOStream() << msg;
308 : } else {
309 13767728 : getOStream() << msg << '\n';
310 : }
311 6917741 : postWriteHook();
312 6917741 : }
313 :
314 :
315 : const SumoXMLAttrMask
316 26032 : OutputDevice::parseWrittenAttributes(const std::vector<std::string>& attrList, const std::string& desc, const std::map<std::string, SumoXMLAttrMask>& special) {
317 26032 : SumoXMLAttrMask result;
318 32600 : for (std::string attrName : attrList) {
319 6568 : if (attrName == "all") {
320 : result.set();
321 : } else if (special.count(attrName) > 0) {
322 : result |= special.find(attrName)->second;
323 : } else {
324 : if (SUMOXMLDefinitions::Attrs.hasString(attrName)) {
325 6506 : int attrNr = SUMOXMLDefinitions::Attrs.get(attrName);
326 6506 : if (attrNr < (int)result.size()) {
327 6496 : result.set(attrNr);
328 : } else {
329 30 : WRITE_ERRORF(TL("Attribute '%' is not support for filtering written attributes in %."), attrName, desc);
330 : }
331 : } else {
332 0 : WRITE_ERRORF(TL("Unknown attribute '%' to write in %."), attrName, desc);
333 : }
334 : }
335 : }
336 26032 : return result;
337 : }
338 :
339 :
340 : /****************************************************************************/
|