def _check_stereotype_can_be_added(self, stereotype): if stereotype in self.stereotypes_: raise CException( f"'{stereotype.name!s}' is already a stereotype instance on {self._get_element_name_string()!s}") if not stereotype.is_element_extended_by_stereotype_(self.element): raise CException(f"stereotype '{stereotype!s}' cannot be added to " + f"{self._get_element_name_string()!s}: no extension by this stereotype found")
def get_common_classifier(objects): common_classifier = None for o in objects: if o is None or not is_cobject(o): raise CException(f"not an object: '{o!s}'") if common_classifier is None: common_classifier = o.classifier else: object_classifiers = {o.classifier }.union(o.classifier.all_superclasses) common_classifier_found = False if common_classifier in object_classifiers: common_classifier_found = True if not common_classifier_found and common_classifier in o.classifier.all_subclasses: common_classifier = o.classifier common_classifier_found = True if not common_classifier_found: for cl in common_classifier.all_superclasses: if cl in object_classifiers: common_classifier = cl common_classifier_found = True break if not common_classifier_found: if is_clink(o): raise CException( f"the link's association is missing a compatible classifier" ) else: raise CException( f"object '{o!s}' has an incompatible classifier") return common_classifier
def stop_elements_exclusive(self, stop_elements_exclusive): if is_cbundlable(stop_elements_exclusive): stop_elements_exclusive = [stop_elements_exclusive] if not isinstance(stop_elements_exclusive, list): raise CException( f"expected a list of stop elements, but got: '{stop_elements_exclusive!s}'" ) for e in stop_elements_exclusive: if not is_cbundlable(e): raise CException( f"expected a list of stop elements, but got: '{stop_elements_exclusive!s}'" + f" with element of wrong type: '{e!s}'") self._stop_elements_exclusive = stop_elements_exclusive self.all_stop_elements = self._stop_elements_inclusive + self._stop_elements_exclusive
def __init__(self, name, **kwargs): """CNamedElement is the superclass for all named elements in Codeable Models, such as CClass, CObject, and so on. The class is usually not used directly. When it is reached in the inheritance hierarchy, it sets all keyword args contained in ``kwargs``. This is performed using the ``set_keyword_args()`` method, to be defined by subclasses. Calling it with keyword args that are not specified as legal keyword args (to be done in subclasses, in the ``legal_keyword_args`` list), causes an exception. For example, ``CClassifier`` adds ``superclasses`` and ``attributes`` to the legal keyword args, which can then be used on ``CClassifier`` and subclasses such as ``CClass``:: CClass(domain_metaclass, "Item", attributes={ "quantity": int, "price": float }) Args: name (str): An optional name. **kwargs: Accepts keyword args defined as ``legal_keyword_args`` by subclasses. Attributes: name (str): The name of the entity. Can be ``None``. """ self.name = name super().__init__() self.is_deleted = False if name is not None and not isinstance(name, str): raise CException(f"is not a name string: '{name!r}'") self._init_keyword_args(**kwargs)
def add_object_(self, obj): if obj in self.objects_: raise CException( f"object '{obj!s}' is already an instance of the class '{self!s}'" ) check_is_cobject(obj) self.objects_.append(obj)
def set_keyword_args(obj, allowed_values, **kwargs): for key in kwargs: if key in allowed_values: setattr(obj, key, kwargs[key]) else: raise CException( f"unknown keyword argument '{key!s}', should be one of: {allowed_values!s}" )
def values(self, values): if values is None: values = [] if not isinstance(values, list): raise CException( f"an enum needs to be initialized with a list of values, but got: '{values!s}'" ) self.values_ = values
def _get_element_name_string(self): if is_cclass(self.element): return f"'{self.element.name!s}'" elif is_clink(self.element): return f"link from '{self.element.source!s}' to '{self.element.target!s}'" elif is_cassociation(self.element): return f"association from '{self.element.source!s}' to '{self.element.target!s}'" raise CException(f"unexpected element type: {self.element!r}")
def _check_derived_association_has_same_aggregation_state( self, metaclass_association): if metaclass_association.aggregation and not self.aggregation: raise CException( f"metaclass association is an aggregation, but derived association is not" ) if self.aggregation and not metaclass_association.aggregation: raise CException( f"derived association is an aggregation, but metaclass association is not" ) if metaclass_association.composition and not self.composition: raise CException( f"metaclass association is a composition, but derived association is not" ) if self.composition and not metaclass_association.composition: raise CException( f"derived association is a composition, but metaclass association is not" )
def _check_association_class_derived_from_association_metaclass( class_, metaclass_, direction_string): try: check_is_cclass(class_) except CException: raise CException( "association 'derived_from' is called on is not a class-level association" ) try: check_is_cmetaclass(metaclass_) except CException: raise CException( "association used as 'derived_from' parameter is not a metaclass-level association" ) if not class_.metaclass.is_classifier_of_type(metaclass_): raise CException( f"association {direction_string!s} class '{class_!s}' is not " + f"derived {direction_string!s} metaclass '{metaclass_!s}'")
def super_layer(self, layer): if layer is not None and not isinstance(layer, CLayer): raise CException(f"not a layer: {layer!s}") if self._super_layer is not None: self._super_layer._sub_layer = None self._super_layer = layer if layer is not None: if layer._sub_layer is not None: layer._sub_layer._super_layer = None layer._sub_layer = self
def tagged_values(self): """dict[str, value]: Getter for getting all tagged values of the association using a dict, and setter of setting all tagged values of the association based on a dict. The dict uses key/value pairs. The value types must conform to the types defined for the attributes. """ if self.is_deleted: raise CException("can't get tagged values on deleted link") return get_var_values( self.stereotype_instances_holder.get_stereotype_instance_path(), self.tagged_values_)
def _eval_descriptor(self, descriptor): # handle name only if a ':' is found in the descriptor index = descriptor.find(":") if index != -1: name = descriptor[0:index] descriptor = descriptor[index + 1:] self.name = name.strip() # handle type of relation aggregation = False composition = False index = descriptor.find("->") length = 2 if index == -1: index = descriptor.find("<>-") if index != -1: length = 3 aggregation = True else: index = descriptor.find("<*>-") length = 4 composition = True if index == -1: raise CException("association descriptor malformed: '" + descriptor + "'") # handle role names and multiplicities source_str = descriptor[0:index] target_str = descriptor[index + length:] regexp_with_role_name = r'\s*\[([^\]]+)\]\s*(\S*)\s*' regexp_only_multiplicity = r'\s*(\S*)\s*' m = re.search(regexp_with_role_name, source_str) if m is not None: self.source_role_name = m.group(1) if m.group(2) != '': self.source_multiplicity = m.group(2) else: m = re.search(regexp_only_multiplicity, source_str) self.source_multiplicity = m.group(1) m = re.search(regexp_with_role_name, target_str) if m is not None: self.role_name = m.group(1) if m.group(2) != '': self.multiplicity = m.group(2) else: m = re.search(regexp_only_multiplicity, target_str) self.multiplicity = m.group(1) if aggregation: self.aggregation = True elif composition: self.composition = True
def bundles(self, bundles): if bundles is None: bundles = [] for b in self.bundles_: b.remove(self) self.bundles_ = [] if is_cbundle(bundles): bundles = [bundles] elif not isinstance(bundles, list): raise CException( f"bundles requires a list of bundles or a bundle as input") for b in bundles: if not is_cbundle(b): raise CException( f"bundles requires a list of bundles or a bundle as input") check_named_element_is_not_deleted(b) if b in self.bundles_: raise CException( f"'{b.name!s}' is already a bundle of '{self.name!s}'") self.bundles_.append(b) b.elements_.append(self)
def get_links(linked_elements): result = [] for linked_ in linked_elements: linked = linked_ if not is_cobject(linked) and not is_clink(linked): if is_cclass(linked): linked_ = linked.class_object else: raise CException( f"'{linked!s}' is not an object, class, or link") result.extend(linked_.links) return result
def add(self, elt): """ Add an element to the bundle. Args: elt (CBundlable): Element to add to the bundle Returns: None """ if elt is not None: if elt in self.elements_: raise CException( f"element '{elt!s}' cannot be added to bundle: element is already in bundle" ) if isinstance(elt, CBundlable): self.elements_.append(elt) elt.bundles_.append(self) return raise CException(f"can't add '{elt!s}': not an element")
def _set_multiplicity(self, multiplicity, is_target_multiplicity): if not isinstance(multiplicity, str): raise CException("multiplicity must be provided as a string") try: dots_pos = multiplicity.find("..") if dots_pos != -1: lower_match = multiplicity[:dots_pos] upper_match = multiplicity[dots_pos + 2:] lower = int(lower_match) if lower < 0: raise CException( f"negative multiplicity in '{multiplicity!s}'") if upper_match.strip() == "*": upper = self.STAR_MULTIPLICITY else: upper = int(upper_match) if lower < 0 or upper < 0: raise CException( f"negative multiplicity in '{multiplicity!s}'") elif multiplicity.strip() == "*": lower = 0 upper = self.STAR_MULTIPLICITY else: lower = int(multiplicity) if lower < 0: raise CException( f"negative multiplicity in '{multiplicity!s}'") upper = lower except Exception as e: if isinstance(e, CException): raise e raise CException(f"malformed multiplicity: '{multiplicity!s}'") if is_target_multiplicity: self.upper_multiplicity = upper self.lower_multiplicity = lower else: self.source_upper_multiplicity = upper self.source_lower_multiplicity = lower
def elements(self, elements): if elements is None: elements = [] for e in self.elements_: e._bundle = None self.elements_ = [] if is_cnamedelement(elements): elements = [elements] elif not isinstance(elements, list): raise CException( f"elements requires a list or a named element as input") for e in elements: if e is not None: check_named_element_is_not_deleted(e) else: raise CException(f"'None' cannot be an element of bundle") is_cnamedelement(e) if e not in self.elements_: # if it is already in the bundle, do not add it twice self.elements_.append(e) # noinspection PyUnresolvedReferences e.bundles_.append(self)
def get_common_metaclasses(classes_or_links): common_metaclasses = None for classifier in classes_or_links: if is_clink(classifier): link_classifiers = [ link_cl for link_cl in classifier.association.all_superclasses if is_cmetaclass(link_cl) ] if not link_classifiers: raise CException( f"the metaclass link's association is missing a compatible classifier" ) if common_metaclasses is None: common_metaclasses = link_classifiers else: common_metaclasses = update_common_metaclasses( common_metaclasses, link_classifiers) if len(common_metaclasses) == 0: break elif classifier is None or not is_cclass(classifier): raise CException(f"not a class or link: '{classifier!s}'") else: if common_metaclasses is None: common_metaclasses = classifier.metaclass.class_path else: common_metaclasses = update_common_metaclasses( common_metaclasses, [classifier.metaclass]) if len(common_metaclasses) == 0: break if common_metaclasses is None: return [None] if len(common_metaclasses) == 0: raise CException(f"no common metaclass for classes or links found") # if some superclasses and their subclasses are in the list, take only the subclasses # and remove duplicates form the list common_metaclasses = _remove_superclasses_and_duplicates( common_metaclasses) return common_metaclasses
def remove_class(self, cl): """Remove the class ``cl`` from the classes of this meta-class. Raises an exception, if ``cl`` is not a class derived from this meta-class. Args: cl (CClass): A class to remove. Returns: None """ if cl not in self.classes_: raise CException( f"can't remove class instance '{cl!s}' from metaclass '{self!s}': not a class instance" ) self.classes_.remove(cl)
def remove(self, element): """ Remove an element from the bundle. Args: element (CBundlable): Element to remove from the bundle. Returns: None """ if (element is None or (not isinstance(element, CBundlable)) or (self not in element.bundles)): raise CException(f"'{element!s}' is not an element of the bundle") self.elements_.remove(element) element.bundles_.remove(self)
def add_class(self, cl): """Add the class ``cl`` to the classes of this meta-class. Args: cl (CClass): A class to add. Returns: None """ check_is_cclass(cl) if cl in self.classes_: raise CException( f"class '{cl!s}' is already a class of the metaclass '{self!s}'" ) self.classes_.append(cl)
def _set_stereotypes(self, elements): if elements is None: elements = [] self._remove_from_stereotype() self.stereotypes_ = [] if is_cstereotype(elements): elements = [elements] elif not isinstance(elements, list): raise CException(f"a list or a stereotype is required as input") for s in elements: check_is_cstereotype(s) if s is not None: check_named_element_is_not_deleted(s) self._check_stereotype_can_be_added(s) self.stereotypes_.append(s) # noinspection PyTypeChecker self._append_to_stereotype(s) self._init_extended_element(s)
def association(self, target, descriptor=None, **kwargs): """Method used to create associations on this class. See documentation of method ``association`` on :py:class:`.CClassifier` for details. Args: target: The association target classifier. descriptor: An optional descriptor making it easier to define associations with a simple string. **kwargs: Accepts all keyword arguments acceptable to :py:class:`.CAssociation` to define associations. Returns: CAssociation: The created association. """ if not isinstance(target, CClass): raise CException( f"class '{self!s}' is not compatible with association target '{target!s}'" ) return super(CClass, self).association(target, descriptor, **kwargs)
def get_opposite_classifier(self, classifier): """Given a classifier, this method returns the opposite in the association, i.e. the source if ``classifier`` is the target, and vice versa. Raises an exception if ``classifier`` is neither source nor target. Args: classifier: The classifier from which we want to get the opposite in the association. Returns: CClassifier: The opposite classifier. """ if classifier == self.source: return self.target elif classifier == self.target: return self.source else: raise CException("can only get opposite if either source or target classifier is provided")
def check_multiplicity_(self, obj, actual_length, actual_opposite_length, check_target_multiplicity): if check_target_multiplicity: upper = self.upper_multiplicity lower = self.lower_multiplicity other_side_lower = self.source_lower_multiplicity multiplicity_string = self.multiplicity_ else: upper = self.source_upper_multiplicity lower = self.source_lower_multiplicity other_side_lower = self.lower_multiplicity multiplicity_string = self.source_multiplicity_ if (upper != CAssociation.STAR_MULTIPLICITY and actual_length > upper) or actual_length < lower: # if there is actually no link as actualOppositeLength is zero, this is ok, if the otherLower # including zero: if not (actual_opposite_length == 0 and other_side_lower == 0): raise CException(f"links of object '{obj}' have wrong multiplicity " + f"'{actual_length!s}': should be '{multiplicity_string!s}'")
def delete_tagged_value(self, name, stereotype=None): """Delete tagged value of a stereotype attribute with the given ``name``. Optionally the stereotype to consider can be specified. This is needed, if one or more attributes of the same name are defined on the inheritance hierarchy. Then a shadowed attribute can be accessed by specifying its stereotype. Args: name: The name of the attribute. stereotype: The optional stereotype on which the attribute is defined. Returns: Supported Attribute Types: Value of the attribute. """ if self.is_deleted: raise CException( f"can't delete tagged value '{name!s}' on deleted link") return delete_var_value( self, self.stereotype_instances_holder.get_stereotype_instance_path(), self.tagged_values_, name, VarValueKind.TAGGED_VALUE, stereotype)
def derived_from(self, metaclass_association): if metaclass_association is not None: if not is_cassociation(metaclass_association): raise CException( f"'{metaclass_association!s}' is not an association") self._check_association_class_derived_from_association_metaclass( self.source, metaclass_association.source, "source") self._check_association_class_derived_from_association_metaclass( self.target, metaclass_association.target, "target") self.check_derived_association_multiplicities_( metaclass_association) self._check_derived_association_has_same_aggregation_state( metaclass_association) if self.derived_from_ is not None: self.derived_from_.derived_associations_.remove(self) self.derived_from_ = None self.derived_from_ = metaclass_association if metaclass_association is not None: metaclass_association.derived_associations_.append(self)
def get_elements(self, **kwargs): """ Get specific elements from the bundle. Per default returns all elements. Args: **kwargs: Used to specify in more detail which elements to get from the bundle. Accepts the following arguments: - ``type``: Return only elements which are instances of the specified type. - ``name``: Return only elements with the specified name. Returns: List[CBundlable]: List of elements. """ type_ = None name = None # use this as name can also be provided as None name_specified = False for key in kwargs: if key == "type": type_ = kwargs["type"] elif key == "name": name = kwargs["name"] name_specified = True else: raise CException(f"unknown argument to getElements: '{key!s}'") elements = [] for elt in self.elements_: append = True if name_specified and elt.name != name: append = False # noinspection PyTypeHints if type_ is not None and not isinstance(elt, type_): append = False if append: elements.append(elt) return elements
def _check_stereotype_can_be_added(self, stereotype): if stereotype in self.stereotypes_: raise CException( f"'{stereotype.name!s}' is already a stereotype of '{self.element.name!s}'" )