diff --git a/cimgen/languages/cpp/lang_pack.py b/cimgen/languages/cpp/lang_pack.py index dfdcc58d..983c41b2 100644 --- a/cimgen/languages/cpp/lang_pack.py +++ b/cimgen/languages/cpp/lang_pack.py @@ -58,6 +58,9 @@ def get_class_location(class_name, class_map, version): # NOSONAR "label": "{{#langPack.label}}{{label}}{{/langPack.label}}", "insert_assign": "{{#langPack.insert_assign_fn}}{{.}}{{/langPack.insert_assign_fn}}", "insert_class_assign": "{{#langPack.insert_class_assign_fn}}{{.}}{{/langPack.insert_class_assign_fn}}", + "insert_get": "{{#langPack.insert_get_fn}}{{.}}{{/langPack.insert_get_fn}}", + "insert_class_get": "{{#langPack.insert_class_get_fn}}{{.}}{{/langPack.insert_class_get_fn}}", + "insert_enum_get": "{{#langPack.insert_enum_get_fn}}{{.}}{{/langPack.insert_enum_get_fn}}", } @@ -161,6 +164,68 @@ def insert_class_assign_fn(text, render): ) +def insert_get_fn(text, render): + attribute_txt = render(text) + attribute_json = eval(attribute_txt) + if not attribute_json["is_primitive_attribute"]: + return "" + label = attribute_json["label"] + class_name = attribute_json["domain"] + return ( + ' get_map.emplace("cim:' + + class_name + + "." + + label + + '", &get_' + + class_name + + "_" + + label + + ");\n" + ) + + +def insert_class_get_fn(text, render): + attribute_txt = render(text) + attribute_json = eval(attribute_txt) + if attribute_json["is_primitive_attribute"] or attribute_json["is_enum_attribute"]: + return "" + if not attribute_json["is_used"]: + return "" + label = attribute_json["label"] + class_name = attribute_json["domain"] + return ( + ' get_map.emplace("cim:' + + class_name + + "." + + label + + '", &get_' + + class_name + + "_" + + label + + ");\n" + ) + + +def insert_enum_get_fn(text, render): + attribute_txt = render(text) + attribute_json = eval(attribute_txt) + if not attribute_json["is_enum_attribute"]: + return "" + label = attribute_json["label"] + class_name = attribute_json["domain"] + return ( + ' get_map.emplace("cim:' + + class_name + + "." + + label + + '", &get_' + + class_name + + "_" + + label + + ");\n" + ) + + def create_nullptr_assigns(text, render): attributes_txt = render(text) if attributes_txt.strip() == "": @@ -343,6 +408,112 @@ def create_assign(text, render): return assign +def create_class_get(text, render): + attribute_txt = render(text) + attribute_json = eval(attribute_txt) + if attribute_json["is_primitive_attribute"] or attribute_json["is_enum_attribute"]: + return "" + if not attribute_json["is_used"]: + return "" + if attribute_json["is_list_attribute"]: + get = """ +bool get_OBJECT_CLASS_LABEL(const BaseClass* BaseClass_ptr1, std::list& BaseClass_list) +{ + if (const OBJECT_CLASS* element = dynamic_cast(BaseClass_ptr1)) + { + std::copy(element->LABEL.begin(), element->LABEL.end(), std::back_inserter(BaseClass_list)); + return !BaseClass_list.empty(); + } + return false; +}""".replace( # noqa: E101,W191 + "OBJECT_CLASS", attribute_json["domain"] + ).replace( + "LABEL", attribute_json["label"] + ) + else: + get = """ +bool get_OBJECT_CLASS_LABEL(const BaseClass* BaseClass_ptr1, std::list& BaseClass_list) +{ + if (const OBJECT_CLASS* element = dynamic_cast(BaseClass_ptr1)) + { + if (element->LABEL != 0) + { + BaseClass_list.push_back(element->LABEL); + return true; + } + } + return false; +}""".replace( # noqa: E101,W191 + "OBJECT_CLASS", attribute_json["domain"] + ).replace( + "LABEL", attribute_json["label"] + ) + + return get + + +def create_get(text, render): + attribute_txt = render(text) + attribute_json = eval(attribute_txt) + get = "" + if not attribute_json["is_primitive_attribute"]: + return "" + label_without_keyword = attribute_json["label"] + if label_without_keyword == "switch": + label_without_keyword = "_switch" + + get = ( + """ +bool get_CLASS_LABEL(const BaseClass* BaseClass_ptr1, std::stringstream& buffer) +{ + if (const CLASS* element = dynamic_cast(BaseClass_ptr1)) + { + buffer << element->LBL_WO_KEYWORD; + if (!buffer.str().empty()) + { + return true; + } + } + buffer.setstate(std::ios::failbit); + return false; +}""".replace( # noqa: E101,W191 + "CLASS", attribute_json["domain"] + ) + .replace("LABEL", attribute_json["label"]) + .replace("LBL_WO_KEYWORD", label_without_keyword) + ) + + return get + + +def create_enum_get(text, render): + attribute_txt = render(text) + attribute_json = eval(attribute_txt) + if not attribute_json["is_enum_attribute"]: + return "" + + get = """ +bool get_CLASS_LABEL(const BaseClass* BaseClass_ptr1, std::stringstream& buffer) +{ + if (const CLASS* element = dynamic_cast(BaseClass_ptr1)) + { + buffer << element->LABEL; + if (!buffer.str().empty()) + { + return true; + } + } + buffer.setstate(std::ios::failbit); + return false; +}""".replace( # noqa: E101,W191 + "CLASS", attribute_json["domain"] + ).replace( + "LABEL", attribute_json["label"] + ) + + return get + + def attribute_decl(text, render): attribute_txt = render(text) attribute_json = eval(attribute_txt) diff --git a/cimgen/languages/cpp/src/CIMWriter.cpp b/cimgen/languages/cpp/src/CIMWriter.cpp new file mode 100644 index 00000000..12551333 --- /dev/null +++ b/cimgen/languages/cpp/src/CIMWriter.cpp @@ -0,0 +1,233 @@ +#include "CIMWriter.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "CGMESProfile.hpp" +#include "BaseClass.hpp" +#include "gettercache.hpp" +#include "profilecache.hpp" + +std::map CIMWriter::writeFiles(const std::string& outputfile, + const std::vector& objList, + const std::string& modelID, + const std::map& classProfileMap) +{ + std::map profileToFileMap; + + for (auto profile : getProfileList()) + { + std::string profileName = getProfileLongName(profile); + std::stringstream rdf; + if (writeCim(rdf, objList, profile, modelID + "_" + profileName, classProfileMap)) + { + std::string file = outputfile + "_" + profileName + ".xml"; + std::ofstream rdfFile(file); + rdfFile << rdf.str(); + profileToFileMap.emplace(profile, file); + } + } + + return profileToFileMap; +} + +bool CIMWriter::writeCim(std::ostream& rdf, const std::vector& objList, + const CGMESProfile& profile, const std::string& modelID, + const std::map& classProfileMap) +{ + int objectsCount = 0; + static char rdfURL[] = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; + static auto cimURL = getCimNamespace(); + static char mdURL[] = "http://iec.ch/TC57/61970-552/ModelDescription/1#"; + + rdf << "" << std::endl; + rdf << "" << std::endl; + + rdf << " " << std::endl; + for (const auto& uri : getProfileURIs(profile)) + { + rdf << " " << uri << "" << std::endl; + } + rdf << " " << std::endl; + + for (const BaseClass* obj : objList) + { + std::string type = obj->debugString(); + std::string id = obj->getRdfid(); + + if (!id.empty() && type != "UnknownType" && isClassMatchingProfile(obj, profile)) + { + std::stringstream rdfObj; + int attributesCount = 0; + + CGMESProfile classProfile = UnknownProfile; + auto it = classProfileMap.find(type); + if (it != classProfileMap.end()) + { + classProfile = it->second; + } + + bool mainEntryOfObject = (classProfile == profile); + if (mainEntryOfObject) + { + rdfObj << " " << std::endl; + } + else + { + rdfObj << " " << std::endl; + } + + for (const auto& attrAndFunc : get_primitive_getter_map(obj)) + { + std::string attr = attrAndFunc.first; + get_function func = attrAndFunc.second; + + if (attr != "cim:IdentifiedObject.mRID" && + getAttributeProfile(obj, attr, classProfile) == profile) + { + std::stringstream stream; + if (func(obj, stream)) + { + rdfObj << " <" << attr << ">" << stream.str() << "" << std::endl; + ++attributesCount; + } + } + } + + for (const auto& attrAndFunc : get_class_getter_map(obj)) + { + std::string attr = attrAndFunc.first; + class_get_function func = attrAndFunc.second; + + if (getAttributeProfile(obj, attr, classProfile) == profile) + { + std::list targetList; + if (func(obj, targetList)) + { + for (const BaseClass* targetObj : targetList) + { + rdfObj << " <" << attr << " rdf:resource='#" << targetObj->getRdfid() << "' />" << std::endl; + ++attributesCount; + } + } + } + } + + for (const auto& attrAndFunc : get_enum_getter_map(obj)) + { + std::string attr = attrAndFunc.first; + get_function func = attrAndFunc.second; + + if (getAttributeProfile(obj, attr, classProfile) == profile) + { + std::stringstream stream; + if (func(obj, stream)) + { + rdfObj << " <" << attr << " rdf:resource='" << cimURL << stream.str() << "' />" << std::endl; + ++attributesCount; + } + } + } + + rdfObj << " " << std::endl; + + if (mainEntryOfObject || attributesCount != 0) + { + rdf << rdfObj.str(); + ++objectsCount; + } + } + } + + rdf << "" << std::endl; + return objectsCount != 0; +} + +bool CIMWriter::isClassMatchingProfile(const BaseClass* obj, const CGMESProfile& profile) +{ + const auto& profiles = get_possible_profiles_for_class(obj); + return std::find(profiles.begin(), profiles.end(), profile) != profiles.end(); +} + +CGMESProfile CIMWriter::getClassProfile(const BaseClass* obj) +{ + const auto& classProfiles = get_possible_profiles_for_class(obj); + if (classProfiles.size() == 1) + { + return classProfiles.front(); + } + + std::map profileCountMap; + for (const auto& attrAndProfiles : get_possible_profiles_for_attributes(obj)) + { + const auto& profiles = attrAndProfiles.second; + bool ambiguousProfile = profiles.size() > 1; + for (CGMESProfile profile : profiles) + { + if (ambiguousProfile && + std::find(classProfiles.begin(), classProfiles.end(), profile) != classProfiles.end()) + { + ++profileCountMap[profile]; + } + } + } + + std::multimap countProfileMap; + for (const auto& profileAndCount : profileCountMap) + { + countProfileMap.emplace(-profileAndCount.second, profileAndCount.first); + } + + for (const auto& countAndProfile : countProfileMap) + { + return countAndProfile.second; + } + + return UnknownProfile; +} + +std::map CIMWriter::getClassProfileMap(const std::vector& objList) +{ + std::map profileMap; + + for (const BaseClass* obj : objList) + { + std::string type = obj->debugString(); + if (profileMap.find(type) == profileMap.end()) + { + CGMESProfile profile = getClassProfile(obj); + profileMap.emplace(type, profile); + } + } + + return profileMap; +} + +CGMESProfile CIMWriter::getAttributeProfile(const BaseClass* obj, const std::string& attr, + const CGMESProfile& classProfile) +{ + const auto& profilesMap = get_possible_profiles_for_attributes(obj); + auto it = profilesMap.find(attr); + if (it != profilesMap.end()) + { + auto profiles = it->second; + if (std::find(profiles.begin(), profiles.end(), classProfile) != profiles.end()) + { + return classProfile; + } + else if (!profiles.empty()) + { + profiles.sort(); + return profiles.front(); + } + } + return UnknownProfile; +} diff --git a/cimgen/languages/cpp/src/CIMWriter.hpp b/cimgen/languages/cpp/src/CIMWriter.hpp new file mode 100644 index 00000000..dbbce2f1 --- /dev/null +++ b/cimgen/languages/cpp/src/CIMWriter.hpp @@ -0,0 +1,109 @@ +#ifndef CIMWRITER_HPP +#define CIMWRITER_HPP + +#include +#include +#include +#include + +#include "CGMESProfile.hpp" +#include "BaseClass.hpp" + +/** + * \brief Class for writing CIM RDF/XML files. + */ +class CIMWriter +{ +public: + /** + * \brief Write CIM RDF/XML files. + * + * This function writes CIM objects into one or more RDF/XML files separated by profiles. + * + * Each CIM object will be written to its corresponding profile file depending on classProfileMap. + * But some objects to more than one file if some attribute profiles are not the same as the class profile. + * + * \param outputfile Stem of the output file, resulting files: _.xml + * \param objList List of CIM objects to write + * \param modelID Stem of the model IDs, resulting IDs: _ + * \param classProfileMap Mapping of CIM type to profile + * \return Written files: Mapping of profile to file + */ + static std::map writeFiles(const std::string& outputfile, + const std::vector& objList, + const std::string& modelID, + const std::map& classProfileMap); + + /** + * \brief Write CIM objects as RDF/XML data to a stream. + * + * This function writes RDF/XML data corresponding to one profile into a stream (e.g. a file stream). + * + * \param rdf Output stream + * \param objList List of CIM objects to write + * \param profile Only data for this profile should be written + * \param modelID Model ID to write into the model description + * \param classProfileMap Mapping of CIM type to profile + * \return Success: at least one object ist written to the stream + */ + static bool writeCim(std::ostream& rdf, const std::vector& objList, + const CGMESProfile& profile, const std::string& modelID, + const std::map& classProfileMap); + + /** + * \brief Check if this profile is a possible profile for this CIM object. + * + * This function checks if the CIM type of an object contains data for a profile. + * The profile could be the main profile of the type, or the type contains attributes for this profile. + * + * \param obj CIM object to get the CIM type from + * \param profile Profile to check + * \return True/False + */ + static bool isClassMatchingProfile(const BaseClass* obj, const CGMESProfile& profile); + + /** + * \brief Get the main profile of this CIM object. + * + * This function searches for the main profile of the CIM type of an object. + * If the type contains attributes for different profiles not all data of the object could be written into one file. + * To write the data to as few as possible files the main profile should be that with most of the attributes. + * But some types contain a lot of rarely used special attributes, i.e. attributes for a special profile + * (e.g. TopologyNode has many attributes for TopologyBoundary, but the main profile should be Topology). + * That's why attributes that only belong to one profile are skipped in the search algorithm. + * + * \param obj CIM object to get the CIM type from + * \return Main profile + */ + static CGMESProfile getClassProfile(const BaseClass* obj); + + /** + * \brief Get the main profiles for a list of CIM objects. + * + * This function searches for the main profile of each CIM type in the object list (see \ref getClassProfile for details). + * + * The result could be used as parameter for the write functions: \ref writeFiles and \ref writeCim. + * But it is also possible to optimize the mapping manually for some CIM types before calling the write function. + * + * \param objList List of CIM objects + * \return Mapping of CIM type to profile + */ + static std::map getClassProfileMap(const std::vector& objList); + + /** + * \brief Get the profile for this attribute of the CIM object. + * + * This function searches for the profile of an attribute for the CIM type of an object. + * If the main profile of the type is a possible profile of the attribute it should be choosen. + * Otherwise, the first profile in the list of possible profiles ordered by profile number. + * + * \param obj CIM object to get the CIM type from + * \param attr Attribute to check + * \param classProfile Main profile of the CIM type + * \return Attribute profile + */ + static CGMESProfile getAttributeProfile(const BaseClass* obj, const std::string& attr, + const CGMESProfile& classProfile); +}; + +#endif // CIMWRITER_HPP diff --git a/cimgen/languages/cpp/static/BaseClass.cpp b/cimgen/languages/cpp/static/BaseClass.cpp index 5baf1c2c..cc93fcd5 100644 --- a/cimgen/languages/cpp/static/BaseClass.cpp +++ b/cimgen/languages/cpp/static/BaseClass.cpp @@ -16,6 +16,9 @@ const char* BaseClass::debugString() const void BaseClass::addConstructToMap(std::unordered_map& factory_map) {} void BaseClass::addPrimitiveAssignFnsToMap(std::unordered_map& assign_map) {} void BaseClass::addClassAssignFnsToMap(std::unordered_map& assign_map) {} +void BaseClass::addPrimitiveGetFnsToMap(std::map& get_map) const {} +void BaseClass::addClassGetFnsToMap(std::map& get_map) const {} +void BaseClass::addEnumGetFnsToMap(std::map& get_map) const {} const BaseClassDefiner BaseClass::declare() { diff --git a/cimgen/languages/cpp/static/BaseClass.hpp b/cimgen/languages/cpp/static/BaseClass.hpp index a5b797f3..f761249c 100644 --- a/cimgen/languages/cpp/static/BaseClass.hpp +++ b/cimgen/languages/cpp/static/BaseClass.hpp @@ -13,6 +13,10 @@ #include "BaseClassDefiner.hpp" #include "CGMESProfile.hpp" +class BaseClass; +typedef bool (*class_get_function)(const BaseClass*, std::list&); +typedef bool (*get_function)(const BaseClass*, std::stringstream&); + class BaseClass { std::string rdfid; @@ -31,6 +35,9 @@ class BaseClass static void addConstructToMap(std::unordered_map& factory_map); static void addPrimitiveAssignFnsToMap(std::unordered_map& assign_map); static void addClassAssignFnsToMap(std::unordered_map& assign_map); + virtual void addPrimitiveGetFnsToMap(std::map& get_map) const; + virtual void addClassGetFnsToMap(std::map& get_map) const; + virtual void addEnumGetFnsToMap(std::map& get_map) const; static const CIMPP::BaseClassDefiner declare(); }; #endif // BASECLASS_HPP diff --git a/cimgen/languages/cpp/static/gettercache.cpp b/cimgen/languages/cpp/static/gettercache.cpp new file mode 100644 index 00000000..90036bec --- /dev/null +++ b/cimgen/languages/cpp/static/gettercache.cpp @@ -0,0 +1,49 @@ +#include "gettercache.hpp" + +#include +#include +#include + +#include "BaseClass.hpp" + +using namespace CIMPP; + +static std::unordered_map> primitive_getter_cache; +static std::unordered_map> class_getter_cache; +static std::unordered_map> enum_getter_cache; + +const std::map& get_primitive_getter_map(const BaseClass* obj) +{ + std::string type = obj->debugString(); + + if (primitive_getter_cache.find(type) == primitive_getter_cache.end()) + { + obj->addPrimitiveGetFnsToMap(primitive_getter_cache[type]); + } + + return primitive_getter_cache.at(type); +} + +const std::map& get_class_getter_map(const BaseClass* obj) +{ + std::string type = obj->debugString(); + + if (class_getter_cache.find(type) == class_getter_cache.end()) + { + obj->addClassGetFnsToMap(class_getter_cache[type]); + } + + return class_getter_cache.at(type); +} + +const std::map& get_enum_getter_map(const BaseClass* obj) +{ + std::string type = obj->debugString(); + + if (enum_getter_cache.find(type) == enum_getter_cache.end()) + { + obj->addEnumGetFnsToMap(enum_getter_cache[type]); + } + + return enum_getter_cache.at(type); +} diff --git a/cimgen/languages/cpp/static/gettercache.hpp b/cimgen/languages/cpp/static/gettercache.hpp new file mode 100644 index 00000000..0c41551c --- /dev/null +++ b/cimgen/languages/cpp/static/gettercache.hpp @@ -0,0 +1,13 @@ +#ifndef GETTERCACHE_HPP +#define GETTERCACHE_HPP + +#include +#include + +#include "BaseClass.hpp" + +const std::map& get_primitive_getter_map(const BaseClass* obj); +const std::map& get_class_getter_map(const BaseClass* obj); +const std::map& get_enum_getter_map(const BaseClass* obj); + +#endif // GETTERCACHE_HPP diff --git a/cimgen/languages/cpp/templates/cpp_header_template.mustache b/cimgen/languages/cpp/templates/cpp_header_template.mustache index 58a15482..b163f9dc 100644 --- a/cimgen/languages/cpp/templates/cpp_header_template.mustache +++ b/cimgen/languages/cpp/templates/cpp_header_template.mustache @@ -41,6 +41,9 @@ namespace CIMPP static void addConstructToMap(std::unordered_map& factory_map); static void addPrimitiveAssignFnsToMap(std::unordered_map& assign_map); static void addClassAssignFnsToMap(std::unordered_map& assign_map); + void addPrimitiveGetFnsToMap(std::map& get_map) const override; + void addClassGetFnsToMap(std::map& get_map) const override; + void addEnumGetFnsToMap(std::map& get_map) const override; static const BaseClassDefiner declare(); }; diff --git a/cimgen/languages/cpp/templates/cpp_object_template.mustache b/cimgen/languages/cpp/templates/cpp_object_template.mustache index 845dd25e..d09c2ce4 100644 --- a/cimgen/languages/cpp/templates/cpp_object_template.mustache +++ b/cimgen/languages/cpp/templates/cpp_object_template.mustache @@ -4,6 +4,8 @@ Generated from the CGMES files via cimgen: https://github.com/sogno-platform/cim #include "{{class_name}}.hpp" #include +#include +#include #include {{#attributes}} @@ -52,6 +54,18 @@ std::map> {{#langPack.create_class_assign}}{{.}}{{/langPack.create_class_assign}} {{/attributes}} +{{#attributes}} +{{#langPack.create_get}}{{.}}{{/langPack.create_get}} +{{/attributes}} + +{{#attributes}} +{{#langPack.create_class_get}}{{.}}{{/langPack.create_class_get}} +{{/attributes}} + +{{#attributes}} +{{#langPack.create_enum_get}}{{.}}{{/langPack.create_enum_get}} +{{/attributes}} + const char {{class_name}}::debugName[] = "{{class_name}}"; const char* {{class_name}}::debugString() const { @@ -77,6 +91,30 @@ void {{class_name}}::addClassAssignFnsToMap(std::unordered_map& get_map) const +{ + {{sub_class_of}}::addPrimitiveGetFnsToMap(get_map); +{{#attributes}} +{{> insert_get}} +{{/attributes}} +} + +void {{class_name}}::addClassGetFnsToMap(std::map& get_map) const +{ + {{sub_class_of}}::addClassGetFnsToMap(get_map); +{{#attributes}} +{{> insert_class_get}} +{{/attributes}} +} + +void {{class_name}}::addEnumGetFnsToMap(std::map& get_map) const +{ + {{sub_class_of}}::addEnumGetFnsToMap(get_map); +{{#attributes}} +{{> insert_enum_get}} +{{/attributes}} +} + const BaseClassDefiner {{class_name}}::declare() { return BaseClassDefiner({{class_name}}::addConstructToMap, {{class_name}}::addPrimitiveAssignFnsToMap, {{class_name}}::addClassAssignFnsToMap, {{class_name}}::debugName);