|
| 1 | +""" |
| 2 | +AUTOSAR XML base classes |
| 3 | +""" |
| 4 | + |
| 5 | +import abc |
| 6 | +import re |
| 7 | +from typing import Any, Type |
| 8 | +from enum import Enum |
| 9 | +import autosar.xml.enumeration as ar_enum |
| 10 | + |
| 11 | + |
| 12 | +class ARObject: |
| 13 | + """ |
| 14 | + Base class for all AUTOSAR objects |
| 15 | + """ |
| 16 | + |
| 17 | + @property |
| 18 | + def is_empty(self) -> bool: |
| 19 | + """ |
| 20 | + True if no value has been set (everything is None) |
| 21 | + """ |
| 22 | + for value in vars(self).values(): |
| 23 | + if isinstance(value, list): |
| 24 | + if len(value) > 0: |
| 25 | + return False |
| 26 | + else: |
| 27 | + if value is not None: |
| 28 | + return False |
| 29 | + return True |
| 30 | + |
| 31 | + def is_empty_with_ignore(self, ignore_set: set) -> bool: |
| 32 | + """ |
| 33 | + Same as is_empty but the caller can give |
| 34 | + a list of property names to ignore during |
| 35 | + check |
| 36 | + """ |
| 37 | + for key in vars(self).keys(): |
| 38 | + if key not in ignore_set: |
| 39 | + value = getattr(self, key) |
| 40 | + if isinstance(value, list): |
| 41 | + if len(value) > 0: |
| 42 | + return False |
| 43 | + else: |
| 44 | + if value is not None: |
| 45 | + return False |
| 46 | + return True |
| 47 | + |
| 48 | + def _assign_optional(self, attr_name: str, value: Any, type_name: type) -> None: |
| 49 | + """ |
| 50 | + Same as _assign but with a None-check |
| 51 | + """ |
| 52 | + if value is not None: |
| 53 | + self._assign(attr_name, value, type_name) |
| 54 | + |
| 55 | + def _assign(self, attr_name: str, value: Any, type_name: type) -> None: |
| 56 | + """ |
| 57 | + Assign single value to attribute with type check. |
| 58 | + """ |
| 59 | + if issubclass(type_name, Enum): |
| 60 | + self._set_attr_with_strict_type(attr_name, value, type_name) |
| 61 | + elif issubclass(type_name, BaseRef): |
| 62 | + self._check_and_set_reference(attr_name, value, type_name) |
| 63 | + else: |
| 64 | + self._set_attr_with_type_cast(attr_name, value, type_name) |
| 65 | + |
| 66 | + def _assign_int_or_str_pattern_optional(self, attr_name: str, value: int | str | None, pattern: re.Pattern) -> None: |
| 67 | + """ |
| 68 | + Same as _assign_int_or_str_pattern but with a None-check |
| 69 | + """ |
| 70 | + if value is not None: |
| 71 | + self._assign_int_or_str_pattern(attr_name, value, pattern) |
| 72 | + |
| 73 | + def _assign_int_or_str_pattern(self, attr_name: str, value: int | str, pattern: re.Pattern) -> None: |
| 74 | + """ |
| 75 | + Special assignment-function for values that can be either int or conforms |
| 76 | + to a specific regular expression |
| 77 | + """ |
| 78 | + if isinstance(value, int): |
| 79 | + pass |
| 80 | + elif isinstance(value, str): |
| 81 | + match = pattern.match(value) |
| 82 | + if match is None: |
| 83 | + raise ValueError(f"Invalid parameter '{value}' for '{attr_name}'") |
| 84 | + else: |
| 85 | + raise TypeError(f"{attr_name}: Invalid type. Expected (int, str), got '{str(type(value))}'") |
| 86 | + setattr(self, attr_name, value) |
| 87 | + |
| 88 | + def _assign_optional_strict(self, attr_name: str, value: Any, type_name: type) -> None: |
| 89 | + """ |
| 90 | + Sets object attribute with strict type check |
| 91 | + """ |
| 92 | + if value is not None: |
| 93 | + self._set_attr_with_strict_type(attr_name, value, type_name) |
| 94 | + |
| 95 | + def _assign_optional_positive_int(self, attr_name, value: int) -> None: |
| 96 | + """ |
| 97 | + Checks that the optional value is a positive integer before assignment |
| 98 | + """ |
| 99 | + if value is not None: |
| 100 | + self._set_attr_positive_int(attr_name, value) |
| 101 | + |
| 102 | + def _set_attr_with_strict_type(self, attr_name: str, value: Any, type_class: type) -> None: |
| 103 | + """ |
| 104 | + Sets object attribute only if the value is matches given type-class |
| 105 | + """ |
| 106 | + if isinstance(value, type_class): |
| 107 | + setattr(self, attr_name, value) |
| 108 | + else: |
| 109 | + raise TypeError( |
| 110 | + f"Invalid type for parameter '{attr_name}'. Expected type {str(type_class)}, got {str(type(value))}") |
| 111 | + |
| 112 | + def _check_and_set_reference(self, attr_name: str, value: Any, ref_type: Type["BaseRef"]): |
| 113 | + """ |
| 114 | + Checks reference type compatibility and on success updates the value |
| 115 | + """ |
| 116 | + accepted_sub_types: set = ref_type.accepted_sub_types() |
| 117 | + if (len(accepted_sub_types) == 0) or not isinstance(accepted_sub_types, set): |
| 118 | + msg = f"Error in reference type '{str(type(ref_type))}', it doesn't seem to have a valid set of sub-types." |
| 119 | + raise RuntimeError(msg) |
| 120 | + if isinstance(value, str): |
| 121 | + if len(accepted_sub_types) == 1: |
| 122 | + new_value = ref_type(value, list(accepted_sub_types)[0]) |
| 123 | + else: |
| 124 | + raise TypeError("Ambigious value for DEST. Unable to create reference directly from string") |
| 125 | + elif isinstance(value, BaseRef): |
| 126 | + if type(value) is ref_type or value.dest in accepted_sub_types: # pylint: disable=C0123 |
| 127 | + new_value = ref_type(value.value, value.dest) |
| 128 | + else: |
| 129 | + raise TypeError(f"'{attr_name}': Reference type {str(type(value))}" |
| 130 | + f"isn't combatible with {str(ref_type)}") |
| 131 | + else: |
| 132 | + raise TypeError(f"'{attr_name}': Invalid type. " |
| 133 | + f"Expected one of (str, {str(ref_type)}), got '{str(type(value))}'") |
| 134 | + setattr(self, attr_name, new_value) |
| 135 | + |
| 136 | + def _set_attr_with_type_cast(self, attr_name: str, value: Any, type_class: type) -> None: |
| 137 | + """ |
| 138 | + Sets object attribute only if it can be converted to given type. |
| 139 | + """ |
| 140 | + if type_class in {bool, int, str, float}: |
| 141 | + new_value = type_class(value) |
| 142 | + else: |
| 143 | + raise NotImplementedError(type_class) |
| 144 | + setattr(self, attr_name, new_value) |
| 145 | + |
| 146 | + def _set_attr_positive_int(self, attr_name: str, value: int) -> None: |
| 147 | + """ |
| 148 | + Checks that value is non-negative before updating attribute |
| 149 | + """ |
| 150 | + if not isinstance(value, int): |
| 151 | + raise TypeError(f"Invalid type for '{attr_name}'. Expected int, got '{str(type(value))}'") |
| 152 | + if value < 0: |
| 153 | + raise ValueError(f"Positive integer expected: {value}") |
| 154 | + setattr(self, attr_name, value) |
| 155 | + |
| 156 | + def _find_by_name(self, elements: list, name: str): |
| 157 | + """ |
| 158 | + Iterates through list of elements and return the first whose |
| 159 | + name matches the name argument |
| 160 | + """ |
| 161 | + for elem in elements: |
| 162 | + if elem.name == name: |
| 163 | + return elem |
| 164 | + return None |
| 165 | + |
| 166 | + |
| 167 | +class BaseRef(ARObject, abc.ABC): |
| 168 | + """ |
| 169 | + Base type for all reference classes |
| 170 | + """ |
| 171 | + |
| 172 | + def __init__(self, |
| 173 | + value: str, |
| 174 | + dest: ar_enum.IdentifiableSubTypes = None) -> None: |
| 175 | + self.value = value |
| 176 | + self.dest: ar_enum.IdentifiableSubTypes = None |
| 177 | + if dest is None: |
| 178 | + if len(self.accepted_sub_types()) == 1: |
| 179 | + dest = list(self.accepted_sub_types())[0] |
| 180 | + else: |
| 181 | + msg_part1 = "Value of dest cannot be None. Accepted values are: " |
| 182 | + msg_part2 = ",".join([str(x) for x in sorted(list(self.accepted_sub_types()))]) |
| 183 | + raise ValueError(msg_part1 + msg_part2) |
| 184 | + if dest in self.accepted_sub_types(): |
| 185 | + self.dest = dest |
| 186 | + else: |
| 187 | + raise ValueError(f"{str(dest)} is not a valid sub-type for {str(type(self))}") |
| 188 | + |
| 189 | + @classmethod |
| 190 | + @abc.abstractmethod |
| 191 | + def accepted_sub_types(cls) -> list[ar_enum.IdentifiableSubTypes]: |
| 192 | + """ |
| 193 | + Subset of ar_enum.IdentifiableSubTypes defining |
| 194 | + which enum values are acceptable for dest |
| 195 | + """ |
| 196 | + |
| 197 | + def __str__(self) -> str: |
| 198 | + """Returns reference as string""" |
| 199 | + return self.value |
0 commit comments