Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/src/utils/iodevices/OutputDevice.cpp
193717 views
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
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
if (myOutputDevices.find(name) != myOutputDevices.end()) {
70
return *myOutputDevices[name];
71
}
72
// build the device
73
const OptionsCont& oc = OptionsCont::getOptions();
74
const int len = (int)name.length();
75
bool isParquet = (oc.exists("output.format") && oc.getString("output.format") == "parquet") || (len > 8 && name.substr(len - 8) == ".parquet");
76
#ifndef HAVE_PARQUET
77
if (isParquet) {
78
WRITE_WARNING("Compiled without Parquet support, falling back to XML.")
79
isParquet = false;
80
}
81
#endif
82
OutputDevice* dev = nullptr;
83
// check whether the device shall print to stdout
84
if (name == "stdout") {
85
dev = OutputDevice_COUT::getDevice();
86
} else if (name == "stderr") {
87
dev = OutputDevice_CERR::getDevice();
88
} else if (FileHelpers::isSocket(name)) {
89
try {
90
const bool ipv6 = name[0] == '['; // IPv6 addresses may be written like '[::1]:8000'
91
const size_t sepIndex = name.find(":", ipv6 ? name.find("]") : 0);
92
const int port = StringUtils::toInt(name.substr(sepIndex + 1));
93
dev = new OutputDevice_Network(ipv6 ? name.substr(1, sepIndex - 2) : name.substr(0, sepIndex), port);
94
} catch (NumberFormatException&) {
95
throw IOError("Given port number '" + name.substr(name.find(":") + 1) + "' is not numeric.");
96
} catch (EmptyData&) {
97
throw IOError(TL("No port number given."));
98
}
99
} else {
100
std::string name2 = (name == "nul" || name == "NUL") ? "/dev/null" : name;
101
if (usePrefix && oc.isSet("output-prefix") && name2 != "/dev/null") {
102
std::string prefix = oc.getString("output-prefix");
103
const std::string::size_type metaTimeIndex = prefix.find("TIME");
104
if (metaTimeIndex != std::string::npos) {
105
const time_t rawtime = std::chrono::system_clock::to_time_t(OptionsIO::getLoadTime());
106
char buffer [80];
107
struct tm* timeinfo = localtime(&rawtime);
108
strftime(buffer, 80, "%Y-%m-%d-%H-%M-%S", timeinfo);
109
prefix.replace(metaTimeIndex, 4, buffer);
110
}
111
name2 = FileHelpers::prependToLastPathComponent(prefix, name);
112
}
113
if (usePrefix && oc.isSet("output-suffix") && name2 != "/dev/null") {
114
std::string suffix = oc.getString("output-suffix");
115
const std::string::size_type metaTimeIndex = suffix.find("TIME");
116
if (metaTimeIndex != std::string::npos) {
117
const time_t rawtime = std::chrono::system_clock::to_time_t(OptionsIO::getLoadTime());
118
char buffer [80];
119
struct tm* timeinfo = localtime(&rawtime);
120
strftime(buffer, 80, "%Y-%m-%d-%H-%M-%S", timeinfo);
121
suffix.replace(metaTimeIndex, 4, buffer);
122
}
123
name2 = FileHelpers::appendBeforeExtension(name2, suffix);
124
}
125
name2 = StringUtils::substituteEnvironment(name2, &OptionsIO::getLoadTime());
126
dev = new OutputDevice_File(name2, isParquet);
127
}
128
if ((oc.exists("output.format") && oc.getString("output.format") == "csv") || (len > 4 && name.substr(len - 4) == ".csv") || (len > 7 && name.substr(len - 7) == ".csv.gz")) {
129
dev->setFormatter(new CSVFormatter(oc.getString("output.column-header"), oc.getString("output.column-separator")[0]));
130
}
131
#ifdef HAVE_PARQUET
132
if (isParquet) {
133
dev->setFormatter(new ParquetFormatter(oc.getString("output.column-header"), oc.getString("output.compression")));
134
}
135
#endif
136
dev->setPrecision();
137
dev->getOStream() << std::setiosflags(std::ios::fixed);
138
dev->myWriteMetadata = oc.exists("write-metadata") && oc.getBool("write-metadata");
139
myOutputDevices[name] = dev;
140
return *dev;
141
}
142
143
144
bool
145
OutputDevice::createDeviceByOption(const std::string& optionName,
146
const std::string& rootElement,
147
const std::string& schemaFile) {
148
if (!OptionsCont::getOptions().isSet(optionName)) {
149
return false;
150
}
151
OutputDevice& dev = OutputDevice::getDevice(OptionsCont::getOptions().getString(optionName));
152
if (rootElement != "") {
153
dev.writeXMLHeader(rootElement, schemaFile);
154
}
155
return true;
156
}
157
158
159
OutputDevice&
160
OutputDevice::getDeviceByOption(const std::string& optionName) {
161
std::string devName = OptionsCont::getOptions().getString(optionName);
162
if (myOutputDevices.find(devName) == myOutputDevices.end()) {
163
throw InvalidArgument("Output device '" + devName + "' for option '" + optionName + "' has not been created.");
164
}
165
return OutputDevice::getDevice(devName);
166
}
167
168
169
void
170
OutputDevice::flushAll() {
171
for (auto item : myOutputDevices) {
172
item.second->flush();
173
}
174
}
175
176
177
void
178
OutputDevice::closeAll(bool keepErrorRetrievers) {
179
std::vector<OutputDevice*> errorDevices;
180
std::vector<OutputDevice*> nonErrorDevices;
181
for (std::map<std::string, OutputDevice*>::iterator i = myOutputDevices.begin(); i != myOutputDevices.end(); ++i) {
182
if (MsgHandler::getErrorInstance()->isRetriever(i->second)) {
183
errorDevices.push_back(i->second);
184
} else {
185
nonErrorDevices.push_back(i->second);
186
}
187
}
188
for (OutputDevice* const dev : nonErrorDevices) {
189
try {
190
dev->close();
191
} catch (const IOError& e) {
192
WRITE_ERROR(TL("Error on closing output devices."));
193
WRITE_ERROR(e.what());
194
}
195
}
196
if (!keepErrorRetrievers) {
197
for (OutputDevice* const dev : errorDevices) {
198
try {
199
dev->close();
200
} catch (const IOError& e) {
201
std::cerr << "Error on closing error output devices." << std::endl;
202
std::cerr << e.what() << std::endl;
203
}
204
}
205
#ifdef WIN32
206
if (myPrevConsoleCP != -1) {
207
SetConsoleOutputCP(myPrevConsoleCP);
208
}
209
#endif
210
}
211
}
212
213
214
// ===========================================================================
215
// member method definitions
216
// ===========================================================================
217
OutputDevice::OutputDevice(const int defaultIndentation, const std::string& filename) :
218
myFilename(filename), myFormatter(new PlainXMLFormatter(defaultIndentation)) {
219
}
220
221
222
OutputDevice::~OutputDevice() {
223
delete myFormatter;
224
}
225
226
227
bool
228
OutputDevice::ok() {
229
return getOStream().good();
230
}
231
232
233
const std::string&
234
OutputDevice::getFilename() {
235
return myFilename;
236
}
237
238
void
239
OutputDevice::close() {
240
while (closeTag()) {}
241
for (std::map<std::string, OutputDevice*>::iterator i = myOutputDevices.begin(); i != myOutputDevices.end(); ++i) {
242
if (i->second == this) {
243
myOutputDevices.erase(i);
244
break;
245
}
246
}
247
MsgHandler::removeRetrieverFromAllInstances(this);
248
delete this;
249
}
250
251
252
void
253
OutputDevice::setPrecision(int precision) {
254
getOStream() << std::setprecision(precision);
255
}
256
257
258
bool
259
OutputDevice::writeXMLHeader(const std::string& rootElement,
260
const std::string& schemaFile,
261
std::map<SumoXMLAttr, std::string> attrs,
262
bool includeConfig) {
263
if (schemaFile != "") {
264
attrs[SUMO_ATTR_XMLNS] = "http://www.w3.org/2001/XMLSchema-instance";
265
attrs[SUMO_ATTR_SCHEMA_LOCATION] = "http://sumo.dlr.de/xsd/" + schemaFile;
266
}
267
return myFormatter->writeXMLHeader(getOStream(), rootElement, attrs, myWriteMetadata, includeConfig);
268
}
269
270
271
OutputDevice&
272
OutputDevice::openTag(const std::string& xmlElement) {
273
myFormatter->openTag(getOStream(), xmlElement);
274
return *this;
275
}
276
277
278
OutputDevice&
279
OutputDevice::openTag(const SumoXMLTag& xmlElement) {
280
myFormatter->openTag(getOStream(), xmlElement);
281
return *this;
282
}
283
284
285
bool
286
OutputDevice::closeTag(const std::string& comment) {
287
if (myFormatter->closeTag(getOStream(), comment)) {
288
postWriteHook();
289
return true;
290
}
291
return false;
292
}
293
294
295
void
296
OutputDevice::postWriteHook() {}
297
298
299
void
300
OutputDevice::inform(const std::string& msg, const bool progress) {
301
if (progress) {
302
getOStream() << msg;
303
} else {
304
getOStream() << msg << '\n';
305
}
306
postWriteHook();
307
}
308
309
310
const SumoXMLAttrMask
311
OutputDevice::parseWrittenAttributes(const std::vector<std::string>& attrList, const std::string& desc, const std::map<std::string, SumoXMLAttrMask>& special) {
312
SumoXMLAttrMask result;
313
for (std::string attrName : attrList) {
314
if (attrName == "all") {
315
result.set();
316
} else if (special.count(attrName) > 0) {
317
result |= special.find(attrName)->second;
318
} else {
319
if (SUMOXMLDefinitions::Attrs.hasString(attrName)) {
320
int attrNr = SUMOXMLDefinitions::Attrs.get(attrName);
321
if (attrNr < (int)result.size()) {
322
result.set(attrNr);
323
} else {
324
WRITE_ERRORF(TL("Attribute '%' is not support for filtering written attributes in %."), attrName, desc);
325
}
326
} else {
327
WRITE_ERRORF(TL("Unknown attribute '%' to write in %."), attrName, desc);
328
}
329
}
330
}
331
return result;
332
}
333
334
335
/****************************************************************************/
336
337