From c2cf9eeecf189285a419f9337bd15f19b6adc61f Mon Sep 17 00:00:00 2001 From: cogu Date: Mon, 18 Mar 2024 19:39:04 +0100 Subject: [PATCH] Improvements to component examples - Fix an xml-related issue with missing tag INTERNAL-BEHAVIORS - Add support for package-to-document mapping action - Examples now create default internal behavior and SWC implementation - Examples use document root option to select default xml output folder --- .../xml/component/application_component.py | 22 ++- .../xml/component/composition_component.py | 25 +-- ...ition.arxml => CompositionComponent.arxml} | 72 -------- .../component/data/ReceiverComponent.arxml | 72 ++++++++ ..._component.arxml => SenderComponent.arxml} | 9 + .../xml/component/data/TimerComponent.arxml | 38 ++++ run_examples.cmd | 4 +- src/autosar/xml/document.py | 45 +---- src/autosar/xml/element.py | 136 +++++++++++++- src/autosar/xml/enumeration.py | 133 +++++++------- src/autosar/xml/reader.py | 7 +- src/autosar/xml/workspace.py | 166 ++++++++++-------- src/autosar/xml/writer.py | 2 + tests/xml/test_software_component_elements.py | 8 +- 14 files changed, 453 insertions(+), 286 deletions(-) rename examples/xml/component/data/{composition.arxml => CompositionComponent.arxml} (56%) create mode 100644 examples/xml/component/data/ReceiverComponent.arxml rename examples/xml/component/data/{sender_component.arxml => SenderComponent.arxml} (82%) create mode 100644 examples/xml/component/data/TimerComponent.arxml diff --git a/examples/xml/component/application_component.py b/examples/xml/component/application_component.py index bb498d3..06f29ab 100644 --- a/examples/xml/component/application_component.py +++ b/examples/xml/component/application_component.py @@ -103,25 +103,23 @@ def create_application_component(workspace: autosar.xml.Workspace): swc.create_provide_port("EngineSpeed", engine_speed_interface, com_spec={"init_value": engine_speed_init.ref(), "uses_end_to_end_protection": False, }) + swc.create_internal_behavior() workspace.add_element("ComponentTypes", swc) + impl = ar_element.SwcImplementation("SenderComponent_Implementation", behavior_ref=swc.internal_behavior.ref()) + workspace.add_element("ComponentTypes", impl) def save_xml_files(workspace: autosar.xml.Workspace): """ Saves workspace as XML documents """ - interface_document_path = os.path.abspath(os.path.join(os.path.dirname( - __file__), 'data', 'portinterfaces.arxml')) - platform_document_path = os.path.abspath(os.path.join(os.path.dirname( - __file__), 'data', 'platform.arxml')) - component_document_path = os.path.abspath(os.path.join(os.path.dirname( - __file__), 'data', 'sender_component.arxml')) - constant_document_path = os.path.abspath(os.path.join(os.path.dirname( - __file__), 'data', 'constants.arxml')) - workspace.create_document(interface_document_path, packages="/PortInterfaces") - workspace.create_document(constant_document_path, packages="/Constants") - workspace.create_document(platform_document_path, packages="/AUTOSAR_Platform") - workspace.create_document(component_document_path, packages="/ComponentTypes") + workspace.set_document_root(os.path.abspath(os.path.join(os.path.dirname(__file__), "data"))) + workspace.create_document("portinterfaces.arxml", packages="/PortInterfaces") + workspace.create_document("constants.arxml", packages="/Constants") + workspace.create_document("platform.arxml", packages="/AUTOSAR_Platform") + workspace.create_document_mapping(package_ref="/ComponentTypes", + element_types=ar_element.SwComponentType, + suffix_filters=["_Implementation"]) workspace.write_documents() diff --git a/examples/xml/component/composition_component.py b/examples/xml/component/composition_component.py index ad781ac..6d34cd5 100644 --- a/examples/xml/component/composition_component.py +++ b/examples/xml/component/composition_component.py @@ -111,7 +111,10 @@ def create_receiver_component(workspace: autosar.xml.Workspace): "handle_never_received": False }) swc.create_require_port("FreeRunningTimer", timer_interface, com_spec={"GetTime": {}, "IsTimerElapsed": {}}) + swc.create_internal_behavior() workspace.add_element("ComponentTypes", swc) + impl = ar_element.SwcImplementation("ReceiverComponent_Implementation", behavior_ref=swc.internal_behavior.ref()) + workspace.add_element("ComponentTypes", impl) def create_server_component(workspace: autosar.xml.Workspace): @@ -123,7 +126,10 @@ def create_server_component(workspace: autosar.xml.Workspace): swc.create_provide_port("FreeRunningTimer", timer_interface, com_spec={"GetTime": {"queue_length": 1}, "IsTimerElapsed": {"queue_length": 1} }) + swc.create_internal_behavior() workspace.add_element("ComponentTypes", swc) + impl = ar_element.SwcImplementation("TimerComponent_Implementation", behavior_ref=swc.internal_behavior.ref()) + workspace.add_element("ComponentTypes", impl) def create_composition_component(workspace: autosar.xml.Workspace): @@ -153,18 +159,13 @@ def save_xml_files(workspace: autosar.xml.Workspace): """ Saves workspace as XML documents """ - interface_document_path = os.path.abspath(os.path.join(os.path.dirname( - __file__), 'data', 'portinterfaces.arxml')) - platform_document_path = os.path.abspath(os.path.join(os.path.dirname( - __file__), 'data', 'platform.arxml')) - component_document_path = os.path.abspath(os.path.join(os.path.dirname( - __file__), 'data', 'composition.arxml')) - constant_document_path = os.path.abspath(os.path.join(os.path.dirname( - __file__), 'data', 'constants.arxml')) - workspace.create_document(interface_document_path, packages="/PortInterfaces") - workspace.create_document(constant_document_path, packages="/Constants") - workspace.create_document(platform_document_path, packages="/AUTOSAR_Platform") - workspace.create_document(component_document_path, packages="/ComponentTypes") + workspace.set_document_root(os.path.abspath(os.path.join(os.path.dirname(__file__), "data"))) + workspace.create_document("portinterfaces.arxml", packages="/PortInterfaces") + workspace.create_document("constants.arxml", packages="/Constants") + workspace.create_document("platform.arxml", packages="/AUTOSAR_Platform") + workspace.create_document_mapping(package_ref="/ComponentTypes", + element_types=ar_element.SwComponentType, + suffix_filters=["_Implementation"]) workspace.write_documents() diff --git a/examples/xml/component/data/composition.arxml b/examples/xml/component/data/CompositionComponent.arxml similarity index 56% rename from examples/xml/component/data/composition.arxml rename to examples/xml/component/data/CompositionComponent.arxml index 2167833..9c461ed 100644 --- a/examples/xml/component/data/composition.arxml +++ b/examples/xml/component/data/CompositionComponent.arxml @@ -4,78 +4,6 @@ ComponentTypes - - ReceiverComponent - - - VehicleSpeed - - - /PortInterfaces/VehicleSpeed_I/VehicleSpeed - false - 0 - false - false - - - /Constants/VehicleSpeed_IV - - - - - /PortInterfaces/VehicleSpeed_I - - - EngineSpeed - - - /PortInterfaces/EngineSpeed_I/EngineSpeed - false - 0 - false - false - - - /Constants/EngineSpeed_IV - - - - - /PortInterfaces/EngineSpeed_I - - - FreeRunningTimer - - - /PortInterfaces/FreeRunningTimer_I/GetTime - - - /PortInterfaces/FreeRunningTimer_I/IsTimerElapsed - - - /PortInterfaces/FreeRunningTimer_I - - - - - TimerComponent - - - FreeRunningTimer - - - /PortInterfaces/FreeRunningTimer_I/GetTime - 1 - - - /PortInterfaces/FreeRunningTimer_I/IsTimerElapsed - 1 - - - /PortInterfaces/FreeRunningTimer_I - - - CompositionComponent diff --git a/examples/xml/component/data/ReceiverComponent.arxml b/examples/xml/component/data/ReceiverComponent.arxml new file mode 100644 index 0000000..3c0a559 --- /dev/null +++ b/examples/xml/component/data/ReceiverComponent.arxml @@ -0,0 +1,72 @@ + + + + + ComponentTypes + + + ReceiverComponent + + + VehicleSpeed + + + /PortInterfaces/VehicleSpeed_I/VehicleSpeed + false + 0 + false + false + + + /Constants/VehicleSpeed_IV + + + + + /PortInterfaces/VehicleSpeed_I + + + EngineSpeed + + + /PortInterfaces/EngineSpeed_I/EngineSpeed + false + 0 + false + false + + + /Constants/EngineSpeed_IV + + + + + /PortInterfaces/EngineSpeed_I + + + FreeRunningTimer + + + /PortInterfaces/FreeRunningTimer_I/GetTime + + + /PortInterfaces/FreeRunningTimer_I/IsTimerElapsed + + + /PortInterfaces/FreeRunningTimer_I + + + + + ReceiverComponent_InternalBehavior + + + + + ReceiverComponent_Implementation + /ComponentTypes/ReceiverComponent/ReceiverComponent_InternalBehavior + + + + + \ No newline at end of file diff --git a/examples/xml/component/data/sender_component.arxml b/examples/xml/component/data/SenderComponent.arxml similarity index 82% rename from examples/xml/component/data/sender_component.arxml rename to examples/xml/component/data/SenderComponent.arxml index 32f5ee2..4dc5947 100644 --- a/examples/xml/component/data/sender_component.arxml +++ b/examples/xml/component/data/SenderComponent.arxml @@ -38,7 +38,16 @@ /PortInterfaces/EngineSpeed_I + + + SenderComponent_InternalBehavior + + + + SenderComponent_Implementation + /ComponentTypes/SenderComponent/SenderComponent_InternalBehavior + diff --git a/examples/xml/component/data/TimerComponent.arxml b/examples/xml/component/data/TimerComponent.arxml new file mode 100644 index 0000000..e6599fb --- /dev/null +++ b/examples/xml/component/data/TimerComponent.arxml @@ -0,0 +1,38 @@ + + + + + ComponentTypes + + + TimerComponent + + + FreeRunningTimer + + + /PortInterfaces/FreeRunningTimer_I/GetTime + 1 + + + /PortInterfaces/FreeRunningTimer_I/IsTimerElapsed + 1 + + + /PortInterfaces/FreeRunningTimer_I + + + + + TimerComponent_InternalBehavior + + + + + TimerComponent_Implementation + /ComponentTypes/TimerComponent/TimerComponent_InternalBehavior + + + + + \ No newline at end of file diff --git a/run_examples.cmd b/run_examples.cmd index 26ecf16..81e9116 100644 --- a/run_examples.cmd +++ b/run_examples.cmd @@ -15,8 +15,8 @@ python examples\xml\port_interface\client_server_interface.py python examples\xml\port_interface\mode_switch_interface.py python examples\xml\component\application_component.py python examples\xml\component\composition_component.py -python examples\xml\template\generate_xml_using_config.py -python examples\xml\template\generate_xml_without_config.py +python examples\template\generate_xml_using_config.py +python examples\template\generate_xml_without_config.py python examples\generator\data_types\gen_type_defs_scalar.py python examples\generator\data_types\gen_type_defs_array.py python examples\generator\data_types\gen_type_defs_record.py diff --git a/src/autosar/xml/document.py b/src/autosar/xml/document.py index 2187e98..42a4f79 100644 --- a/src/autosar/xml/document.py +++ b/src/autosar/xml/document.py @@ -3,7 +3,7 @@ AUTOSAR Document """ # pylint: disable=R0801 -from typing import Any +from autosar.base import DEFAULT_SCHEMA_VERSION import autosar.xml.element as ar_element @@ -23,49 +23,14 @@ def schema_file(self) -> str: return f'AUTOSAR_{self.schema_version:05d}.xsd' -class Document(DocumentMeta): +class Document(ar_element.PackageCollection, DocumentMeta): """ Implements AUTOSAR root description element """ - def __init__(self, packages: list[ar_element.Package] | None = None, schema_version=51) -> None: - super().__init__(schema_version) + def __init__(self, packages: list[ar_element.Package] | None = None, schema_version=DEFAULT_SCHEMA_VERSION) -> None: + ar_element.PackageCollection.__init__(self, packages) + DocumentMeta.__init__(self, schema_version) self.file_info_comment = None # .FILE-INFO-COMMENT self.admin_data = None # .ADMIN-DATA self.introduction = None # .INTRODUCTION - self.packages: list[ar_element.Package] = [] # .PACKAGES - self._package_map = {} # internal package map - if packages is not None: - for package in packages: - self.append(package) - - def append(self, package: ar_element.Package): - """ - Appends package to this document and - appropriately updates reference links - """ - if isinstance(package, ar_element.Package): - if package.name in self._package_map: - raise ValueError( - f"Package with SHORT-NAME '{package.name}' already exists") - package.parent = self - self.packages.append(package) - self._package_map[package.name] = package - - def find(self, ref: str) -> Any: - """ - Finds item by reference - """ - if ref.startswith('/'): - ref = ref[1:] - parts = ref.partition('/') - package = self._package_map.get(parts[0], None) - if (package is not None) and (len(parts[2]) > 0): - return package.find(parts[2]) - return package - - def update_ref_parts(self, ref_parts: list[str]): - """ - Utility method used generating XML references - """ - ref_parts.append('') diff --git a/src/autosar/xml/element.py b/src/autosar/xml/element.py index 6d6559b..1caf2ba 100644 --- a/src/autosar/xml/element.py +++ b/src/autosar/xml/element.py @@ -559,6 +559,19 @@ def __str__(self) -> str: return self.value +class PackageRef(BaseRef): + """ + References to AR-PACKAGE--SUBTYPES-ENUM + """ + + def __init__(self, value: str) -> None: + super().__init__(value, ar_enum.IdentifiableSubTypes.AR_PACKAGE) + + def _accepted_subtypes(self) -> set[ar_enum.IdentifiableSubTypes]: + """Acceptable values for dest""" + return {ar_enum.IdentifiableSubTypes.AR_PACKAGE} + + class CompuMethodRef(BaseRef): """ CompuMethod reference @@ -3362,7 +3375,7 @@ def __init__(self, class Package(CollectableElement): """ - AR:PACKAGE + AR:AR-PACKAGE """ def __init__(self, name: str, **kwargs: dict) -> None: @@ -3460,6 +3473,88 @@ def filter_regex(self, pattern: str | re.Pattern) -> Iterator[ARElement]: if regex.match(elem.name): yield elem + def ref(self) -> PackageRef: + """ + Returns a reference to this package or + None if the package is not a sub-package or a root package + in a document/workspace + """ + ref_str = self._calc_ref_string() + return None if ref_str is None else PackageRef(ref_str) + + +class PackageCollection: + """ + Base class that maintains a collection of AUTOSAR packages + """ + + def __init__(self, packages: list[Package] | None = None) -> None: + self.packages: list[Package] = [] # .PACKAGES + self._package_dict = {} # internal package map + if packages is not None: + for package in packages: + self.append(package) + + def append(self, package: Package): + """ + Appends package to this document and + appropriately updates reference links + """ + if isinstance(package, Package): + if package.name in self._package_dict: + raise ValueError( + f"Package with SHORT-NAME '{package.name}' already exists") + package.parent = self + self.packages.append(package) + self._package_dict[package.name] = package + + def find(self, ref: str) -> Any: + """ + Finds item by reference + """ + if ref.startswith('/'): + ref = ref[1:] + parts = ref.partition('/') + package = self._package_dict.get(parts[0], None) + if (package is not None) and (len(parts[2]) > 0): + return package.find(parts[2]) + return package + + def update_ref_parts(self, ref_parts: list[str]): + """ + Utility method used generating XML references + """ + ref_parts.append('') + + def create_package(self, name: str, **kwargs) -> Package: + """ + Creates new package in collection + """ + if name in self._package_dict: + return ValueError(f"Package with name '{name}' already exists") + package = Package(name, **kwargs) + self.append(package) + return package + + def make_packages(self, *refs: list[str]) -> Package | list[Package]: + """ + Recursively creates packages from reference(s) + Returns a list of created packages. + If only one argument is given it will return that package (not a list). + """ + result = [] + for ref in refs: + if ref.startswith('/'): + ref = ref[1:] + parts = ref.partition('/') + package = self._package_dict.get(parts[0], None) + if package is None: + package = self.create_package(parts[0]) + if len(parts[2]) > 0: + package = package.make_packages(parts[2]) + result.append(package) + return result[0] if len(result) == 1 else result + # --- ModeDeclaration elements @@ -5263,11 +5358,38 @@ def __init__(self, symbol_props: SymbolProps | None = None, **kwargs) -> None: super().__init__(name, **kwargs) - self.internal_behavior: SwcInternalBehavior | None = None + self._internal_behavior: SwcInternalBehavior | None = None self.symbol_props = None # AR:SYMBOL-PROPS - self._assign_optional_strict("internal_behavior", internal_behavior, SwcInternalBehavior) + self._assign_optional_strict("_internal_behavior", internal_behavior, SwcInternalBehavior) self._assign_optional_strict("symbol_props", symbol_props, SymbolProps) + @property + def internal_behavior(self) -> Union["SwcInternalBehavior", None]: + """ + Internal behavior getter + """ + return self._internal_behavior + + @internal_behavior.setter + def internal_behavior(self, value: Union["SwcInternalBehavior", None]): + """ + Internal behavior setter + """ + self._internal_behavior = value + if value is not None: + value.parent = self + + def create_internal_behavior(self, name: str = None, **kwargs) -> "SwcInternalBehavior": + """ + Creates an empty internal behavior object and adds it to the component. + If the name argument is left as None, a default name will be created based on the name of + the software component + """ + if name is None: + name = self.name + "_InternalBehavior" + self.internal_behavior = SwcInternalBehavior(name, **kwargs) + return self.internal_behavior + class ApplicationSoftwareComponentType(AtomicSoftwareComponentType): """ @@ -5793,6 +5915,14 @@ def __init__(self, **kwargs) -> None: super().__init__(name, **kwargs) + def ref(self) -> SwcInternalBehaviorRef | None: + """ + Returns a reference to this element or + None if the element is not yet part of a package + """ + ref_str = self._calc_ref_string() + return None if ref_str is None else SwcInternalBehaviorRef(ref_str) + class SwcImplementation(Implementation): """ diff --git a/src/autosar/xml/enumeration.py b/src/autosar/xml/enumeration.py index 190eb2a..a8aacb2 100644 --- a/src/autosar/xml/enumeration.py +++ b/src/autosar/xml/enumeration.py @@ -206,39 +206,40 @@ class IdentifiableSubTypes(Enum): APPLICATION_RECORD_DATA_TYPE = 12 APPLICATION_RECORD_ELEMENT = 13 APPLICATION_SW_COMPONENT_TYPE = 14 - ARGUMENT_DATA_PROTOTYPE = 15 - AUTOSAR_DATA_PROTOTYPE = 16 - AUTOSAR_DATA_TYPE = 17 - BSW_MODULE_ENTRY = 18 - CLIENT_SERVER_INTERFACE = 19 - CLIENT_SERVER_OPERATION = 20 - COMPOSITION_SW_COMPONENT_TYPE = 21 - COMPU_METHOD = 22 - CONSTANT_SPECIFICATION = 23 - DATA_CONSTR = 24 - DATA_PROTOTYPE = 25 - E2E_PROFILE_COMPATIBILITY_PROPS = 26 - IMPLEMENTATION_DATA_TYPE = 27 - IMPLEMENTATION_DATA_TYPE_ELEMENT = 28 - MODE_DECLARATION = 29 - MODE_DECLARATION_GROUP = 30 - MODE_DECLARATION_GROUP_PROTOTYPE = 31 - MODE_SWITCH_INTERFACE = 32 - NV_DATA_INTERFACE = 33 - P_PORT_PROTOTYPE = 34 - PARAMETER_INTERFACE = 35 - PARAMETER_DATA_PROTOTYPE = 36 - PHYSICAL_DIMENSION = 37 - PORT_PROTOTYPE = 38 - PR_PORT_PROTOTYPE = 39 - R_PORT_PROTOTYPE = 40 - SENDER_RECEIVER_INTERFACE = 41 - SW_ADDR_METHOD = 42 - SW_BASE_TYPE = 43 - SW_COMPONENT_PROTOTYPE = 44 - SWC_INTERNAL_BEHAVIOR = 45 - UNIT = 46 - VARIABLE_DATA_PROTOTYPE = 47 + AR_PACKAGE = 15 + ARGUMENT_DATA_PROTOTYPE = 16 + AUTOSAR_DATA_PROTOTYPE = 17 + AUTOSAR_DATA_TYPE = 18 + BSW_MODULE_ENTRY = 19 + CLIENT_SERVER_INTERFACE = 20 + CLIENT_SERVER_OPERATION = 21 + COMPOSITION_SW_COMPONENT_TYPE = 22 + COMPU_METHOD = 23 + CONSTANT_SPECIFICATION = 24 + DATA_CONSTR = 25 + DATA_PROTOTYPE = 26 + E2E_PROFILE_COMPATIBILITY_PROPS = 27 + IMPLEMENTATION_DATA_TYPE = 28 + IMPLEMENTATION_DATA_TYPE_ELEMENT = 29 + MODE_DECLARATION = 30 + MODE_DECLARATION_GROUP = 31 + MODE_DECLARATION_GROUP_PROTOTYPE = 32 + MODE_SWITCH_INTERFACE = 33 + NV_DATA_INTERFACE = 34 + P_PORT_PROTOTYPE = 35 + PARAMETER_INTERFACE = 36 + PARAMETER_DATA_PROTOTYPE = 37 + PHYSICAL_DIMENSION = 38 + PORT_PROTOTYPE = 39 + PR_PORT_PROTOTYPE = 40 + R_PORT_PROTOTYPE = 41 + SENDER_RECEIVER_INTERFACE = 42 + SW_ADDR_METHOD = 43 + SW_BASE_TYPE = 44 + SW_COMPONENT_PROTOTYPE = 45 + SWC_INTERNAL_BEHAVIOR = 46 + UNIT = 47 + VARIABLE_DATA_PROTOTYPE = 48 class IntervalType(Enum): @@ -706,6 +707,7 @@ class VersionedTextValue: "APPLICATION-RECORD-DATA-TYPE": IdentifiableSubTypes.APPLICATION_RECORD_DATA_TYPE, "APPLICATION-RECORD-ELEMENT": IdentifiableSubTypes.APPLICATION_RECORD_ELEMENT, "APPLICATION-SW-COMPONENT-TYPE": IdentifiableSubTypes.APPLICATION_SW_COMPONENT_TYPE, + "AR-PACKAGE": IdentifiableSubTypes.AR_PACKAGE, "ARGUMENT-DATA-PROTOTYPE": IdentifiableSubTypes.ARGUMENT_DATA_PROTOTYPE, "AUTOSAR-DATA-PROTOTYPE": IdentifiableSubTypes.AUTOSAR_DATA_PROTOTYPE, "AUTOSAR-DATA-TYPE": IdentifiableSubTypes.AUTOSAR_DATA_TYPE, @@ -1061,38 +1063,39 @@ def xml_to_enum(enum_type_name: str, xml_text: str, schema_version: int = ar_bas "APPLICATION-RECORD-ELEMENT", # 13 "APPLICATION-SW-COMPONENT-TYPE", # 14 "ARGUMENT-DATA-PROTOTYPE", # 15 - "AUTOSAR-DATA-PROTOTYPE", # 16 - "AUTOSAR-DATA-TYPE", # 17 - "BSW-MODULE-ENTRY", # 18 - "CLIENT-SERVER-INTERFACE", # 19 - "CLIENT-SERVER-OPERATION", # 20 - "COMPOSITION-SW-COMPONENT-TYPE", # 21 - "COMPU-METHOD", # 22 - "CONSTANT-SPECIFICATION", # 23 - "DATA-CONSTR", # 24 - "DATA-PROTOTYPE", # 25 - "E-2-E-PROFILE-COMPATIBILITY-PROPS", # 26 - "IMPLEMENTATION-DATA-TYPE", # 27 - "IMPLEMENTATION-DATA-TYPE-ELEMENT", # 28 - "MODE-DECLARATION", # 29 - "MODE-DECLARATION-GROUP", # 30 - "MODE-DECLARATION-GROUP-PROTOTYPE", # 31 - "MODE-SWITCH-INTERFACE", # 32 - "NV-DATA-INTERFACE", # 33 - "P-PORT-PROTOTYPE", # 34 - "PARAMETER-INTERFACE", # 35 - "PARAMETER-DATA-PROTOTYPE", # 36 - "PHYSICAL-DIMENSION", # 37 - "PORT-PROTOTYPE", # 38 - "PR-PORT-PROTOTYPE", # 39 - "R-PORT-PROTOTYPE", # 40 - "SENDER-RECEIVER-INTERFACE", # 41 - "SW-ADDR-METHOD", # 42 - "SW-BASE-TYPE", # 43 - "SW-COMPONENT-PROTOTYPE", # 44 - "SWC-INTERNAL-BEHAVIOR", # 45 - "UNIT", # 46 - "VARIABLE-DATA-PROTOTYPE", # 47 + "AR-PACKAGE", # 16 + "AUTOSAR-DATA-PROTOTYPE", # 17 + "AUTOSAR-DATA-TYPE", # 18 + "BSW-MODULE-ENTRY", # 19 + "CLIENT-SERVER-INTERFACE", # 20 + "CLIENT-SERVER-OPERATION", # 21 + "COMPOSITION-SW-COMPONENT-TYPE", # 22 + "COMPU-METHOD", # 23 + "CONSTANT-SPECIFICATION", # 24 + "DATA-CONSTR", # 25 + "DATA-PROTOTYPE", # 26 + "E-2-E-PROFILE-COMPATIBILITY-PROPS", # 27 + "IMPLEMENTATION-DATA-TYPE", # 28 + "IMPLEMENTATION-DATA-TYPE-ELEMENT", # 29 + "MODE-DECLARATION", # 30 + "MODE-DECLARATION-GROUP", # 31 + "MODE-DECLARATION-GROUP-PROTOTYPE", # 32 + "MODE-SWITCH-INTERFACE", # 33 + "NV-DATA-INTERFACE", # 34 + "P-PORT-PROTOTYPE", # 35 + "PARAMETER-INTERFACE", # 36 + "PARAMETER-DATA-PROTOTYPE", # 37 + "PHYSICAL-DIMENSION", # 38 + "PORT-PROTOTYPE", # 39 + "PR-PORT-PROTOTYPE", # 40 + "R-PORT-PROTOTYPE", # 41 + "SENDER-RECEIVER-INTERFACE", # 42 + "SW-ADDR-METHOD", # 43 + "SW-BASE-TYPE", # 44 + "SW-COMPONENT-PROTOTYPE", # 45 + "SWC-INTERNAL-BEHAVIOR", # 46 + "UNIT", # 47 + "VARIABLE-DATA-PROTOTYPE", # 48 ], "IntervalType": [ "CLOSED", # 0 diff --git a/src/autosar/xml/reader.py b/src/autosar/xml/reader.py index 92c9978..6528707 100644 --- a/src/autosar/xml/reader.py +++ b/src/autosar/xml/reader.py @@ -3993,9 +3993,12 @@ def _read_atomic_sw_component_type(self, child_elements: ChildElementMap, data: """ Reads group AR:ATOMIC-SW-COMPONENT-TYPE """ - xml_child = child_elements.get("SWC-INTERNAL-BEHAVIOR") + xml_child = child_elements.get("INTERNAL-BEHAVIORS") if xml_child is not None: - data["internal_behavior"] = self._read_swc_internal_behavior(xml_child) + # We only support max 1 internal behavior element + xml_grand_child = xml_child.find("./SWC-INTERNAL-BEHAVIOR") + if xml_grand_child is not None: + data["internal_behavior"] = self._read_swc_internal_behavior(xml_grand_child) xml_child = child_elements.get("SYMBOL-PROPS") if xml_child is not None: data["symbol_props"] = self._read_symbol_props(xml_child) diff --git a/src/autosar/xml/workspace.py b/src/autosar/xml/workspace.py index 4062b4d..e4763f0 100644 --- a/src/autosar/xml/workspace.py +++ b/src/autosar/xml/workspace.py @@ -18,7 +18,7 @@ class DocumentConfig: """ - Internal class used during document creation + Used to store settings about a document creation action """ def __init__(self, file_path: str, package_refs: list[str] | str | None = None) -> None: @@ -33,6 +33,27 @@ def __init__(self, file_path: str, package_refs: list[str] | str | None = None) self.package_refs.append(package_ref) +class PackageToDocumentMapping: + """ + Used to store settings for a package-to-document-mapping action. + This is used to split a package into multiple documents based on element types + """ + + def __init__(self, + package_ref: str, + element_types: type | tuple[type], + suffix_filters: str | list[str]): + self.package_ref: str = package_ref + self.element_types: type | tuple[type] = element_types + self.suffix_filters: list[str] = [] + if isinstance(suffix_filters, str): + self.element_types.append(suffix_filters) + else: + for suffix_filter in suffix_filters: + assert isinstance(package_ref, str) + self.suffix_filters.append(suffix_filter) + + class Namespace: """ Namespace @@ -52,18 +73,18 @@ def __init__(self, name: str, package_map: dict[str, str], base_ref: str | None self.package_map[package_role] = abs_path -class Workspace: +class Workspace(ar_element.PackageCollection): """ Workspace """ def __init__(self, config_file_path: str | None = None, document_root: str | None = None) -> None: self.namespaces: dict[str, Namespace] = {} - self.packages: list[ar_element.Package] = [] - self._package_dict: dict[str, ar_element.Package] = {} # Each key is the name of an actual package self.documents: list[DocumentConfig] = [] + self.document_mappings: list[PackageToDocumentMapping] = [] self.document_root = document_root self.package_map: dict[str, ar_element.Package] = {} # Each key is user-defined + super().__init__() if config_file_path is not None: self.load_config(config_file_path) @@ -97,35 +118,6 @@ def get_package_ref_by_role(self, namespace_name: str, role: ar_enum.PackageRole raise ValueError(f"Role '{str(role)}'not in namespace map") from ex return posixpath.normpath(posixpath.join(base_ref, rel_path)) - def create_package(self, name: str, **kwargs) -> ar_element.Package: - """ - Creates new package in workspace - """ - if name in self._package_dict: - return ValueError(f"Package with name '{name}' already exists") - package = ar_element.Package(name, **kwargs) - self.append(package) - return package - - def make_packages(self, *refs: list[str]) -> ar_element.Package | list[ar_element.Package]: - """ - Recursively creates packages from reference(s) - Returns a list of created packages. - If only one argument is given it will return that package (not a list). - """ - result = [] - for ref in refs: - if ref.startswith('/'): - ref = ref[1:] - parts = ref.partition('/') - package = self._package_dict.get(parts[0], None) - if package is None: - package = self.create_package(parts[0]) - if len(parts[2]) > 0: - package = package.make_packages(parts[2]) - result.append(package) - return result[0] if len(result) == 1 else result - def init_package_map(self, mapping: dict[str, str]) -> None: """ Initializes an internally stored package map using key-value pairs. @@ -161,7 +153,7 @@ def find_element(self, package_key: str, element_name: str) -> ar_element.ARElem raise RuntimeError("Internal package map not initialized") return self.package_map[package_key].find(element_name) - def get_package(self, package_key: str) -> ar_element.Package: + def get_package_by_key(self, package_key: str) -> ar_element.Package: """ Returns the package referenced by package_key. @@ -171,33 +163,6 @@ def get_package(self, package_key: str) -> ar_element.Package: raise RuntimeError("Internal package map not initialized") return self.package_map[package_key] - def append(self, package: ar_element.Package): - """ - Appends package to this worksapace - """ - assert isinstance(package, ar_element.Package) - self._package_dict[package.name] = package - self.packages.append(package) - package.parent = self - - def find(self, ref: str) -> ar_element.Identifiable | None: - """ - Finds item by reference - """ - if ref.startswith('/'): - ref = ref[1:] - parts = ref.partition('/') - package = self._package_dict.get(parts[0], None) - if (package is not None) and (len(parts[2]) > 0): - return package.find(parts[2]) - return package - - def update_ref_parts(self, ref_parts: list[str]): - """ - Utility method used generating XML references - """ - ref_parts.append('') - def apply(self, template: Any, **kwargs) -> Any: """ Applies template oject in this workspace @@ -216,31 +181,30 @@ def create_document(self, file_path: str, packages: str | list[str] | None = Non """ self.documents.append(DocumentConfig(file_path, packages)) + def create_document_mapping(self, + package_ref: str, + element_types: type | tuple[type], + suffix_filters: str | list[str]): + """ + Splits a package into multiple documents. The document name(s) equal the short-name of the found element(s) + """ + self.document_mappings.append(PackageToDocumentMapping(package_ref, element_types, suffix_filters)) + def set_document_root(self, directory: str) -> None: """ Sets root directory where documents are written """ self.document_root = directory - def write_documents(self, scehema_version=ar_base.DEFAULT_SCHEMA_VERSION) -> None: + def write_documents(self, schema_version=ar_base.DEFAULT_SCHEMA_VERSION) -> None: """ Writes all documents to file system """ writer = Writer() for document_config in self.documents: - document = document_config.document - file_path = document_config.file_path - package_refs = document_config.package_refs - document.schema_version = scehema_version - for package_ref in package_refs: - package = self.find(package_ref) - if package is not None: - if not isinstance(package, ar_element.Package): - raise ValueError(f"'{package_ref}' does not reference a package element") - document.append(package) - if self.document_root is not None: - file_path = os.path.join(self.document_root, file_path) - writer.write_file(document, file_path) + self._write_document_from_config(writer, schema_version, document_config) + for package_document_mapping in self.document_mappings: + self._gen_package_to_document_mapping(writer, schema_version, package_document_mapping) def load_config(self, file_path: str) -> None: """ @@ -257,6 +221,58 @@ def load_config(self, file_path: str) -> None: for name, doc_config in document.items(): self._create_document_from_config(name, doc_config) + def _write_document_from_config(self, + writer: Writer, + schema_version: int, + document_config: DocumentConfig) -> None: + document = document_config.document + file_path = document_config.file_path + package_refs = document_config.package_refs + document.schema_version = schema_version + for package_ref in package_refs: + package = self.find(package_ref) + if package is not None: + if not isinstance(package, ar_element.Package): + raise ValueError(f"'{package_ref}' does not reference a package element") + document.append(package) + if self.document_root is not None: + file_path = os.path.join(self.document_root, file_path) + writer.write_file(document, file_path) + + def _gen_package_to_document_mapping(self, + writer: Writer, + schema_version: int, + mapping: PackageToDocumentMapping) -> None: + package = self.find(mapping.package_ref) + if package is not None: + if not isinstance(package, ar_element.Package): + raise ValueError(f"'{mapping.package_ref}' does not reference a package element") + element_map = {} + type_matched_elements = {} + for element in package.elements: + element_map[element.name] = element + if isinstance(element, mapping.element_types): + type_matched_elements[element.name] = element + for matched_element in type_matched_elements.values(): + document_name = matched_element.name + ".arxml" + element_list = [matched_element] + if mapping.suffix_filters: + for suffix in mapping.suffix_filters: + if len(suffix) > 0: + name = matched_element.name + suffix + extra_element = element_map.get(name, None) + if extra_element is not None: + element_list.append(extra_element) + document = ar_document.Document(schema_version=schema_version) + new_package = document.make_packages(str(package.ref())) + for element in sorted(element_list, key=lambda x: x.name): + new_package.append(element) + if self.document_root is not None: + file_path = os.path.join(self.document_root, document_name) + else: + file_path = document_name + writer.write_file(document, file_path) + def _create_namespace_from_config(self, name: str, config: dict): base_ref = None package_map = {} diff --git a/src/autosar/xml/writer.py b/src/autosar/xml/writer.py index 626d1f1..b9294c1 100644 --- a/src/autosar/xml/writer.py +++ b/src/autosar/xml/writer.py @@ -3459,7 +3459,9 @@ def _write_atomic_sw_component_type(self, elem: ar_element.AtomicSoftwareCompone Writes AR:ATOMIC-SW-COMPONENT-TYPE """ if elem.internal_behavior is not None: + self._add_child("INTERNAL-BEHAVIORS") self._write_swc_internal_behavior(elem.internal_behavior) + self._leave_child() if elem.symbol_props is not None: self._write_symbol_props(elem.symbol_props, "SYMBOL-PROPS") diff --git a/tests/xml/test_software_component_elements.py b/tests/xml/test_software_component_elements.py index 0c219fd..92b3a17 100644 --- a/tests/xml/test_software_component_elements.py +++ b/tests/xml/test_software_component_elements.py @@ -1341,9 +1341,11 @@ def test_internal_behavior(self): writer = autosar.xml.Writer() xml = ''' ShortName - - BehaviorName - + + + BehaviorName + + ''' self.assertEqual(writer.write_str_elem(element), xml) reader = autosar.xml.Reader()