//========================================================================== // ENVIRUTILS.CC - part of // OMNeT++/OMNEST // Discrete System Simulation in C++ // // Author: Andras Varga // //========================================================================== /*--------------------------------------------------------------* Copyright (C) 1992-2017 Andras Varga Copyright (C) 2006-2017 OpenSim Ltd. This file is distributed WITHOUT ANY WARRANTY. See the file `license' for details on this and other legal matters. *--------------------------------------------------------------*/ #include "common/stringutil.h" #include "common/unitconversion.h" #include "common/opp_ctype.h" #include "nedxml/nedparser.h" // NedParser::getBuiltInDeclarations() #include "omnetpp/ccanvas.h" #include "omnetpp/cconfigoption.h" #include "omnetpp/cconfiguration.h" #include "omnetpp/cnedmathfunction.h" #include "omnetpp/cnedfunction.h" #include "omnetpp/cresultrecorder.h" #include "omnetpp/checkandcast.h" #include "omnetpp/cchannel.h" #include "sim/resultfilters.h" // ExpressionFilter #include "sim/resultrecorders.h" // ExpressionRecorder #include "args.h" #include "appreg.h" #include "envirutils.h" using namespace omnetpp::common; using namespace omnetpp::nedxml; namespace omnetpp { namespace envir { using std::ostream; std::string EnvirUtils::getConfigOptionType(cConfigOption *option) { if (option->isGlobal()) return "Global setting (applies to all simulation runs)"; else if (!option->isPerObject()) return "Per-simulation-run setting"; else { const char *typeComment = ""; switch (option->getObjectKind()) { case cConfigOption::KIND_COMPONENT: typeComment = "modules and channels"; break; case cConfigOption::KIND_CHANNEL: typeComment = "channels"; break; case cConfigOption::KIND_MODULE: typeComment = "modules"; break; case cConfigOption::KIND_SIMPLE_MODULE: typeComment = "simple modules"; break; case cConfigOption::KIND_UNSPECIFIED_TYPE: typeComment = "modules and channels"; break; // note: this is currently only used for **.typename case cConfigOption::KIND_PARAMETER: typeComment = "module/channel parameters"; break; case cConfigOption::KIND_STATISTIC: typeComment = "statistics (@statistic)"; break; case cConfigOption::KIND_SCALAR: typeComment = "scalar results"; break; case cConfigOption::KIND_VECTOR: typeComment = "vector results"; break; case cConfigOption::KIND_OTHER: typeComment = "other objects"; break; case cConfigOption::KIND_NONE: ASSERT(false); break; } return std::string("Per-object setting for ") + typeComment; } } void EnvirUtils::dumpComponentList(std::ostream& out, const char *category, bool verbose) { cConfigurationEx *config = getEnvir()->getConfigEx(); bool wantAll = !strcmp(category, "all"); bool processed = false; out << "\n"; if (wantAll || !strcmp(category, "config") || !strcmp(category, "configdetails")) { processed = true; if (verbose) out << "Supported configuration options:\n"; bool printDescriptions = strcmp(category, "configdetails") == 0; cRegistrationList *table = configOptions.getInstance(); table->sort(); for (int i = 0; i < table->size(); i++) { cConfigOption *obj = dynamic_cast(table->get(i)); ASSERT(obj); if (!printDescriptions) out << " "; if (obj->isPerObject()) out << "**."; out << obj->getName() << "="; out << "<" << cConfigOption::getTypeName(obj->getType()) << ">"; if (obj->getUnit()) out << ", unit=\"" << obj->getUnit() << "\""; if (obj->getDefaultValue()) out << ", default:" << obj->getDefaultValue() << ""; if (!printDescriptions) out << "; " << (obj->isGlobal() ? "global" : obj->isPerObject() ? "per-object" : "per-run") << " setting"; // TODO getOptionKindName() out << "\n"; if (printDescriptions) out << " " << getConfigOptionType(obj) << ".\n"; if (printDescriptions && !opp_isempty(obj->getDescription())) out << opp_indentlines(opp_breaklines(obj->getDescription(), 75), " ") << "\n"; if (printDescriptions) out << "\n"; } if (!wantAll && verbose) out << "Use '-h configvars' to print the list of dollar variables that can be used in configuration values.\n"; out << "\n"; } if (wantAll || !strcmp(category, "configvars")) { processed = true; if (verbose) out << "Predefined variables that can be used in config values:\n"; std::vector varNames = config->getPredefinedVariableNames(); for (const char *varName : varNames) { out << "${" << varName << "}\n"; const char *desc = config->getVariableDescription(varName); out << opp_indentlines(opp_breaklines(desc, 75), " ") << "\n"; } out << "\n"; } if (!strcmp(category, "latexconfig")) { // internal undocumented option, for maintenance purposes // generate LaTeX code for the appendix in the User Manual processed = true; cRegistrationList *table = configOptions.getInstance(); table->sort(); out << "\\begin{description}\n"; for (int i = 0; i < table->size(); i++) { cConfigOption *obj = dynamic_cast(table->get(i)); ASSERT(obj); out << "\\item[" << (obj->isPerObject() ? "**." : "") << opp_latexQuote(obj->getName()) << "] = "; out << "\\textit{<" << cConfigOption::getTypeName(obj->getType()) << ">}"; if (obj->getUnit()) out << ", unit=\\ttt{" << obj->getUnit() << "}"; if (obj->getDefaultValue()) out << ", default: \\ttt{" << opp_latexInsertBreaks(opp_latexQuote(obj->getDefaultValue())) << "}"; out << "\\\\\n"; out << " \\textit{" << getConfigOptionType(obj) << ".}\\\\\n"; out << opp_indentlines(opp_breaklines(opp_markup2Latex(opp_latexQuote(obj->getDescription())), 75), " ") << "\n"; } out << "\\end{description}\n\n"; out << "Predefined variables that can be used in config values:\n\n"; std::vector varNames = config->getPredefinedVariableNames(); out << "\\begin{description}\n"; for (auto & varName : varNames) { out << "\\item[\\$\\{" << varName << "\\}] : \\\\\n"; const char *desc = config->getVariableDescription(varName); out << opp_indentlines(opp_breaklines(opp_markup2Latex(opp_latexQuote(desc)), 75), " ") << "\n"; } out << "\\end{description}\n\n"; } if (!strcmp(category, "jconfig")) { // internal undocumented option, for maintenance purposes // generate Java code for ConfigurationRegistry.java in the IDE processed = true; if (verbose) out << "Supported configuration options (as Java code):\n"; cRegistrationList *table = configOptions.getInstance(); table->sort(); for (int i = 0; i < table->size(); i++) { cConfigOption *key = dynamic_cast(table->get(i)); ASSERT(key); std::string id = "CFGID_"; for (const char *s = key->getName(); *s; s++) id.append(1, opp_isalpha(*s) ? opp_toupper(*s) : *s == '-' ? '_' : *s == '%' ? 'n' : *s); const char *method = key->isGlobal() ? "addGlobalOption" : !key->isPerObject() ? "addPerRunOption" : "addPerObjectOption"; #define CASE(X) case cConfigOption::X: typestring = #X; break; const char *typestring; switch (key->getType()) { CASE(CFG_BOOL) CASE(CFG_INT) CASE(CFG_DOUBLE) CASE(CFG_STRING) CASE(CFG_FILENAME) CASE(CFG_FILENAMES) CASE(CFG_PATH) CASE(CFG_CUSTOM) } #undef CASE #define CASE(X) case cConfigOption::X: kindstring = #X; break; const char *kindstring; switch (key->getObjectKind()) { CASE(KIND_COMPONENT) CASE(KIND_CHANNEL) CASE(KIND_MODULE) CASE(KIND_SIMPLE_MODULE) CASE(KIND_UNSPECIFIED_TYPE) CASE(KIND_PARAMETER) CASE(KIND_STATISTIC) CASE(KIND_SCALAR) CASE(KIND_VECTOR) CASE(KIND_NONE) CASE(KIND_OTHER) } #undef CASE out << " public static final ConfigOption " << id << " = "; out << method << (key->getUnit() ? "U" : "") << "(\n"; out << " \"" << key->getName() << "\", "; if (key->isPerObject()) out << kindstring << ", "; if (!key->getUnit()) out << typestring << ", "; else out << "\"" << key->getUnit() << "\", "; if (!key->getDefaultValue()) out << "null"; else out << "\"" << opp_replacesubstring(key->getDefaultValue(), "\"", "\\\"", true) << "\""; out << ",\n"; std::string desc = key->getDescription(); desc = opp_replacesubstring(desc, "\n", "\\n\n", true); // keep explicit line breaks desc = opp_breaklines(desc, 75); // break long lines desc = opp_replacesubstring(desc, "\"", "\\\"", true); desc = opp_replacesubstring(desc, "\n", " \" +\n\"", true); desc = opp_replacesubstring(desc, "\\n \"", "\\n\"", true); // remove bogus space after explicit line breaks desc = "\"" + desc + "\""; out << opp_indentlines(desc, " ") << ");\n"; } out << "\n"; std::vector varNames = config->getPredefinedVariableNames(); for (auto & varName : varNames) { opp_string id = varName; opp_strupr(id.buffer()); const char *desc = config->getVariableDescription(varName); out << " public static final String CFGVAR_" << id << " = addConfigVariable("; out << "\"" << varName << "\", \"" << opp_replacesubstring(desc, "\"", "\\\"", true) << "\");\n"; } out << "\n"; } if (wantAll || !strcmp(category, "classes")) { processed = true; if (verbose) out << "Registered C++ classes, including modules, channels and messages:\n"; cRegistrationList *table = classes.getInstance(); table->sort(); for (int i = 0; i < table->size(); i++) { cObject *obj = table->get(i); out << " class " << obj->getFullName() << "\n"; } if (verbose) { out << "Note: if your class is not listed, it needs to be registered in the\n"; out << "C++ code using Define_Module(), Define_Channel() or Register_Class().\n"; } out << "\n"; } if (wantAll || !strcmp(category, "classdesc")) { processed = true; if (verbose) out << "Classes that have associated reflection information (needed for Tkenv inspectors):\n"; cRegistrationList *table = classDescriptors.getInstance(); table->sort(); for (int i = 0; i < table->size(); i++) { cObject *obj = table->get(i); out << " class " << obj->getFullName() << "\n"; } out << "\n"; } if (wantAll || !strcmp(category, "nedfunctions")) { processed = true; if (verbose) out << "Functions that can be used in NED expressions and in omnetpp.ini:\n"; cRegistrationList *table = nedFunctions.getInstance(); table->sort(); std::set categories; for (int i = 0; i < table->size(); i++) { cNedFunction *nf = dynamic_cast(table->get(i)); cNedMathFunction *mf = dynamic_cast(table->get(i)); categories.insert(nf ? nf->getCategory() : mf ? mf->getCategory() : "???"); } for (auto category : categories) { out << "\n Category \"" << category << "\":\n"; for (int i = 0; i < table->size(); i++) { cObject *obj = table->get(i); cNedFunction *nf = dynamic_cast(table->get(i)); cNedMathFunction *mf = dynamic_cast(table->get(i)); const char *fcat = nf ? nf->getCategory() : mf ? mf->getCategory() : "???"; const char *desc = nf ? nf->getDescription() : mf ? mf->getDescription() : "???"; if (fcat == category) { out << " " << obj->getFullName() << " : " << obj->str() << "\n"; if (desc) out << " " << desc << "\n"; } } } out << "\n"; } if (wantAll || !strcmp(category, "neddecls")) { processed = true; if (verbose) { out << "Built-in NED declarations:\n\n"; out << "---START---\n"; } out << NedParser::getBuiltInDeclarations(); if (verbose) out << "---END---\n"; out << "\n"; } if (wantAll || !strcmp(category, "units")) { processed = true; if (verbose) { out << "Recognized measurement units (note: other units can be used as well, only\n"; out << "no automatic conversion will be available for them):\n"; } std::vector units = UnitConversion::getAllUnits(); for (auto u : units) { const char *bu = UnitConversion::getBaseUnit(u); out << " " << u << "\t" << UnitConversion::getLongName(u); if (opp_strcmp(u, bu) != 0) out << "\t" << UnitConversion::getConversionDescription(u); out << "\n"; } out << "\n"; } if (wantAll || !strcmp(category, "enums")) { processed = true; if (verbose) out << "Enums defined in .msg files\n"; cRegistrationList *table = enums.getInstance(); table->sort(); for (int i = 0; i < table->size(); i++) { cObject *obj = table->get(i); out << " " << obj->getFullName() << " : " << obj->str() << "\n"; } out << "\n"; } if (wantAll || !strcmp(category, "userinterfaces")) { processed = true; if (verbose) out << "User interfaces loaded:\n"; cRegistrationList *table = omnetapps.getInstance(); table->sort(); for (int i = 0; i < table->size(); i++) { cObject *obj = table->get(i); out << " " << obj->getFullName() << " : " << obj->str() << "\n"; } out << "\n"; } if (wantAll || !strcmp(category, "resultfilters")) { processed = true; if (verbose) out << "Result filters that can be used in @statistic properties:\n"; cRegistrationList *table = resultFilters.getInstance(); table->sort(); for (int i = 0; i < table->size(); i++) { cObject *obj = table->get(i); out << " " << obj->getFullName() << " : " << obj->str() << "\n"; } out << "\n"; } if (wantAll || !strcmp(category, "resultrecorders")) { processed = true; if (verbose) out << "Result recorders that can be used in @statistic properties:\n"; cRegistrationList *table = resultRecorders.getInstance(); table->sort(); for (int i = 0; i < table->size(); i++) { cObject *obj = table->get(i); out << " " << obj->getFullName() << " : " << obj->str() << "\n"; } out << "\n"; } if (wantAll || !strcmp(category, "figures")) { processed = true; if (verbose) out << "Figure types and their accepted @figure property keys:\n"; for (auto it : figureTypes) { std::string type = it.first; std::string className = it.second; cFigure *figure = check_and_cast(createOne(className.c_str())); out << " " << type << " (" << className << "): " << opp_join(figure->getAllowedPropertyKeys(), ", ") << "\n"; delete figure; } out << "\n"; } if (!processed) throw cRuntimeError("Unrecognized category for '-h' option: %s", category); } void EnvirUtils::dumpResultRecorders(std::ostream& out, cComponent *component) { dumpComponentResultRecorders(out, component); if (component->isModule()) { cModule *module = (cModule *)component; for (cModule::SubmoduleIterator it(module); !it.end(); ++it) dumpResultRecorders(out, *it); for (cModule::ChannelIterator it(module); !it.end(); ++it) dumpResultRecorders(out, *it); } } void EnvirUtils::dumpComponentResultRecorders(std::ostream& out, cComponent *component) { bool componentPathPrinted = false; std::vector signals = component->getLocalListenedSignals(); for (int signalID : signals) { bool signalNamePrinted = false; std::vector listeners = component->getLocalSignalListeners(signalID); for (auto & listener : listeners) { if (dynamic_cast(listener)) { if (!componentPathPrinted) { out << component->getFullPath() << " (" << component->getNedTypeName() << "):\n"; componentPathPrinted = true; } if (!signalNamePrinted) { out << " \"" << cComponent::getSignalName(signalID) << "\" (signalID=" << signalID << "):\n"; signalNamePrinted = true; } dumpResultRecorderChain(out, (cResultListener *)listener, 0); } } } } void EnvirUtils::dumpResultRecorderChain(std::ostream& out, cResultListener *listener, int depth) { std::string indent(4*depth+8, ' '); out << indent; if (ExpressionRecorder *expressionRecorder = dynamic_cast(listener)) out << expressionRecorder->getExpression().str() << " (" << listener->getClassName() << ")"; else if (ExpressionFilter *expressionFilter = dynamic_cast(listener)) out << expressionFilter->getExpression().str() << " (" << listener->getClassName() << ")"; else out << listener->getClassName(); if (cResultRecorder *resultRecorder = dynamic_cast(listener)) out << " ==> " << resultRecorder->getResultName(); out << "\n"; if (cResultFilter *resultFilter = dynamic_cast(listener)) { std::vector delegates = resultFilter->getDelegates(); for (auto & delegate : delegates) dumpResultRecorderChain(out, delegate, depth+1); } } } // namespace envir } // namespace omnetpp