1 /****************************************************************************/
2 // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.org/sumo
3 // Copyright (C) 2001-2019 German Aerospace Center (DLR) and others.
4 // This program and the accompanying materials
5 // are made available under the terms of the Eclipse Public License v2.0
6 // which accompanies this distribution, and is available at
7 // http://www.eclipse.org/legal/epl-v20.html
8 // SPDX-License-Identifier: EPL-2.0
9 /****************************************************************************/
10 /// @file    OptionsCont.cpp
11 /// @author  Daniel Krajzewicz
12 /// @author  Jakob Erdmann
13 /// @author  Michael Behrisch
14 /// @author  Walter Bamberger
15 /// @date    Mon, 17 Dec 2001
16 /// @version $Id$
17 ///
18 // A storage for options (typed value containers)
19 /****************************************************************************/
20 // ===========================================================================
21 // included modules
22 // ===========================================================================
23 #include <config.h>
24 
25 #include <map>
26 #include <string>
27 #include <exception>
28 #include <algorithm>
29 #include <vector>
30 #include <iostream>
31 #include <cstdlib>
32 #include <cassert>
33 #include <ctime>
34 #include <cstring>
35 #include <cerrno>
36 #include <iterator>
37 #include "Option.h"
38 #include "OptionsCont.h"
39 #include <utils/common/UtilExceptions.h>
40 #include <utils/common/FileHelpers.h>
41 #include <utils/common/MsgHandler.h>
42 #include <utils/common/StringTokenizer.h>
43 #include <utils/common/StringUtils.h>
44 #include <utils/xml/SUMOSAXAttributes.h>
45 #include <sstream>
46 
47 
48 // ===========================================================================
49 // static member definitions
50 // ===========================================================================
51 OptionsCont OptionsCont::myOptions;
52 
53 
54 // ===========================================================================
55 // method definitions
56 // ===========================================================================
57 OptionsCont&
getOptions()58 OptionsCont::getOptions() {
59     return myOptions;
60 }
61 
62 
OptionsCont()63 OptionsCont::OptionsCont()
64     : myAddresses(), myValues(), myDeprecatedSynonymes(), myHaveInformedAboutDeprecatedDivider(false) {
65     myCopyrightNotices.push_back("Copyright (C) 2001-2019 German Aerospace Center (DLR) and others; https://sumo.dlr.de");
66 }
67 
68 
~OptionsCont()69 OptionsCont::~OptionsCont() {
70     clear();
71 }
72 
73 
74 void
doRegister(const std::string & name,Option * v)75 OptionsCont::doRegister(const std::string& name, Option* v) {
76     assert(v != 0);
77     ItemAddressContType::iterator i = std::find(myAddresses.begin(), myAddresses.end(), v);
78     if (i == myAddresses.end()) {
79         myAddresses.push_back(v);
80     }
81     if (myValues.find(name) != myValues.end()) {
82         throw ProcessError(name + " is an already used option name.");
83     }
84     myValues[name] = v;
85 }
86 
87 
88 void
doRegister(const std::string & name1,char abbr,Option * v)89 OptionsCont::doRegister(const std::string& name1, char abbr, Option* v) {
90     doRegister(name1, v);
91     doRegister(convertChar(abbr), v);
92 }
93 
94 
95 void
addSynonyme(const std::string & name1,const std::string & name2,bool isDeprecated)96 OptionsCont::addSynonyme(const std::string& name1, const std::string& name2, bool isDeprecated) {
97     KnownContType::iterator i1 = myValues.find(name1);
98     KnownContType::iterator i2 = myValues.find(name2);
99     if (i1 == myValues.end() && i2 == myValues.end()) {
100         throw ProcessError("Neither the option '" + name1 + "' nor the option '" + name2 + "' is known yet");
101     }
102     if (i1 != myValues.end() && i2 != myValues.end()) {
103         if ((*i1).second == (*i2).second) {
104             return;
105         }
106         throw ProcessError("Both options '" + name1 + "' and '" + name2 + "' do exist and differ.");
107     }
108     if (i1 == myValues.end() && i2 != myValues.end()) {
109         doRegister(name1, (*i2).second);
110         if (isDeprecated) {
111             myDeprecatedSynonymes[name1] = false;
112         }
113     }
114     if (i1 != myValues.end() && i2 == myValues.end()) {
115         doRegister(name2, (*i1).second);
116         if (isDeprecated) {
117             myDeprecatedSynonymes[name2] = false;
118         }
119     }
120 }
121 
122 
123 void
addXMLDefault(const std::string & name,const std::string & xmlRoot)124 OptionsCont::addXMLDefault(const std::string& name, const std::string& xmlRoot) {
125     myXMLDefaults[xmlRoot] = name;
126 }
127 
128 
129 bool
exists(const std::string & name) const130 OptionsCont::exists(const std::string& name) const {
131     return myValues.count(name) > 0;
132 }
133 
134 
135 bool
isSet(const std::string & name,bool failOnNonExistant) const136 OptionsCont::isSet(const std::string& name, bool failOnNonExistant) const {
137     KnownContType::const_iterator i = myValues.find(name);
138     if (i == myValues.end()) {
139         if (failOnNonExistant) {
140             throw ProcessError("Internal request for unknown option '" + name + "'!");
141         } else {
142             return false;
143         }
144     }
145     return (*i).second->isSet();
146 }
147 
148 
149 void
unSet(const std::string & name,bool failOnNonExistant) const150 OptionsCont::unSet(const std::string& name, bool failOnNonExistant) const {
151     KnownContType::const_iterator i = myValues.find(name);
152     if (i == myValues.end()) {
153         if (failOnNonExistant) {
154             throw ProcessError("Internal request for unknown option '" + name + "'!");
155         } else {
156             return;
157         }
158     }
159     (*i).second->unSet();
160 }
161 
162 
163 bool
isDefault(const std::string & name) const164 OptionsCont::isDefault(const std::string& name) const {
165     KnownContType::const_iterator i = myValues.find(name);
166     if (i == myValues.end()) {
167         return false;
168     }
169     return (*i).second->isDefault();
170 }
171 
172 
173 Option*
getSecure(const std::string & name) const174 OptionsCont::getSecure(const std::string& name) const {
175     KnownContType::const_iterator k = myValues.find(name);
176     if (k == myValues.end()) {
177         throw ProcessError("No option with the name '" + name + "' exists.");
178     }
179     std::map<std::string, bool>::iterator s = myDeprecatedSynonymes.find(name);
180     if (s != myDeprecatedSynonymes.end() && !s->second) {
181         std::string defaultName;
182         for (std::map<std::string, std::vector<std::string> >::const_iterator i = mySubTopicEntries.begin(); i != mySubTopicEntries.end(); ++i) {
183             for (std::vector<std::string>::const_iterator j = i->second.begin(); j != i->second.end(); ++j) {
184                 KnownContType::const_iterator l = myValues.find(*j);
185                 if (l != myValues.end() && l->second == k->second) {
186                     defaultName = *j;
187                     break;
188                 }
189             }
190             if (defaultName != "") {
191                 break;
192             }
193         }
194         WRITE_WARNING("Please note that '" + name + "' is deprecated.\n Use '" + defaultName + "' instead.");
195         s->second = true;
196     }
197     return k->second;
198 }
199 
200 
201 std::string
getString(const std::string & name) const202 OptionsCont::getString(const std::string& name) const {
203     Option* o = getSecure(name);
204     return o->getString();
205 }
206 
207 
208 double
getFloat(const std::string & name) const209 OptionsCont::getFloat(const std::string& name) const {
210     Option* o = getSecure(name);
211     return o->getFloat();
212 }
213 
214 
215 int
getInt(const std::string & name) const216 OptionsCont::getInt(const std::string& name) const {
217     Option* o = getSecure(name);
218     return o->getInt();
219 }
220 
221 
222 bool
getBool(const std::string & name) const223 OptionsCont::getBool(const std::string& name) const {
224     Option* o = getSecure(name);
225     return o->getBool();
226 }
227 
228 
229 const IntVector&
getIntVector(const std::string & name) const230 OptionsCont::getIntVector(const std::string& name) const {
231     Option* o = getSecure(name);
232     return o->getIntVector();
233 }
234 
235 
236 const FloatVector&
getFloatVector(const std::string & name) const237 OptionsCont::getFloatVector(const std::string& name) const {
238     Option* o = getSecure(name);
239     return o->getFloatVector();
240 }
241 
242 
243 bool
set(const std::string & name,const std::string & value)244 OptionsCont::set(const std::string& name, const std::string& value) {
245     Option* o = getSecure(name);
246     if (!o->isWriteable()) {
247         reportDoubleSetting(name);
248         return false;
249     }
250     try {
251         if (!o->set(value)) {
252             return false;
253         }
254     } catch (ProcessError& e) {
255         WRITE_ERROR("While processing option '" + name + "':\n " + e.what());
256         return false;
257     }
258     return true;
259 }
260 
261 
262 bool
setDefault(const std::string & name,const std::string & value)263 OptionsCont::setDefault(const std::string& name, const std::string& value) {
264     if (set(name, value)) {
265         getSecure(name)->resetDefault();
266         return true;
267     }
268     return false;
269 }
270 
271 
272 bool
setByRootElement(const std::string & root,const std::string & value)273 OptionsCont::setByRootElement(const std::string& root, const std::string& value) {
274     if (myXMLDefaults.count(root) > 0) {
275         return set(myXMLDefaults[root], value);
276     }
277     if (myXMLDefaults.count("") > 0) {
278         return set(myXMLDefaults[""], value);
279     }
280     return false;
281 }
282 
283 
284 std::vector<std::string>
getSynonymes(const std::string & name) const285 OptionsCont::getSynonymes(const std::string& name) const {
286     Option* o = getSecure(name);
287     std::vector<std::string> v(0);
288     for (KnownContType::const_iterator i = myValues.begin(); i != myValues.end(); i++) {
289         if ((*i).second == o && name != (*i).first) {
290             v.push_back((*i).first);
291         }
292     }
293     return v;
294 }
295 
296 
297 const std::string&
getDescription(const std::string & name) const298 OptionsCont::getDescription(const std::string& name) const {
299     return getSecure(name)->getDescription();
300 }
301 
302 
303 std::ostream&
operator <<(std::ostream & os,const OptionsCont & oc)304 operator<<(std::ostream& os, const OptionsCont& oc) {
305     std::vector<std::string> done;
306     os << "Options set:" << std::endl;
307     for (OptionsCont::KnownContType::const_iterator i = oc.myValues.begin();
308             i != oc.myValues.end(); i++) {
309         std::vector<std::string>::iterator j = std::find(done.begin(), done.end(), (*i).first);
310         if (j == done.end()) {
311             std::vector<std::string> synonymes = oc.getSynonymes((*i).first);
312             if (synonymes.size() != 0) {
313                 os << (*i).first << " (";
314                 for (j = synonymes.begin(); j != synonymes.end(); j++) {
315                     if (j != synonymes.begin()) {
316                         os << ", ";
317                     }
318                     os << (*j);
319                 }
320                 os << ")";
321             } else {
322                 os << (*i).first;
323             }
324             if ((*i).second->isSet()) {
325                 os << ": " << (*i).second->getValueString() << std::endl;
326             } else {
327                 os << ": <INVALID>" << std::endl;
328             }
329             done.push_back((*i).first);
330             copy(synonymes.begin(), synonymes.end(), back_inserter(done));
331         }
332     }
333     return os;
334 }
335 
336 
337 void
relocateFiles(const std::string & configuration) const338 OptionsCont::relocateFiles(const std::string& configuration) const {
339     for (Option* const option : myAddresses) {
340         if (option->isFileName() && option->isSet()) {
341             std::vector<std::string> fileList = StringTokenizer(option->getString(), ",").getVector();
342             for (std::string& f : fileList) {
343                 // Pruning is necessary because filenames may be separated by ', ' in the configuration file
344                 f = StringUtils::urlDecode(FileHelpers::checkForRelativity(StringUtils::prune(f), configuration));
345             }
346             const std::string conv = joinToString(fileList, ',');
347             if (conv != option->getString()) {
348                 const bool hadDefault = option->isDefault();
349                 option->set(conv);
350                 if (hadDefault) {
351                     option->resetDefault();
352                 }
353             }
354         }
355     }
356 }
357 
358 
359 bool
isUsableFileList(const std::string & name) const360 OptionsCont::isUsableFileList(const std::string& name) const {
361     Option* o = getSecure(name);
362     // check whether the option is set
363     //  return false i not
364     if (!o->isSet()) {
365         return false;
366     }
367     // check whether the list of files is valid
368     bool ok = true;
369     std::vector<std::string> files = getStringVector(name);
370     if (files.size() == 0) {
371         WRITE_ERROR("The file list for '" + name + "' is empty.");
372         ok = false;
373     }
374     for (std::vector<std::string>::const_iterator fileIt = files.begin(); fileIt != files.end(); ++fileIt) {
375         if (!FileHelpers::isReadable(*fileIt)) {
376             if (*fileIt != "") {
377                 WRITE_ERROR("File '" + *fileIt + "' is not accessible (" + std::strerror(errno) + ").");
378                 ok = false;
379             } else {
380                 WRITE_WARNING("Empty file name given; ignoring.");
381             }
382         }
383     }
384     return ok;
385 }
386 
387 
388 bool
checkDependingSuboptions(const std::string & name,const std::string & prefix) const389 OptionsCont::checkDependingSuboptions(const std::string& name, const std::string& prefix) const {
390     Option* o = getSecure(name);
391     if (o->isSet()) {
392         return true;
393     }
394     bool ok = true;
395     std::vector<std::string> seenSynonymes;
396     for (KnownContType::const_iterator i = myValues.begin(); i != myValues.end(); i++) {
397         if (std::find(seenSynonymes.begin(), seenSynonymes.end(), (*i).first) != seenSynonymes.end()) {
398             continue;
399         }
400         if ((*i).second->isSet() && !(*i).second->isDefault() && (*i).first.find(prefix) == 0) {
401             WRITE_ERROR("Option '" + (*i).first + "' needs option '" + name + "'.");
402             std::vector<std::string> synonymes = getSynonymes((*i).first);
403             std::copy(synonymes.begin(), synonymes.end(), std::back_inserter(seenSynonymes));
404             ok = false;
405         }
406     }
407     return ok;
408 }
409 
410 
411 void
reportDoubleSetting(const std::string & arg) const412 OptionsCont::reportDoubleSetting(const std::string& arg) const {
413     std::vector<std::string> synonymes = getSynonymes(arg);
414     std::ostringstream s;
415     s << "A value for the option '" + arg + "' was already set.\n Possible synonymes: ";
416     for (std::vector<std::string>::iterator i = synonymes.begin(); i != synonymes.end();) {
417         s << (*i);
418         i++;
419         if (i != synonymes.end()) {
420             s << ", ";
421         }
422     }
423     WRITE_ERROR(s.str());
424 }
425 
426 
427 std::string
convertChar(char abbr) const428 OptionsCont::convertChar(char abbr) const {
429     char buf[2];
430     buf[0] = abbr;
431     buf[1] = 0;
432     std::string s(buf);
433     return s;
434 }
435 
436 
437 bool
isBool(const std::string & name) const438 OptionsCont::isBool(const std::string& name) const {
439     Option* o = getSecure(name);
440     return o->isBool();
441 }
442 
443 
444 void
resetWritable()445 OptionsCont::resetWritable() {
446     for (ItemAddressContType::iterator i = myAddresses.begin(); i != myAddresses.end(); i++) {
447         (*i)->resetWritable();
448     }
449 }
450 
451 
452 bool
isWriteable(const std::string & name)453 OptionsCont::isWriteable(const std::string& name) {
454     Option* o = getSecure(name);
455     return o->isWriteable();
456 }
457 
458 
459 void
clear()460 OptionsCont::clear() {
461     ItemAddressContType::iterator i;
462     for (i = myAddresses.begin(); i != myAddresses.end(); i++) {
463         delete (*i);
464     }
465     myAddresses.clear();
466     myValues.clear();
467     mySubTopics.clear();
468     mySubTopicEntries.clear();
469 }
470 
471 
472 void
addDescription(const std::string & name,const std::string & subtopic,const std::string & description)473 OptionsCont::addDescription(const std::string& name,
474                             const std::string& subtopic,
475                             const std::string& description) {
476     Option* o = getSecure(name);
477     assert(o != 0);
478     assert(find(mySubTopics.begin(), mySubTopics.end(), subtopic) != mySubTopics.end());
479     o->setDescription(description);
480     mySubTopicEntries[subtopic].push_back(name);
481 }
482 
483 
484 void
setApplicationName(const std::string & appName,const std::string & fullName)485 OptionsCont::setApplicationName(const std::string& appName,
486                                 const std::string& fullName) {
487     myAppName = appName;
488     myFullName = fullName;
489 }
490 
491 
492 void
setApplicationDescription(const std::string & appDesc)493 OptionsCont::setApplicationDescription(const std::string& appDesc) {
494     myAppDescription = appDesc;
495 }
496 
497 
498 void
addCallExample(const std::string & example,const std::string & desc)499 OptionsCont::addCallExample(const std::string& example, const std::string& desc) {
500     myCallExamples.push_back(std::make_pair(example, desc));
501 }
502 
503 
504 void
setAdditionalHelpMessage(const std::string & add)505 OptionsCont::setAdditionalHelpMessage(const std::string& add) {
506     myAdditionalMessage = add;
507 }
508 
509 
510 void
addCopyrightNotice(const std::string & copyrightLine)511 OptionsCont::addCopyrightNotice(const std::string& copyrightLine) {
512     myCopyrightNotices.push_back(copyrightLine);
513 }
514 
515 
516 void
clearCopyrightNotices()517 OptionsCont::clearCopyrightNotices() {
518     myCopyrightNotices.clear();
519 }
520 
521 
522 void
addOptionSubTopic(const std::string & topic)523 OptionsCont::addOptionSubTopic(const std::string& topic) {
524     mySubTopics.push_back(topic);
525     mySubTopicEntries[topic] = std::vector<std::string>();
526 }
527 
528 
529 void
splitLines(std::ostream & os,std::string what,int offset,int nextOffset)530 OptionsCont::splitLines(std::ostream& os, std::string what,
531                         int offset, int nextOffset) {
532     while (what.length() > 0) {
533         if ((int)what.length() > 79 - offset) {
534             std::string::size_type splitPos = what.rfind(';', 79 - offset);
535             if (splitPos == std::string::npos) {
536                 splitPos = what.rfind(' ', 79 - offset);
537             } else {
538                 splitPos++;
539             }
540             if (splitPos != std::string::npos) {
541                 os << what.substr(0, splitPos) << std::endl;
542                 what = what.substr(splitPos);
543                 for (int r = 0; r < nextOffset + 1; ++r) {
544                     os << ' ';
545                 }
546             } else {
547                 os << what;
548                 what = "";
549             }
550             offset = nextOffset;
551         } else {
552             os << what;
553             what = "";
554         }
555     }
556     os << std::endl;
557 }
558 
559 
560 bool
processMetaOptions(bool missingOptions)561 OptionsCont::processMetaOptions(bool missingOptions) {
562     if (missingOptions) {
563         // no options are given
564         std::cout << myFullName << std::endl;
565         std::cout << " Build features: " << HAVE_ENABLED << std::endl;
566         for (std::vector<std::string>::const_iterator it =
567                     myCopyrightNotices.begin(); it != myCopyrightNotices.end(); ++it) {
568             std::cout << " " << *it << std::endl;
569         }
570         std::cout << " License EPL-2.0: Eclipse Public License Version 2 <https://eclipse.org/legal/epl-v20.html>\n";
571         std::cout << " Use --help to get the list of options." << std::endl;
572         return true;
573     }
574 
575     myWriteLicense = getBool("write-license");
576     // check whether the help shall be printed
577     if (getBool("help")) {
578         std::cout << myFullName << std::endl;
579         for (std::vector<std::string>::const_iterator it =
580                     myCopyrightNotices.begin(); it != myCopyrightNotices.end(); ++it) {
581             std::cout << " " << *it << std::endl;
582         }
583         printHelp(std::cout);
584         return true;
585     }
586     // check whether the help shall be printed
587     if (getBool("version")) {
588         std::cout << myFullName << std::endl;
589         std::cout << " Build features: " << HAVE_ENABLED << std::endl;
590         for (std::vector<std::string>::const_iterator it =
591                     myCopyrightNotices.begin(); it != myCopyrightNotices.end(); ++it) {
592             std::cout << " " << *it << std::endl;
593         }
594         std::cout << "\n" << myFullName << " is part of SUMO.\n";
595         std::cout << "This program and the accompanying materials\n";
596         std::cout << "are made available under the terms of the Eclipse Public License v2.0\n";
597         std::cout << "which accompanies this distribution, and is available at\n";
598         std::cout << "http://www.eclipse.org/legal/epl-v20.html\n";
599         std::cout << "SPDX-License-Identifier: EPL-2.0" << std::endl;
600         return true;
601     }
602     // check whether the settings shall be printed
603     if (exists("print-options") && getBool("print-options")) {
604         std::cout << (*this);
605     }
606     // check whether something has to be done with options
607     // whether the current options shall be saved
608     if (isSet("save-configuration", false)) { // sumo-gui does not register these
609         if (getString("save-configuration") == "-" || getString("save-configuration") == "stdout") {
610             writeConfiguration(std::cout, true, false, getBool("save-commented"));
611             return true;
612         }
613         std::ofstream out(getString("save-configuration").c_str());
614         if (!out.good()) {
615             throw ProcessError("Could not save configuration to '" + getString("save-configuration") + "'");
616         } else {
617             writeConfiguration(out, true, false, getBool("save-commented"));
618             if (getBool("verbose")) {
619                 WRITE_MESSAGE("Written configuration to '" + getString("save-configuration") + "'");
620             }
621             return true;
622         }
623     }
624     // whether the template shall be saved
625     if (isSet("save-template", false)) { // sumo-gui does not register these
626         if (getString("save-template") == "-" || getString("save-template") == "stdout") {
627             writeConfiguration(std::cout, false, true, getBool("save-commented"));
628             return true;
629         }
630         std::ofstream out(getString("save-template").c_str());
631         if (!out.good()) {
632             throw ProcessError("Could not save template to '" + getString("save-template") + "'");
633         } else {
634             writeConfiguration(out, false, true, getBool("save-commented"));
635             if (getBool("verbose")) {
636                 WRITE_MESSAGE("Written template to '" + getString("save-template") + "'");
637             }
638             return true;
639         }
640     }
641     if (isSet("save-schema", false)) { // sumo-gui does not register these
642         if (getString("save-schema") == "-" || getString("save-schema") == "stdout") {
643             writeSchema(std::cout);
644             return true;
645         }
646         std::ofstream out(getString("save-schema").c_str());
647         if (!out.good()) {
648             throw ProcessError("Could not save schema to '" + getString("save-schema") + "'");
649         } else {
650             writeSchema(out);
651             if (getBool("verbose")) {
652                 WRITE_MESSAGE("Written schema to '" + getString("save-schema") + "'");
653             }
654             return true;
655         }
656     }
657     return false;
658 }
659 
660 void
printHelp(std::ostream & os)661 OptionsCont::printHelp(std::ostream& os) {
662     std::vector<std::string>::const_iterator i, j;
663     // print application description
664     splitLines(os, myAppDescription, 0, 0);
665     os << std::endl;
666 
667     // check option sizes first
668     //  we want to know how large the largest not-too-large-entry will be
669     int tooLarge = 40;
670     int maxSize = 0;
671     for (i = mySubTopics.begin(); i != mySubTopics.end(); ++i) {
672         const std::vector<std::string>& entries = mySubTopicEntries[*i];
673         for (j = entries.begin(); j != entries.end(); ++j) {
674             Option* o = getSecure(*j);
675             // name, two leading spaces and "--"
676             int csize = (int)j->length() + 2 + 4;
677             // abbreviation length ("-X, "->4chars) if any
678             const std::vector<std::string> synonymes = getSynonymes(*j);
679             for (std::vector<std::string>::const_iterator s = synonymes.begin(); s != synonymes.end(); ++s) {
680                 if (s->length() == 1 && myDeprecatedSynonymes.count(*s) == 0) {
681                     csize += 4;
682                     break;
683                 }
684             }
685             // the type name
686             if (!o->isBool()) {
687                 csize += 1 + (int)o->getTypeName().length();
688             }
689             // divider
690             csize += 2;
691             if (csize < tooLarge && maxSize < csize) {
692                 maxSize = csize;
693             }
694         }
695     }
696 
697     const std::string helpTopic = StringUtils::to_lower_case(getSecure("help")->getValueString());
698     if (helpTopic != "") {
699         bool foundTopic = false;
700         for (const std::string& topic : mySubTopics) {
701             if (StringUtils::to_lower_case(topic).find(helpTopic) != std::string::npos) {
702                 foundTopic = true;
703                 printHelpOnTopic(topic, tooLarge, maxSize, os);
704             }
705         }
706         if (!foundTopic) {
707             // print topic list
708             os << "Help Topics:"  << std::endl;
709             for (std::string t : mySubTopics) {
710                 os << "    " << t << std::endl;
711             }
712         }
713         return;
714     }
715     // print usage BNF
716     os << "Usage: " << myAppName << " [OPTION]*" << std::endl;
717     // print additional text if any
718     if (myAdditionalMessage.length() > 0) {
719         os << myAdditionalMessage << std::endl << ' ' << std::endl;
720     }
721     // print the options
722     for (i = mySubTopics.begin(); i != mySubTopics.end(); ++i) {
723         printHelpOnTopic(*i, tooLarge, maxSize, os);
724     }
725     os << std::endl;
726     // print usage examples, calc size first
727     if (myCallExamples.size() != 0) {
728         os << "Examples:" << std::endl;
729         for (std::vector<std::pair<std::string, std::string> >::const_iterator e = myCallExamples.begin(); e != myCallExamples.end(); ++e) {
730             os << "  " << myAppName << ' ' << e->first << std::endl;
731             os << "    " << e->second << std::endl;
732         }
733     }
734     os << std::endl;
735     os << "Report bugs at <https://github.com/eclipse/sumo/issues>." << std::endl;
736     os << "Get in contact via <sumo@dlr.de>." << std::endl;
737 }
738 
739 void
printHelpOnTopic(const std::string & topic,int tooLarge,int maxSize,std::ostream & os)740 OptionsCont::printHelpOnTopic(const std::string& topic, int tooLarge, int maxSize, std::ostream& os) {
741     os << topic << " Options:" << std::endl;
742     for (std::string entry : mySubTopicEntries[topic]) {
743         // start length computation
744         int csize = (int)entry.length() + 2;
745         Option* o = getSecure(entry);
746         os << "  ";
747         // write abbreviation if given
748         std::vector<std::string> synonymes = getSynonymes(entry);
749         for (std::vector<std::string>::const_iterator s = synonymes.begin(); s != synonymes.end(); ++s) {
750             if (s->length() == 1 && myDeprecatedSynonymes.count(*s) == 0) {
751                 os << '-' << *s << ", ";
752                 csize += 4;
753                 break;
754             }
755         }
756         // write leading '-'/"--"
757         os << "--";
758         csize += 2;
759         // write the name
760         os << entry;
761         // write the type if not a bool option
762         if (!o->isBool()) {
763             os << ' ' << o->getTypeName();
764             csize += 1 + (int)o->getTypeName().length();
765         }
766         csize += 2;
767         // write the description formatting it
768         os << "  ";
769         for (int r = maxSize; r > csize; --r) {
770             os << ' ';
771         }
772         int offset = csize > tooLarge ? csize : maxSize;
773         splitLines(os, o->getDescription(), offset, maxSize);
774     }
775     os << std::endl;
776 }
777 
778 void
writeConfiguration(std::ostream & os,const bool filled,const bool complete,const bool addComments,const bool inComment) const779 OptionsCont::writeConfiguration(std::ostream& os, const bool filled,
780                                 const bool complete, const bool addComments,
781                                 const bool inComment) const {
782     if (!inComment) {
783         writeXMLHeader(os, false);
784     }
785     os << "<configuration xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"http://sumo.dlr.de/xsd/";
786     if (myAppName == "sumo-gui") {
787         os << "sumo";
788     } else if (myAppName == "netedit") {
789         os << "netconvert";
790     } else {
791         os << myAppName;
792     }
793     os << "Configuration.xsd\">" << std::endl << std::endl;
794     for (std::vector<std::string>::const_iterator i = mySubTopics.begin(); i != mySubTopics.end(); ++i) {
795         std::string subtopic = *i;
796         if (subtopic == "Configuration" && !complete) {
797             continue;
798         }
799         std::replace(subtopic.begin(), subtopic.end(), ' ', '_');
800         std::transform(subtopic.begin(), subtopic.end(), subtopic.begin(), tolower);
801         const std::vector<std::string>& entries = mySubTopicEntries.find(*i)->second;
802         bool hadOne = false;
803         for (std::vector<std::string>::const_iterator j = entries.begin(); j != entries.end(); ++j) {
804             Option* o = getSecure(*j);
805             bool write = complete || (filled && !o->isDefault());
806             if (!write) {
807                 continue;
808             }
809             if (!hadOne) {
810                 os << "    <" << subtopic << ">" << std::endl;
811             }
812             // add the comment if wished
813             if (addComments) {
814                 os << "        <!-- " << StringUtils::escapeXML(o->getDescription(), inComment) << " -->" << std::endl;
815             }
816             // write the option and the value (if given)
817             os << "        <" << *j << " value=\"";
818             if (o->isSet() && (filled || o->isDefault())) {
819                 os << StringUtils::escapeXML(o->getValueString(), inComment);
820             }
821             if (complete) {
822                 std::vector<std::string> synonymes = getSynonymes(*j);
823                 if (!synonymes.empty()) {
824                     os << "\" synonymes=\"";
825                     for (std::vector<std::string>::const_iterator s = synonymes.begin(); s != synonymes.end(); ++s) {
826                         if (s != synonymes.begin()) {
827                             os << " ";
828                         }
829                         os << (*s);
830                     }
831                 }
832                 os << "\" type=\"" << o->getTypeName();
833                 if (!addComments) {
834                     os << "\" help=\"" << StringUtils::escapeXML(o->getDescription());
835                 }
836             }
837             os << "\"/>" << std::endl;
838             // append an endline if a comment was printed
839             if (addComments) {
840                 os << std::endl;
841             }
842             hadOne = true;
843         }
844         if (hadOne) {
845             os << "    </" << subtopic << ">" << std::endl << std::endl;
846         }
847     }
848     os << "</configuration>" << std::endl;
849 }
850 
851 
852 void
writeSchema(std::ostream & os)853 OptionsCont::writeSchema(std::ostream& os) {
854     writeXMLHeader(os, false);
855     os << "<xsd:schema elementFormDefault=\"qualified\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">\n\n";
856     os << "    <xsd:include schemaLocation=\"baseTypes.xsd\"/>\n";
857     os << "    <xsd:element name=\"configuration\" type=\"configurationType\"/>\n\n";
858     os << "    <xsd:complexType name=\"configurationType\">\n";
859     os << "        <xsd:all>\n";
860     for (std::vector<std::string>::const_iterator i = mySubTopics.begin(); i != mySubTopics.end(); ++i) {
861         std::string subtopic = *i;
862         if (subtopic == "Configuration") {
863             continue;
864         }
865         std::replace(subtopic.begin(), subtopic.end(), ' ', '_');
866         std::transform(subtopic.begin(), subtopic.end(), subtopic.begin(), tolower);
867         os << "            <xsd:element name=\"" << subtopic << "\" type=\"" << subtopic << "TopicType\" minOccurs=\"0\"/>\n";
868     }
869     os << "        </xsd:all>\n";
870     os << "    </xsd:complexType>\n\n";
871     for (std::vector<std::string>::const_iterator i = mySubTopics.begin(); i != mySubTopics.end(); ++i) {
872         std::string subtopic = *i;
873         if (subtopic == "Configuration") {
874             continue;
875         }
876         std::replace(subtopic.begin(), subtopic.end(), ' ', '_');
877         std::transform(subtopic.begin(), subtopic.end(), subtopic.begin(), tolower);
878         os << "    <xsd:complexType name=\"" << subtopic << "TopicType\">\n";
879         os << "        <xsd:all>\n";
880         const std::vector<std::string>& entries = mySubTopicEntries[*i];
881         for (std::vector<std::string>::const_iterator j = entries.begin(); j != entries.end(); ++j) {
882             Option* o = getSecure(*j);
883             std::string type = o->getTypeName();
884             std::transform(type.begin(), type.end(), type.begin(), tolower);
885             if (type == "int[]") {
886                 type = "intArray";
887             }
888             os << "            <xsd:element name=\"" << *j << "\" type=\"" << type << "OptionType\" minOccurs=\"0\"/>\n";
889         }
890         os << "        </xsd:all>\n";
891         os << "    </xsd:complexType>\n\n";
892     }
893     os << "</xsd:schema>\n";
894 }
895 
896 
897 void
writeXMLHeader(std::ostream & os,const bool includeConfig) const898 OptionsCont::writeXMLHeader(std::ostream& os, const bool includeConfig) const {
899     time_t rawtime;
900     char buffer [80];
901 
902     os << "<?xml version=\"1.0\"" << SUMOSAXAttributes::ENCODING << "?>\n\n";
903     time(&rawtime);
904     strftime(buffer, 80, "<!-- generated on %c by ", localtime(&rawtime));
905     os << buffer << myFullName << "\n";
906     if (myWriteLicense) {
907         os << "This data file and the accompanying materials\n";
908         os << "are made available under the terms of the Eclipse Public License v2.0\n";
909         os << "which accompanies this distribution, and is available at\n";
910         os << "http://www.eclipse.org/legal/epl-v20.html\n";
911         os << "SPDX-License-Identifier: EPL-2.0\n";
912     }
913     if (includeConfig) {
914         writeConfiguration(os, true, false, false, true);
915     }
916     os << "-->\n\n";
917 }
918 
919 
920 std::vector<std::string>
getStringVector(const std::string & name) const921 OptionsCont::getStringVector(const std::string& name) const {
922     Option* o = getSecure(name);
923     std::string def = o->getString();
924     if (def.find(';') != std::string::npos && !myHaveInformedAboutDeprecatedDivider) {
925         WRITE_WARNING("Please note that using ';' as list separator is deprecated.\n From 1.0 onwards, only ',' will be accepted.");
926         myHaveInformedAboutDeprecatedDivider = true;
927     }
928     StringTokenizer st(def, ";,", true);
929     std::vector<std::string> ret = st.getVector();
930     for (std::vector<std::string>::iterator i = ret.begin(); i != ret.end(); ++i) {
931         (*i) = StringUtils::prune(*i);
932     }
933     return ret;
934 }
935 
936 
937 bool
isInStringVector(const std::string & optionName,const std::string & itemName)938 OptionsCont::isInStringVector(const std::string& optionName,
939                               const std::string& itemName) {
940     if (isSet(optionName)) {
941         std::vector<std::string> values = getStringVector(optionName);
942         return std::find(values.begin(), values.end(), itemName) != values.end();
943     }
944     return false;
945 }
946 
947 
948 /****************************************************************************/
949