Line data Source code
1 : /****************************************************************************/
2 : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3 : // Copyright (C) 2001-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 NLBuilder.cpp
15 : /// @author Daniel Krajzewicz
16 : /// @author Jakob Erdmann
17 : /// @author Michael Behrisch
18 : /// @date Mon, 9 Jul 2001
19 : ///
20 : // The main interface for loading a microsim
21 : /****************************************************************************/
22 : #include <config.h>
23 :
24 : #include <iostream>
25 : #include <vector>
26 : #include <string>
27 : #include <map>
28 :
29 : #include <utils/common/MsgHandler.h>
30 : #include <utils/common/StringTokenizer.h>
31 : #include <utils/common/SystemFrame.h>
32 : #include <utils/options/Option.h>
33 : #include <utils/options/OptionsCont.h>
34 : #include <utils/options/OptionsIO.h>
35 : #include <utils/common/StringUtils.h>
36 : #include <utils/common/FileHelpers.h>
37 : #include <utils/common/SysUtils.h>
38 : #include <utils/common/ToString.h>
39 : #include <utils/vehicle/SUMORouteLoaderControl.h>
40 : #include <utils/vehicle/SUMORouteLoader.h>
41 : #include <utils/xml/XMLSubSys.h>
42 : #ifdef HAVE_FOX
43 : #include <utils/foxtools/MsgHandlerSynchronized.h>
44 : #endif
45 : #include <libsumo/Helper.h>
46 : #include <mesosim/MEVehicleControl.h>
47 : #include <microsim/MSVehicleControl.h>
48 : #include <microsim/MSVehicleTransfer.h>
49 : #include <microsim/MSNet.h>
50 : #include <microsim/devices/MSDevice.h>
51 : #include <microsim/devices/MSDevice_ToC.h>
52 : #include <microsim/devices/MSDevice_BTreceiver.h>
53 : #include <microsim/devices/MSDevice_FCDReplay.h>
54 : #include <microsim/MSEdgeControl.h>
55 : #include <microsim/MSGlobals.h>
56 : #include <microsim/output/MSDetectorControl.h>
57 : #include <microsim/MSFrame.h>
58 : #include <microsim/MSEdgeWeightsStorage.h>
59 : #include <microsim/MSStateHandler.h>
60 : #include <microsim/MSDriverState.h>
61 : #include <microsim/trigger/MSTriggeredRerouter.h>
62 : #include <traci-server/TraCIServer.h>
63 :
64 : #include "NLHandler.h"
65 : #include "NLNetShapeHandler.h"
66 : #include "NLEdgeControlBuilder.h"
67 : #include "NLJunctionControlBuilder.h"
68 : #include "NLDetectorBuilder.h"
69 : #include "NLTriggerBuilder.h"
70 : #include "NLBuilder.h"
71 :
72 :
73 : // ===========================================================================
74 : // method definitions
75 : // ===========================================================================
76 : // ---------------------------------------------------------------------------
77 : // NLBuilder::EdgeFloatTimeLineRetriever_EdgeWeight - methods
78 : // ---------------------------------------------------------------------------
79 : void
80 0 : NLBuilder::EdgeFloatTimeLineRetriever_EdgeEffort::addEdgeWeight(const std::string& id,
81 : double value, double begTime, double endTime) const {
82 0 : MSEdge* edge = MSEdge::dictionary(id);
83 0 : if (edge != nullptr) {
84 0 : myNet.getWeightsStorage().addEffort(edge, begTime, endTime, value);
85 : } else {
86 0 : WRITE_ERRORF(TL("Trying to set the effort for the unknown edge '%'."), id);
87 : }
88 0 : }
89 :
90 :
91 : // ---------------------------------------------------------------------------
92 : // NLBuilder::EdgeFloatTimeLineRetriever_EdgeTravelTime - methods
93 : // ---------------------------------------------------------------------------
94 : void
95 18 : NLBuilder::EdgeFloatTimeLineRetriever_EdgeTravelTime::addEdgeWeight(const std::string& id,
96 : double value, double begTime, double endTime) const {
97 18 : MSEdge* edge = MSEdge::dictionary(id);
98 18 : if (edge != nullptr) {
99 18 : myNet.getWeightsStorage().addTravelTime(edge, begTime, endTime, value);
100 : } else {
101 0 : WRITE_ERRORF(TL("Trying to set the travel time for the unknown edge '%'."), id);
102 : }
103 18 : }
104 :
105 :
106 : // ---------------------------------------------------------------------------
107 : // NLBuilder - methods
108 : // ---------------------------------------------------------------------------
109 43757 : NLBuilder::NLBuilder(OptionsCont& oc,
110 : MSNet& net,
111 : NLEdgeControlBuilder& eb,
112 : NLJunctionControlBuilder& jb,
113 : NLDetectorBuilder& db,
114 43757 : NLHandler& xmlHandler)
115 43757 : : myOptions(oc), myEdgeBuilder(eb), myJunctionBuilder(jb),
116 43757 : myDetectorBuilder(db),
117 43757 : myNet(net), myXMLHandler(xmlHandler) {}
118 :
119 :
120 43757 : NLBuilder::~NLBuilder() {}
121 :
122 :
123 : bool
124 43757 : NLBuilder::build() {
125 : // try to build the net
126 87514 : if (!load("net-file", true)) {
127 : return false;
128 : }
129 43381 : if (myXMLHandler.networkVersion() == MMVersion(0, 0)) {
130 0 : throw ProcessError(TL("Invalid network, no network version declared."));
131 : }
132 : // check whether the loaded net agrees with the simulation options
133 89623 : if ((myOptions.getBool("no-internal-links") || myOptions.getBool("mesosim")) && myXMLHandler.haveSeenInternalEdge() && myXMLHandler.haveSeenDefaultLength()) {
134 5740 : WRITE_WARNING(TL("Network contains internal links which are ignored. Vehicles will 'jump' across junctions and thus underestimate route lengths and travel times."));
135 : }
136 43381 : buildNet();
137 86644 : if (myOptions.isSet("alternative-net-file")) {
138 0 : for (std::string fname : myOptions.getStringVector("alternative-net-file")) {
139 0 : const long before = PROGRESS_BEGIN_TIME_MESSAGE("Loading alternative net from '" + fname + "'");
140 0 : NLNetShapeHandler nsh(fname, myNet);
141 0 : if (!XMLSubSys::runParser(nsh, fname, true)) {
142 0 : WRITE_MESSAGE("Loading of alternative net failed.");
143 : return false;
144 : }
145 0 : nsh.sortInternalShapes();
146 0 : PROGRESS_TIME_MESSAGE(before);
147 0 : }
148 : }
149 : // @note on loading order constraints:
150 : // - additional-files before route-files and state-files due to referencing
151 : // - additional-files before weight-files since the latter might contain intermodal edge data and the intermodal net depends on the stops and public transport from the additionals
152 :
153 : bool stateBeginMismatch = false;
154 86644 : if (myOptions.isSet("load-state")) {
155 : // first, load only the time
156 220 : const SUMOTime stateTime = MSStateHandler::MSStateTimeHandler::getTime(myOptions.getString("load-state"));
157 424 : if (myOptions.isDefault("begin")) {
158 300 : myOptions.set("begin", time2string(stateTime));
159 150 : if (TraCIServer::getInstance() != nullptr) {
160 18 : TraCIServer::getInstance()->stateLoaded(stateTime);
161 : }
162 : } else {
163 124 : if (stateTime != string2time(myOptions.getString("begin"))) {
164 45 : WRITE_WARNINGF(TL("State was written at a different time=% than the begin time %!"), time2string(stateTime), myOptions.getString("begin"));
165 : stateBeginMismatch = true;
166 : }
167 : }
168 : }
169 :
170 86628 : if (myOptions.getBool("junction-taz")) {
171 : // create a TAZ for every junction
172 7958 : const MSJunctionControl& junctions = myNet.getJunctionControl();
173 86450 : for (auto it = junctions.begin(); it != junctions.end(); it++) {
174 78492 : const std::string sinkID = it->first + "-sink";
175 78492 : const std::string sourceID = it->first + "-source";
176 78492 : if (MSEdge::dictionary(sinkID) == nullptr && MSEdge::dictionary(sourceID) == nullptr) {
177 : // sink must be built and added before source
178 156984 : MSEdge* sink = myEdgeBuilder.buildEdge(sinkID, SumoXMLEdgeFunc::CONNECTOR, "", "", -1, 0);
179 156984 : MSEdge* source = myEdgeBuilder.buildEdge(sourceID, SumoXMLEdgeFunc::CONNECTOR, "", "", -1, 0);
180 : sink->setOtherTazConnector(source);
181 : source->setOtherTazConnector(sink);
182 78492 : MSEdge::dictionary(sinkID, sink);
183 78492 : MSEdge::dictionary(sourceID, source);
184 78492 : sink->initialize(new std::vector<MSLane*>());
185 78492 : source->initialize(new std::vector<MSLane*>());
186 78492 : const MSJunction* junction = it->second;
187 384760 : for (const MSEdge* edge : junction->getIncoming()) {
188 306268 : if (!edge->isInternal()) {
189 176140 : const_cast<MSEdge*>(edge)->addSuccessor(sink);
190 : }
191 : }
192 384760 : for (const MSEdge* edge : junction->getOutgoing()) {
193 306268 : if (!edge->isInternal()) {
194 176140 : source->addSuccessor(const_cast<MSEdge*>(edge));
195 : }
196 : }
197 : } else {
198 0 : WRITE_WARNINGF(TL("A TAZ with id '%' already exists. Not building junction TAZ."), it->first)
199 : }
200 : }
201 : }
202 :
203 : // load additional net elements (sources, detectors, ...)
204 86628 : if (myOptions.isSet("additional-files")) {
205 59420 : if (!load("additional-files")) {
206 751 : return false;
207 : }
208 : // load shapes with separate handler
209 28959 : NLShapeHandler sh("", myNet.getShapeContainer());
210 57918 : if (!ShapeHandler::loadFiles(myOptions.getStringVector("additional-files"), sh)) {
211 : return false;
212 : }
213 28959 : if (myXMLHandler.haveSeenAdditionalSpeedRestrictions()) {
214 65 : myNet.getEdgeControl().setAdditionalRestrictions();
215 : }
216 28959 : if (MSGlobals::gUseMesoSim && myXMLHandler.haveSeenMesoEdgeType()) {
217 20 : myNet.getEdgeControl().setMesoTypes();
218 24 : for (MSTrafficLightLogic* tll : myNet.getTLSControl().getAllLogics()) {
219 4 : tll->initMesoTLSPenalties();
220 20 : }
221 : }
222 28959 : MSTriggeredRerouter::checkParkingRerouteConsistency();
223 : }
224 : // init tls after all detectors have been loaded
225 42563 : myJunctionBuilder.postLoadInitialization();
226 : // declare meandata set by options
227 85064 : buildDefaultMeanData("edgedata-output", "DEFAULT_EDGEDATA", false);
228 85064 : buildDefaultMeanData("lanedata-output", "DEFAULT_LANEDATA", true);
229 :
230 42532 : if (stateBeginMismatch && myNet.getVehicleControl().getLoadedVehicleNo() > 0) {
231 4 : throw ProcessError(TL("Loading vehicles ahead of a state file is not supported. Correct --begin option or load vehicles with option --route-files"));
232 : }
233 :
234 : // load weights if wished
235 85060 : if (myOptions.isSet("weight-files")) {
236 22 : if (!myOptions.isUsableFileList("weight-files")) {
237 0 : return false;
238 : }
239 : // build and prepare the weights handler
240 : std::vector<SAXWeightsHandler::ToRetrieveDefinition*> retrieverDefs;
241 : // travel time, first (always used)
242 11 : EdgeFloatTimeLineRetriever_EdgeTravelTime ttRetriever(myNet);
243 11 : retrieverDefs.push_back(new SAXWeightsHandler::ToRetrieveDefinition("traveltime", true, ttRetriever));
244 : // the measure to use, then
245 11 : EdgeFloatTimeLineRetriever_EdgeEffort eRetriever(myNet);
246 11 : std::string measure = myOptions.getString("weight-attribute");
247 22 : if (!myOptions.isDefault("weight-attribute")) {
248 0 : if (measure == "CO" || measure == "CO2" || measure == "HC" || measure == "PMx" || measure == "NOx" || measure == "fuel" || measure == "electricity") {
249 : measure += "_perVeh";
250 : }
251 0 : retrieverDefs.push_back(new SAXWeightsHandler::ToRetrieveDefinition(measure, true, eRetriever));
252 : }
253 : // set up handler
254 11 : SAXWeightsHandler handler(retrieverDefs, "");
255 : // start parsing; for each file in the list
256 22 : std::vector<std::string> files = myOptions.getStringVector("weight-files");
257 22 : for (std::vector<std::string>::iterator i = files.begin(); i != files.end(); ++i) {
258 : // report about loading when wished
259 33 : WRITE_MESSAGEF(TL("Loading weights from '%'..."), *i);
260 : // parse the file
261 11 : if (!XMLSubSys::runParser(handler, *i)) {
262 : return false;
263 : }
264 : }
265 22 : }
266 : // load the previous state if wished
267 85060 : if (myOptions.isSet("load-state")) {
268 210 : myNet.setCurrentTimeStep(string2time(myOptions.getString("begin")));
269 210 : const std::string& f = myOptions.getString("load-state");
270 630 : long before = PROGRESS_BEGIN_TIME_MESSAGE(TLF("Loading state from '%'", f));
271 210 : MSStateHandler h(f, string2time(myOptions.getString("load-state.offset")));
272 210 : XMLSubSys::runParser(h, f);
273 210 : if (MsgHandler::getErrorInstance()->wasInformed()) {
274 : return false;
275 : }
276 210 : PROGRESS_TIME_MESSAGE(before);
277 210 : }
278 : // routes from FCD files
279 42530 : MSDevice_FCDReplay::init();
280 : // load routes
281 85060 : if (myOptions.isSet("route-files")) {
282 70750 : if (string2time(myOptions.getString("route-steps")) <= 0) {
283 : // incremental loading is disabled. Load route files fully
284 0 : if (!load("route-files")) {
285 : return false;
286 : }
287 : } else {
288 : // message must come after additional-files have been loaded (but buildRouteLoaderControl was called earlier)
289 106345 : for (std::string file : myOptions.getStringVector("route-files")) {
290 106785 : WRITE_MESSAGE(TLF("Loading route-files incrementally from '%'", file));
291 : }
292 : }
293 : }
294 : // optionally switch off traffic lights
295 85060 : if (myOptions.getBool("tls.all-off")) {
296 100 : myNet.getTLSControl().switchOffAll();
297 : }
298 42530 : WRITE_MESSAGE(TL("Loading done."));
299 42530 : return true;
300 : }
301 :
302 :
303 : MSNet*
304 36706 : NLBuilder::init(const bool isLibsumo) {
305 36706 : OptionsCont& oc = OptionsCont::getOptions();
306 36706 : oc.clear();
307 36706 : MSFrame::fillOptions();
308 36706 : OptionsIO::getOptions();
309 36660 : if (oc.processMetaOptions(OptionsIO::getArgC() < 2)) {
310 266 : SystemFrame::close();
311 266 : return nullptr;
312 : }
313 36394 : SystemFrame::checkOptions(oc);
314 36394 : std::string validation = oc.getString("xml-validation");
315 40552 : std::string routeValidation = oc.getString("xml-validation.routes");
316 36394 : if (isLibsumo) {
317 1168 : if (oc.isDefault("xml-validation")) {
318 : validation = "never";
319 : }
320 1168 : if (oc.isDefault("xml-validation.routes")) {
321 : routeValidation = "never";
322 : }
323 : }
324 36394 : XMLSubSys::setValidation(validation, oc.getString("xml-validation.net"), routeValidation);
325 36394 : if (!MSFrame::checkOptions()) {
326 308 : throw ProcessError();
327 : }
328 : #ifdef HAVE_FOX
329 72156 : if (oc.getInt("threads") > 1) {
330 : // make the output aware of threading
331 : MsgHandler::setFactory(&MsgHandlerSynchronized::create);
332 : }
333 : #endif
334 36078 : MsgHandler::initOutputOptions();
335 36078 : initRandomness();
336 36078 : MSFrame::setMSGlobals(oc);
337 : MSVehicleControl* vc = nullptr;
338 36078 : if (MSGlobals::gUseMesoSim) {
339 2996 : vc = new MEVehicleControl();
340 : } else {
341 33082 : vc = new MSVehicleControl();
342 : }
343 36078 : MSNet* net = new MSNet(vc, new MSEventControl(), new MSEventControl(), new MSEventControl());
344 : // need to init TraCI-Server before loading routes to catch VehicleState::BUILT
345 40236 : TraCIServer::openSocket(std::map<int, TraCIServer::CmdExecutor>());
346 36072 : if (isLibsumo) {
347 584 : libsumo::Helper::registerStateListener();
348 : }
349 :
350 36072 : NLEdgeControlBuilder eb;
351 36072 : NLDetectorBuilder db(*net);
352 36072 : NLJunctionControlBuilder jb(*net, db);
353 36072 : NLTriggerBuilder tb;
354 36072 : NLHandler handler("", *net, db, tb, eb, jb);
355 36072 : tb.setHandler(&handler);
356 36072 : NLBuilder builder(oc, *net, eb, jb, db, handler);
357 36072 : MsgHandler::getErrorInstance()->clear();
358 36072 : MsgHandler::getWarningInstance()->clear();
359 36072 : MsgHandler::getMessageInstance()->clear();
360 36072 : if (builder.build()) {
361 : // preload the routes especially for TraCI
362 35094 : net->loadRoutes();
363 : return net;
364 : }
365 899 : delete net;
366 899 : throw ProcessError();
367 55252 : }
368 :
369 :
370 : void
371 43765 : NLBuilder::initRandomness() {
372 43765 : RandHelper::initRandGlobal();
373 43765 : RandHelper::initRandGlobal(MSRouteHandler::getParsingRNG());
374 43765 : RandHelper::initRandGlobal(MSDevice::getEquipmentRNG());
375 43765 : RandHelper::initRandGlobal(OUProcess::getRNG());
376 43765 : RandHelper::initRandGlobal(MSDevice_ToC::getResponseTimeRNG());
377 43765 : RandHelper::initRandGlobal(MSDevice_BTreceiver::getRecognitionRNG());
378 43765 : MSLane::initRNGs(OptionsCont::getOptions());
379 43765 : }
380 :
381 :
382 : void
383 43381 : NLBuilder::buildNet() {
384 : MSEdgeControl* edges = nullptr;
385 : MSJunctionControl* junctions = nullptr;
386 : SUMORouteLoaderControl* routeLoaders = nullptr;
387 : MSTLLogicControl* tlc = nullptr;
388 : std::vector<SUMOTime> stateDumpTimes;
389 : std::vector<std::string> stateDumpFiles;
390 : try {
391 43381 : MSFrame::buildStreams(); // ensure streams are ready for output during building
392 43339 : edges = myEdgeBuilder.build(myXMLHandler.networkVersion());
393 43339 : junctions = myJunctionBuilder.build();
394 43339 : junctions->postloadInitContainer();
395 43335 : routeLoaders = buildRouteLoaderControl(myOptions);
396 43332 : tlc = myJunctionBuilder.buildTLLogics();
397 86961 : for (std::string timeStr : myOptions.getStringVector("save-state.times")) {
398 297 : stateDumpTimes.push_back(string2time(timeStr));
399 : }
400 86664 : if (myOptions.isSet("save-state.files")) {
401 474 : stateDumpFiles = myOptions.getStringVector("save-state.files");
402 237 : if (stateDumpFiles.size() != stateDumpTimes.size()) {
403 0 : throw ProcessError(TL("Wrong number of state file names!"));
404 : }
405 : } else {
406 43095 : const std::string prefix = myOptions.getString("save-state.prefix");
407 86190 : const std::string suffix = myOptions.getString("save-state.suffix");
408 43111 : for (std::vector<SUMOTime>::iterator i = stateDumpTimes.begin(); i != stateDumpTimes.end(); ++i) {
409 16 : std::string timeStamp = time2string(*i);
410 : std::replace(timeStamp.begin(), timeStamp.end(), ':', '-');
411 32 : stateDumpFiles.push_back(prefix + "_" + timeStamp + suffix);
412 : }
413 : }
414 49 : } catch (ProcessError&) {
415 49 : MSEdge::clear();
416 49 : MSLane::clear();
417 49 : delete edges;
418 49 : delete junctions;
419 49 : delete routeLoaders;
420 49 : delete tlc;
421 49 : throw;
422 49 : }
423 : // if anthing goes wrong after this point, the net is responsible for cleaning up
424 43342 : myNet.closeBuilding(myOptions, edges, junctions, routeLoaders, tlc, stateDumpTimes, stateDumpFiles,
425 : myXMLHandler.haveSeenInternalEdge(),
426 : myXMLHandler.hasJunctionHigherSpeeds(),
427 43332 : myXMLHandler.networkVersion());
428 43440 : }
429 :
430 :
431 : bool
432 73467 : NLBuilder::load(const std::string& mmlWhat, const bool isNet) {
433 73467 : if (!myOptions.isUsableFileList(mmlWhat)) {
434 : return false;
435 : }
436 73349 : std::vector<std::string> files = myOptions.getStringVector(mmlWhat);
437 160898 : for (std::vector<std::string>::const_iterator fileIt = files.begin(); fileIt != files.end(); ++fileIt) {
438 265674 : const long before = PROGRESS_BEGIN_TIME_MESSAGE(TLF("Loading % from '%'", mmlWhat, *fileIt));
439 88558 : if (!XMLSubSys::runParser(myXMLHandler, *fileIt, isNet)) {
440 3027 : WRITE_MESSAGEF(TL("Loading of % failed."), mmlWhat);
441 : return false;
442 : }
443 87549 : PROGRESS_TIME_MESSAGE(before);
444 : }
445 : return true;
446 73349 : }
447 :
448 :
449 : SUMORouteLoaderControl*
450 43521 : NLBuilder::buildRouteLoaderControl(const OptionsCont& oc) {
451 : // build the loaders
452 43521 : SUMORouteLoaderControl* loaders = new SUMORouteLoaderControl(string2time(oc.getString("route-steps")));
453 : // check whether a list is existing
454 115441 : if (oc.isSet("route-files") && string2time(oc.getString("route-steps")) > 0) {
455 71920 : std::vector<std::string> files = oc.getStringVector("route-files");
456 72137 : for (std::vector<std::string>::const_iterator fileIt = files.begin(); fileIt != files.end(); ++fileIt) {
457 72360 : if (!FileHelpers::isReadable(*fileIt)) {
458 9 : throw ProcessError(TLF("The route file '%' is not accessible.", *fileIt));
459 : }
460 : }
461 : // open files for reading
462 72134 : for (std::vector<std::string>::const_iterator fileIt = files.begin(); fileIt != files.end(); ++fileIt) {
463 36177 : loaders->add(new SUMORouteLoader(new MSRouteHandler(*fileIt, false)));
464 : }
465 35960 : }
466 43518 : return loaders;
467 : }
468 :
469 :
470 : void
471 85064 : NLBuilder::buildDefaultMeanData(const std::string& optionName, const std::string& id, bool useLanes) {
472 85064 : if (OptionsCont::getOptions().isSet(optionName)) {
473 81 : if (useLanes && MSGlobals::gUseMesoSim && !OptionsCont::getOptions().getBool("meso-lane-queue")) {
474 4 : WRITE_WARNING(TL("LaneData requested for mesoscopic simulation but --meso-lane-queue is not active. Falling back to edgeData."));
475 : useLanes = false;
476 : }
477 : try {
478 69 : SUMOTime begin = string2time(OptionsCont::getOptions().getString("begin"));
479 138 : myDetectorBuilder.createEdgeLaneMeanData(id, -1, begin, -1, "traffic", useLanes, false, false,
480 69 : false, false, false, 100000, 0, SUMO_const_haltingSpeed, "", "", std::vector<MSEdge*>(), false,
481 138 : OptionsCont::getOptions().getString(optionName));
482 0 : } catch (InvalidArgument& e) {
483 0 : WRITE_ERROR(e.what());
484 0 : } catch (IOError& e) {
485 0 : WRITE_ERROR(e.what());
486 0 : }
487 : }
488 85064 : }
489 :
490 : /****************************************************************************/
|