diff --git a/.gitignore b/.gitignore
index 1395c2d..2c18da1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
-.idea/
-out/
+**/.pytest_cache
+**/.vscode
+**/__pycache__
+*.noseids
diff --git a/CodeableModels.iml b/CodeableModels.iml
deleted file mode 100644
index 3a8ffcf..0000000
--- a/CodeableModels.iml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/README.md b/README.md
index b941644..9ba12d0 100644
--- a/README.md
+++ b/README.md
@@ -17,27 +17,28 @@ clear from the interfaces.
### Prerequisites / Installing
-The project - by purpose - only uses plain old Java objects. So there are no requirements
-to get the project up and running, other than placing it in your classpath.
+The project - by purpose - only uses plain Python. So there are no requirements
+to get the project up and running.
-JUnit is required in the classpath for executing the test cases.
+Nosetest is required for executing the test cases. Find installation instructions under:
+[Nosetest](https://nose.readthedocs.io/en/latest/)
## Running the tests
-```
-codedableModels/tests/AllTests.java
-```
-
-contains the test suite. Run it as a JUnit test suite. All other files in the tests
-directory contain individual JUnit tests.
+Execute `nosetests` either in the main directory of the project or in `./tests`. The test files contained in
+this directory comprise the test suite.
## Deployment
-No specific instructions so far; simply build
+No specific instructions so far; simply import from the `codeableModels` module like:
+
+```
+from codeableModels import CMetaclass, CClass, CObject, CAttribute, CException, CEnum, CStereotype
+```
## Built With
-* [JUnit](https://junit.org) - The test framework used
+* [Nosetest](https://nose.readthedocs.io/en/latest/) - The test framework used
## Contributing
diff --git a/codeableModels/__init__.py b/codeableModels/__init__.py
new file mode 100644
index 0000000..b008312
--- /dev/null
+++ b/codeableModels/__init__.py
@@ -0,0 +1,22 @@
+#from os.path import dirname, basename, isfile
+#import glob
+#import importlib
+
+#modules = glob.glob(dirname(__file__)+"/*.py")
+#__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]
+
+#from .impl import *
+
+from codeableModels.cexception import CException
+from codeableModels.cnamedelement import CNamedElement
+from codeableModels.cbundlable import CBundlable
+from codeableModels.cattribute import CAttribute
+from codeableModels.cclassifier import CClassifier
+from codeableModels.cmetaclass import CMetaclass
+from codeableModels.cstereotype import CStereotype
+from codeableModels.cclass import CClass
+from codeableModels.cobject import CObject
+from codeableModels.cenum import CEnum
+from codeableModels.cbundle import CBundle, CPackage, CLayer
+from codeableModels.cassociation import CAssociation
+from codeableModels.clink import CLink, setLinks, addLinks, deleteLinks
diff --git a/codeableModels/cassociation.py b/codeableModels/cassociation.py
new file mode 100644
index 0000000..358cace
--- /dev/null
+++ b/codeableModels/cassociation.py
@@ -0,0 +1,241 @@
+from codeableModels.internal.commons import setKeywordArgs, isCClassifier, isCMetaclass, isCStereotype
+from codeableModels.cexception import CException
+from codeableModels.cnamedelement import CNamedElement
+from codeableModels.internal.stereotype_holders import CStereotypesHolder
+import re
+
+class CAssociation(CNamedElement):
+ STAR_MULTIPLICITY = -1
+
+ def __init__(self, source, target, descriptor = None, **kwargs):
+ self.source = source
+ self.target = target
+ self.roleName = None
+ self.sourceRoleName = None
+ self._sourceMultiplicityString = "1"
+ self._sourceLowerMultiplicity = 1
+ self._sourceUpperMultiplicity = 1
+ self._multiplicityString = "*"
+ self._lowerMultiplicity = 0
+ self._upperMultiplicity = self.STAR_MULTIPLICITY
+ self._aggregation = False
+ self._composition = False
+ self._stereotypesHolder = CStereotypesHolder(self)
+ name = kwargs.pop("name", None)
+ self.ends = None
+ super().__init__(name, **kwargs)
+ if descriptor != None:
+ self._evalDescriptor(descriptor)
+
+ def _initKeywordArgs(self, legalKeywordArgs = None, **kwargs):
+ if legalKeywordArgs == None:
+ legalKeywordArgs = []
+ legalKeywordArgs.extend(["multiplicity", "roleName", "sourceMultiplicity",
+ "sourceRoleName", "aggregation", "composition", "stereotypes"])
+ super()._initKeywordArgs(legalKeywordArgs, **kwargs)
+
+ def __str__(self):
+ return super(CAssociation, self).__str__()
+ def __repr__(self):
+ name = ""
+ if self.name != None:
+ name = self.name
+ return f"CAssociation name = {name!s}, source = {self.source!s} -> target = {self.target!s}"
+
+ def _getOppositeClass(self, cl):
+ if cl == self.source:
+ return self.target
+ else:
+ return self.source
+
+ def _matches(self, classifier, roleName, associationClassifier, associationRoleName):
+ if classifier == None and roleName == None:
+ return False
+ matches = True
+ if roleName != None:
+ if roleName != associationRoleName:
+ matches = False
+ if matches and classifier != None:
+ if not classifier.conformsToType(associationClassifier):
+ matches = False
+ if matches:
+ return True
+ return False
+
+ def _matchesTarget(self, classifier, roleName):
+ return self._matches(classifier, roleName, self.target, self.roleName)
+
+ def _matchesSource(self, classifier, roleName):
+ return self._matches(classifier, roleName, self.source, self.sourceRoleName)
+
+ @property
+ def aggregation(self):
+ return self._aggregation
+ @aggregation.setter
+ def aggregation(self, aggregation):
+ if aggregation:
+ self._composition = False
+ self._aggregation = aggregation
+
+ def _setMultiplicity(self, multiplicity, isTargetMultiplicity):
+ if not isinstance(multiplicity, str):
+ raise CException("multiplicity must be provided as a string")
+ lower = -2
+ upper = -2
+ try:
+ dotsPos = multiplicity.find("..")
+ if dotsPos != -1:
+ lowerMatch = multiplicity[:dotsPos]
+ upperMatch = multiplicity[dotsPos+2:]
+ lower = int(lowerMatch)
+ if lower < 0:
+ raise CException(f"negative multiplicity in '{multiplicity!s}'")
+ if upperMatch.strip() == "*":
+ upper = self.STAR_MULTIPLICITY
+ else:
+ upper = int(upperMatch)
+ 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 isTargetMultiplicity:
+ self._upperMultiplicity = upper
+ self._lowerMultiplicity = lower
+ else:
+ self._sourceUpperMultiplicity = upper
+ self._sourceLowerMultiplicity = lower
+
+ @property
+ def multiplicity(self):
+ return self._multiplicityString
+ @multiplicity.setter
+ def multiplicity(self, multiplicity):
+ self._multiplicityString = multiplicity
+ self._setMultiplicity(multiplicity, True)
+
+ @property
+ def sourceMultiplicity(self):
+ return self._sourceMultiplicityString
+ @sourceMultiplicity.setter
+ def sourceMultiplicity(self, multiplicity):
+ self._sourceMultiplicityString = multiplicity
+ self._setMultiplicity(multiplicity, False)
+
+ @property
+ def composition(self):
+ return self._composition
+ @composition.setter
+ def composition(self, composition):
+ if composition:
+ self._aggregation = False
+ self._composition = composition
+
+ @property
+ def stereotypes(self):
+ return self._stereotypesHolder.stereotypes
+
+ @stereotypes.setter
+ def stereotypes(self, elements):
+ self._stereotypesHolder.stereotypes = elements
+
+ def delete(self):
+ if self._isDeleted == True:
+ return
+ if isCMetaclass(self.source):
+ allInstances = self.source.allClasses
+ elif isCStereotype(self.source):
+ allInstances = self.source.allExtendedInstances
+ else:
+ allInstances = self.source.allObjects
+ for instance in allInstances:
+ for link in instance.linkObjects:
+ link.delete()
+ self.source._associations.remove(self)
+ if self.source != self.target:
+ self.target._associations.remove(self)
+ for s in self._stereotypesHolder._stereotypes:
+ s._extended.remove(self)
+ self._stereotypesHolder._stereotypes = []
+ super().delete()
+
+ def _checkMultiplicity(self, object, actualLength, actualOppositeLength, checkTargetMultiplicity):
+ if checkTargetMultiplicity:
+ upper = self._upperMultiplicity
+ lower = self._lowerMultiplicity
+ otherSideLower = self._sourceLowerMultiplicity
+ multiplicityString = self._multiplicityString
+ else:
+ upper = self._sourceUpperMultiplicity
+ lower = self._sourceLowerMultiplicity
+ otherSideLower = self._lowerMultiplicity
+ multiplicityString = self._sourceMultiplicityString
+
+ if (upper != CAssociation.STAR_MULTIPLICITY and actualLength > upper) or actualLength < lower:
+ # if there is actually no link as actualOppositeLength is zero, this is ok, if the otherLower including zero:
+ if not (actualOppositeLength == 0 and otherSideLower == 0):
+ raise CException(f"links of object '{object}' have wrong multiplicity '{actualLength!s}': should be '{multiplicityString!s}'")
+
+ def _evalDescriptor(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
+ sourceStr = descriptor[0:index]
+ targetStr = descriptor[index+length:]
+ regexpWithRoleName = '\s*\[([^\]]+)\]\s*(\S*)\s*'
+ regexpOnlyMultiplicity = '\s*(\S*)\s*'
+
+ m = re.search(regexpWithRoleName, sourceStr)
+ if m != None:
+ self.sourceRoleName = m.group(1)
+ if m.group(2) != '':
+ self.sourceMultiplicity = m.group(2)
+ else:
+ m = re.search(regexpOnlyMultiplicity, sourceStr)
+ self.sourceMultiplicity = m.group(1)
+
+ m = re.search(regexpWithRoleName, targetStr)
+ if m != None:
+ self.roleName = m.group(1)
+ if m.group(2) != '':
+ self.multiplicity = m.group(2)
+ else:
+ m = re.search(regexpOnlyMultiplicity, targetStr)
+ self.multiplicity = m.group(1)
+
+ if aggregation:
+ self.aggregation = True
+ elif composition:
+ self.composition = True
diff --git a/codeableModels/cattribute.py b/codeableModels/cattribute.py
new file mode 100644
index 0000000..977a52a
--- /dev/null
+++ b/codeableModels/cattribute.py
@@ -0,0 +1,98 @@
+from codeableModels.internal.commons import isCNamedElement, checkNamedElementIsNotDeleted, setKeywordArgs, isCClassifier, isCEnum, getAttributeType
+from codeableModels.cexception import CException
+from codeableModels.cenum import CEnum
+
+class CAttribute(object):
+ def __init__(self, **kwargs):
+ self._name = None
+ self._classifier = None
+ self._type = None
+ self._default = None
+ setKeywordArgs(self, ["type", "default"], **kwargs)
+
+ def __str__(self):
+ return self.__repr__()
+ def __repr__(self):
+ return f"CAttribute type = {self._type!s}, default = {self._default!s}"
+
+ def checkAttributeTypeIsNotDeleted(self):
+ if isCNamedElement(self._type):
+ checkNamedElementIsNotDeleted(self._type)
+
+ @property
+ def name(self):
+ return self._name
+
+ @property
+ def classifier(self):
+ return self._classifier
+
+ @property
+ def type(self):
+ self.checkAttributeTypeIsNotDeleted()
+ return self._type
+
+ def __wrongDefaultException(self, default, type):
+ raise CException(f"default value '{default!s}' incompatible with attribute's type '{type!s}'")
+
+ @type.setter
+ def type(self, type):
+ if isCNamedElement(type):
+ checkNamedElementIsNotDeleted(type)
+ if self._default != None:
+ if isCEnum(type):
+ if not self._default in type.values:
+ self.__wrongDefaultException(self._default, type)
+ elif isCClassifier(type):
+ if (not self._default == type) and (not type in type.allSuperclasses):
+ self.__wrongDefaultException(self._default, type)
+ else:
+ if not isinstance(self._default, type):
+ self.__wrongDefaultException(self._default, type)
+ self._type = type
+
+ @property
+ def default(self):
+ self.checkAttributeTypeIsNotDeleted()
+ return self._default
+
+ @default.setter
+ def default(self, default):
+ if default == None:
+ self._default = None
+ return
+
+ self.checkAttributeTypeIsNotDeleted()
+ if self._type != None:
+ if isCEnum(self._type):
+ if not default in self._type.values:
+ self.__wrongDefaultException(default, self._type)
+ elif isCClassifier(self._type):
+ if not default in self._type.objects:
+ self.__wrongDefaultException(default, self._type)
+ elif not isinstance(default, self._type):
+ self.__wrongDefaultException(default, self._type)
+ else:
+ attrType = getAttributeType(default)
+ if attrType == None:
+ raise CException(f"unknown attribute type: '{default!r}'")
+ self.type = attrType
+ self._default = default
+ if self._classifier != None:
+ self._classifier._updateDefaultValuesOfClassifier(self)
+
+ def checkAttributeValueType(self, name, value):
+ attrType = getAttributeType(value)
+ if attrType == None:
+ raise CException(f"value for attribute '{name!s}' is not a known attribute type")
+ if isCClassifier(attrType):
+ if attrType != self._type and (not self._type in attrType.allSuperclasses):
+ raise CException(f"type of object '{value!s}' is not matching type of attribute '{name!s}'")
+ return
+ if attrType != self._type:
+ if (self.type == float and attrType == int):
+ return
+ if not (isCEnum(self._type) and attrType == str):
+ raise CException(f"value type for attribute '{name!s}' does not match attribute type")
+ if isCEnum(self._type) and attrType == str and (not self._type.isLegalValue(value)):
+ raise CException(f"value '{value!s}' is not element of enumeration")
diff --git a/codeableModels/cbundlable.py b/codeableModels/cbundlable.py
new file mode 100644
index 0000000..39ec647
--- /dev/null
+++ b/codeableModels/cbundlable.py
@@ -0,0 +1,122 @@
+from codeableModels.internal.commons import setKeywordArgs, checkNamedElementIsNotDeleted, isCBundle, isCMetaclass, isCClass, isCStereotype, isCBundlable
+from codeableModels.cexception import CException
+from codeableModels.cnamedelement import CNamedElement
+
+class CBundlable(CNamedElement):
+ def __init__(self, name, **kwargs):
+ self._bundles = []
+ super().__init__(name, **kwargs)
+
+ def _initKeywordArgs(self, legalKeywordArgs = None, **kwargs):
+ if legalKeywordArgs == None:
+ legalKeywordArgs = []
+ legalKeywordArgs.append("bundles")
+ super()._initKeywordArgs(legalKeywordArgs, **kwargs)
+
+ @property
+ def bundles(self):
+ return list(self._bundles)
+
+ @bundles.setter
+ def bundles(self, bundles):
+ if bundles == None:
+ bundles = []
+ for b in self._bundles:
+ b.remove(self)
+ self._bundles = []
+ if isCBundle(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 isCBundle(b):
+ raise CException(f"bundles requires a list of bundles or a bundle as input")
+ checkNamedElementIsNotDeleted(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 delete(self):
+ if self._isDeleted == True:
+ return
+ bundlesToDelete = list(self._bundles)
+ for b in bundlesToDelete:
+ b.remove(self)
+ self._bundles = []
+ super().delete()
+
+ def getConnectedElements(self, **kwargs):
+ context = ConnectedElementsContext()
+
+ allowedKeywordArgs = ["addBundles", "processBundles", "stopElementsInclusive", "stopElementsExclusive"]
+ if isCMetaclass(self) or isCBundle(self) or isCStereotype(self):
+ allowedKeywordArgs = ["addStereotypes", "processStereotypes"] + allowedKeywordArgs
+ setKeywordArgs(context, allowedKeywordArgs, **kwargs)
+
+ if self in context.stopElementsExclusive:
+ return []
+ context.elements.append(self)
+ self._computeConnected(context)
+ if context.addBundles == False:
+ context.elements = [elt for elt in context.elements if not isCBundle(elt)]
+ if context.addStereotypes == False:
+ context.elements = [elt for elt in context.elements if not isCStereotype(elt)]
+ return context.elements
+
+ def _appendConnected(self, context, connected):
+ for c in connected:
+ if not c in context.elements:
+ context.elements.append(c)
+ if not c in context._allStopElements:
+ c._computeConnected(context)
+
+ def _computeConnected(self, context):
+ connected = []
+ for bundle in self._bundles:
+ if not bundle in context.stopElementsExclusive:
+ connected.append(bundle)
+ self._appendConnected(context, connected)
+
+class ConnectedElementsContext(object):
+ def __init__(self):
+ self.elements = []
+ self.addBundles = False
+ self.addStereotypes = False
+ self.processBundles = False
+ self.processStereotypes = False
+ self._stopElementsInclusive = []
+ self._stopElementsExclusive = []
+ self._allStopElements = []
+
+ @property
+ def stopElementsInclusive(self):
+ return list(self._stopElementsInclusive)
+
+ @stopElementsInclusive.setter
+ def stopElementsInclusive(self, stopElementsInclusive):
+ if isCBundlable(stopElementsInclusive):
+ stopElementsInclusive = [stopElementsInclusive]
+ if not isinstance(stopElementsInclusive, list):
+ raise CException(f"expected one element or a list of stop elements, but got: '{stopElementsInclusive!s}'")
+ for e in stopElementsInclusive:
+ if not isCBundlable(e):
+ raise CException(f"expected one element or a list of stop elements, but got: '{stopElementsInclusive!s}' with element of wrong type: '{e!s}'")
+ self._stopElementsInclusive = stopElementsInclusive
+ self._allStopElements = self._stopElementsInclusive + self._stopElementsExclusive
+
+ @property
+ def stopElementsExclusive(self):
+ return list(self._stopElementsExclusive)
+
+ @stopElementsExclusive.setter
+ def stopElementsExclusive(self, stopElementsExclusive):
+ if isCBundlable(stopElementsExclusive):
+ stopElementsExclusive = [stopElementsExclusive]
+ if not isinstance(stopElementsExclusive, list):
+ raise CException(f"expected a list of stop elements, but got: '{stopElementsExclusive!s}'")
+ for e in stopElementsExclusive:
+ if not isCBundlable(e):
+ raise CException(f"expected a list of stop elements, but got: '{stopElementsExclusive!s}' with element of wrong type: '{e!s}'")
+ self._stopElementsExclusive = stopElementsExclusive
+ self._allStopElements = self._stopElementsInclusive + self._stopElementsExclusive
\ No newline at end of file
diff --git a/codeableModels/cbundle.py b/codeableModels/cbundle.py
new file mode 100644
index 0000000..9543875
--- /dev/null
+++ b/codeableModels/cbundle.py
@@ -0,0 +1,161 @@
+from codeableModels.cobject import CObject
+from codeableModels.cclassifier import CClassifier
+from codeableModels.cexception import CException
+from codeableModels.cmetaclass import CMetaclass
+from codeableModels.cbundlable import CBundlable
+from codeableModels.cstereotype import CStereotype
+from codeableModels.cclass import CClass
+from codeableModels.cenum import CEnum
+from codeableModels.internal.commons import isCNamedElement, setKeywordArgs, checkNamedElementIsNotDeleted
+
+class CBundle(CBundlable):
+ def __init__(self, name=None, **kwargs):
+ self._elements = []
+ super().__init__(name, **kwargs)
+
+ def _initKeywordArgs(self, legalKeywordArgs = None, **kwargs):
+ if legalKeywordArgs == None:
+ legalKeywordArgs = []
+ legalKeywordArgs.append("elements")
+ super()._initKeywordArgs(legalKeywordArgs, **kwargs)
+
+ def add(self, elt):
+ if elt != 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 remove(self, element):
+ if (element == None or
+ (not isinstance(element, CBundlable)) or
+ (not self in element.bundles)):
+ raise CException(f"'{element!s}' is not an element of the bundle")
+ self._elements.remove(element)
+ element._bundles.remove(self)
+
+ def delete(self):
+ if self._isDeleted == True:
+ return
+ elementsToDelete = list(self._elements)
+ for e in elementsToDelete:
+ e._bundles.remove(self)
+ self._elements = []
+ super().delete()
+
+ @property
+ def elements(self):
+ return list(self._elements)
+
+ @elements.setter
+ def elements(self, elements):
+ if elements == None:
+ elements = []
+ for e in self._elements:
+ e._bundle = None
+ self._elements = []
+ if isCNamedElement(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 != None:
+ checkNamedElementIsNotDeleted(e)
+ else:
+ raise CException(f"'None' cannot be an element of bundle")
+ isCNamedElement(e)
+ if e in self._elements:
+ raise CException(f"'{e.name!s}' is already an element of bundle '{self.name!s}'")
+ self._elements.append(e)
+ e._bundles.append(self)
+
+ def getElements(self, **kwargs):
+ type = None
+ name = None
+ # use this as name can also be provided as None
+ nameSpecified = False
+ for key in kwargs:
+ if key == "type":
+ type = kwargs["type"]
+ elif key == "name":
+ name = kwargs["name"]
+ nameSpecified = True
+ else:
+ raise CException(f"unknown argument to getElements: '{key!s}'")
+ elements = []
+ for elt in self._elements:
+ append = True
+ if nameSpecified and elt.name != name:
+ append = False
+ if type != None and not isinstance(elt, type):
+ append = False
+ if append:
+ elements.append(elt)
+ return elements
+
+ def getElement(self, **kwargs):
+ l = self.getElements(**kwargs)
+ return None if len(l) == 0 else l[0]
+
+ def _computeConnected(self, context):
+ super()._computeConnected(context)
+ if context.processBundles == False:
+ return
+ connected = []
+ for element in self._elements:
+ if not element in context.stopElementsExclusive:
+ connected.append(element)
+ self._appendConnected(context, connected)
+
+class CPackage(CBundle):
+ pass
+
+class CLayer(CBundle):
+ def __init__(self, name=None, **kwargs):
+ self._subLayer = None
+ self._superLayer = None
+ super().__init__(name, **kwargs)
+
+ def _initKeywordArgs(self, legalKeywordArgs = None, **kwargs):
+ if legalKeywordArgs == None:
+ legalKeywordArgs = []
+ legalKeywordArgs.append("subLayer")
+ legalKeywordArgs.append("superLayer")
+ super()._initKeywordArgs(legalKeywordArgs, **kwargs)
+
+ @property
+ def subLayer(self):
+ return self._subLayer
+
+ @subLayer.setter
+ def subLayer(self, layer):
+ if layer != None and not isinstance(layer, CLayer):
+ raise CException(f"not a layer: {layer!s}")
+ if self._subLayer != None:
+ self._subLayer._superLayer = None
+ self._subLayer = layer
+ if layer != None:
+ if layer._superLayer != None:
+ layer._superLayer._subLayer = None
+ layer._superLayer = self
+
+ @property
+ def superLayer(self):
+ return self._superLayer
+
+ @superLayer.setter
+ def superLayer(self, layer):
+ if layer != None and not isinstance(layer, CLayer):
+ raise CException(f"not a layer: {layer!s}")
+ if self._superLayer != None:
+ self._superLayer._subLayer = None
+ self._superLayer = layer
+ if layer != None:
+ if layer._subLayer != None:
+ layer._subLayer._superLayer = None
+ layer._subLayer = self
+
+
diff --git a/codeableModels/cclass.py b/codeableModels/cclass.py
new file mode 100644
index 0000000..74e3b65
--- /dev/null
+++ b/codeableModels/cclass.py
@@ -0,0 +1,164 @@
+from codeableModels.cclassifier import CClassifier
+from codeableModels.internal.commons import checkIsCMetaclass, checkIsCObject, checkIsCStereotype, isCStereotype, checkNamedElementIsNotDeleted
+from codeableModels.cexception import CException
+from codeableModels.cobject import CObject
+from codeableModels.internal.taggedvalues import CTaggedValues
+from codeableModels.internal.stereotype_holders import CStereotypeInstancesHolder
+
+class CClass(CClassifier):
+ def __init__(self, metaclass, name=None, **kwargs):
+ self._metaclass = None
+ self.metaclass = metaclass
+ self._objects = []
+ self._classObject = CObject(self.metaclass, name, _classObjectClass = self)
+ self._stereotypeInstancesHolder = CStereotypeInstancesHolder(self)
+ self._taggedValues = CTaggedValues()
+ super().__init__(name, **kwargs)
+
+ def _initKeywordArgs(self, legalKeywordArgs = None, **kwargs):
+ if legalKeywordArgs == None:
+ legalKeywordArgs = []
+ legalKeywordArgs.append("stereotypeInstances")
+ super()._initKeywordArgs(legalKeywordArgs, **kwargs)
+
+ @property
+ def metaclass(self):
+ return self._metaclass
+
+ @property
+ def classObject(self):
+ return self._classObject
+
+ @metaclass.setter
+ def metaclass(self, mcl):
+ checkIsCMetaclass(mcl)
+ if (self._metaclass != None):
+ self._metaclass._removeClass(self)
+ if mcl != None:
+ checkNamedElementIsNotDeleted(mcl)
+ self._metaclass = mcl
+ self._metaclass._addClass(self)
+
+ @property
+ def objects(self):
+ return list(self._objects)
+
+ @property
+ def allObjects(self):
+ allObjects = list(self._objects)
+ for scl in self.allSubclasses:
+ for cl in scl._objects:
+ allObjects.append(cl)
+ return allObjects
+
+ def _addObject(self, obj):
+ if obj in self._objects:
+ raise CException(f"object '{obj!s}' is already an instance of the class '{self!s}'")
+ checkIsCObject(obj)
+ self._objects.append(obj)
+
+ def _removeObject(self, obj):
+ if not obj in self._objects:
+ raise CException(f"can't remove object '{obj!s}'' from class '{self!s}': not an instance")
+ self._objects.remove(obj)
+
+ def delete(self):
+ if self._isDeleted == True:
+ return
+
+ objectsToDelete = list(self._objects)
+ for obj in objectsToDelete:
+ obj.delete()
+ self._objects = []
+
+ for si in self.stereotypeInstances:
+ si._extendedInstances.remove(self)
+ self._stereotypeInstancesHolder._stereotypes = []
+
+ self.metaclass._removeClass(self)
+ self._metaclass = None
+
+ super().delete()
+
+ self._classObject.delete()
+
+
+ @property
+ def classPath(self):
+ return self._classObject.classPath
+
+ def instanceOf(self, cl):
+ return self._classObject.instanceOf(cl)
+
+ def _updateDefaultValuesOfClassifier(self, attribute = None):
+ for i in self.allObjects:
+ attrItems = self._attributes.items()
+ if attribute != None:
+ attrItems = {attribute._name: attribute}.items()
+ for attrName, attr in attrItems:
+ if attr.default != None:
+ if i.getValue(attrName, self) == None:
+ i.setValue(attrName, attr.default, self)
+
+ def _removeAttributeValuesOfClassifier(self, attributesToKeep):
+ for i in self.allObjects:
+ for attrName in self.attributeNames:
+ if not attrName in attributesToKeep:
+ i._removeValue(attrName, self)
+
+ def getValue(self, attributeName, cl = None):
+ return self._classObject.getValue(attributeName, cl)
+
+ def setValue(self, attributeName, value, cl = None):
+ return self._classObject.setValue(attributeName, value, cl)
+
+ def _removeValue(self, attributeName, cl):
+ return self._classObject._removeValue(attributeName, cl)
+
+ def getObjects(self, name):
+ return list(o for o in self.objects if o.name == name)
+ def getObject(self, name):
+ l = self.getObjects(name)
+ return None if len(l) == 0 else l[0]
+
+ @property
+ def stereotypeInstances(self):
+ return self._stereotypeInstancesHolder.stereotypes
+
+ @stereotypeInstances.setter
+ def stereotypeInstances(self, elements):
+ self._stereotypeInstancesHolder.stereotypes = elements
+
+ def getTaggedValue(self, name, stereotype = None):
+ return self._taggedValues.getTaggedValue(name, self._stereotypeInstancesHolder.getStereotypeInstancePath(), stereotype)
+
+ def setTaggedValue(self, name, value, stereotype = None):
+ self._taggedValues.setTaggedValue(name, value, self._stereotypeInstancesHolder.getStereotypeInstancePath(), stereotype)
+
+ def _removeTaggedValue(self, attributeName, stereotype):
+ self._taggedValues.removeTaggedValue(attributeName, stereotype)
+
+ def association(self, target, descriptor = None, **kwargs):
+ 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)
+
+ @property
+ def linkObjects(self):
+ return self._classObject.linkObjects
+
+ @property
+ def links(self):
+ return self._classObject.links
+
+ def getLinks(self, **kwargs):
+ return self._classObject.getLinks(**kwargs)
+
+ def _getLinksForAssociation(self, association):
+ return self._classObject._getLinksForAssociation(association)
+
+ def addLinks(self, links, **kwargs):
+ return self._classObject.addLinks(links, **kwargs)
+
+ def deleteLinks(self, links, **kwargs):
+ return self._classObject.deleteLinks(links, **kwargs)
\ No newline at end of file
diff --git a/codeableModels/cclassifier.py b/codeableModels/cclassifier.py
new file mode 100644
index 0000000..0416711
--- /dev/null
+++ b/codeableModels/cclassifier.py
@@ -0,0 +1,206 @@
+from codeableModels.cbundlable import CBundlable
+from codeableModels.cenum import CEnum
+from codeableModels.cattribute import CAttribute
+from codeableModels.cassociation import CAssociation
+from codeableModels.cexception import CException
+from codeableModels.internal.commons import setKeywordArgs, isKnownAttributeType, isCAttribute, isCClassifier, checkNamedElementIsNotDeleted
+
+class CClassifier(CBundlable):
+ def __init__(self, name=None, **kwargs):
+ self._superclasses = []
+ self._subclasses = []
+ self._attributes = {}
+ self._associations = []
+ super().__init__(name, **kwargs)
+
+ def _initKeywordArgs(self, legalKeywordArgs = None, **kwargs):
+ if legalKeywordArgs == None:
+ legalKeywordArgs = []
+ legalKeywordArgs.append("attributes")
+ legalKeywordArgs.append("superclasses")
+ super()._initKeywordArgs(legalKeywordArgs, **kwargs)
+
+ @property
+ def attributes(self):
+ return list(self._attributes.values())
+
+ def _setAttribute(self, name, value):
+ if name in self._attributes.keys():
+ raise CException(f"duplicate attribute name: '{name!s}'")
+ attr = None
+ if isCAttribute(value):
+ attr = value
+ elif isKnownAttributeType(value) or isinstance(value, CEnum) or isinstance(value, CClassifier):
+ attr = CAttribute(type = value)
+ else:
+ attr = CAttribute(default = value)
+ attr._name = name
+ attr._classifier = self
+ self._attributes.update({name: attr})
+
+ @attributes.setter
+ def attributes(self, attributeDescriptions):
+ if attributeDescriptions == None:
+ attributeDescriptions = {}
+ self._removeAttributeValuesOfClassifier(attributeDescriptions.keys())
+ self._attributes = {}
+ if not isinstance(attributeDescriptions, dict):
+ raise CException(f"malformed attribute description: '{attributeDescriptions!s}'")
+ for attributeName in attributeDescriptions:
+ self._setAttribute(attributeName, attributeDescriptions[attributeName])
+ self._updateDefaultValuesOfClassifier()
+
+ def _removeAttributeValuesOfClassifier(self, attributesToKeep):
+ raise CException("should be overridden by subclasses to update defaults on instances")
+ def _updateDefaultValuesOfClassifier(self, attribute = None):
+ raise CException("should be overridden by subclasses to update defaults on instances")
+
+ def _checkSameTypeAsSelf(self, cl):
+ return isinstance(cl, self.__class__)
+
+ @property
+ def subclasses(self):
+ return list(self._subclasses)
+
+ @property
+ def superclasses(self):
+ return list(self._superclasses)
+
+ @superclasses.setter
+ def superclasses(self, elements):
+ if elements == None:
+ elements = []
+ for sc in self._superclasses:
+ sc._subclasses.remove(self)
+ self._superclasses = []
+ if isCClassifier(elements):
+ elements = [elements]
+ for scl in elements:
+ if scl != None:
+ checkNamedElementIsNotDeleted(scl)
+ if not isinstance(scl, self.__class__):
+ raise CException(f"cannot add superclass '{scl!s}' to '{self!s}': not of type {self.__class__!s}")
+ if scl in self._superclasses:
+ raise CException(f"'{scl.name!s}' is already a superclass of '{self.name!s}'")
+ self._superclasses.append(scl)
+ scl._subclasses.append(self)
+
+ @property
+ def allSuperclasses(self):
+ return self._getAllSuperclasses()
+
+ @property
+ def allSubclasses(self):
+ return self._getAllSubclasses()
+
+ def conformsToType(self, classifier):
+ typeClassifiers = classifier.allSubclasses
+ typeClassifiers.add(classifier)
+ if self in typeClassifiers:
+ return True
+ return False
+
+ @property
+ def attributeNames(self):
+ return list(self._attributes.keys())
+
+ def getAttribute(self, attributeName):
+ if attributeName == None or not isinstance(attributeName, str):
+ return None
+ try:
+ return self._attributes[attributeName]
+ except KeyError:
+ return None
+
+ def _removeAllAssociations(self):
+ associations = self.associations.copy()
+ for association in associations:
+ association.delete()
+
+ def _removeSubclass(self, cl):
+ if not cl in self._subclasses:
+ raise CException(f"can't remove subclass '{cl!s}' from classifier '{self!s}': not a subclass")
+ self._subclasses.remove(cl)
+
+ def _removeSuperclass(self, cl):
+ if not cl in self._superclasses:
+ raise CException(f"can't remove superclass '{cl!s}' from classifier '{self!s}': not a superclass")
+ self._superclasses.remove(cl)
+
+ def delete(self):
+ if self._isDeleted == True:
+ return
+ super().delete()
+ # self.superclasses removes the self subclass from the superclasses
+ self.superclasses = []
+ for subclass in self._subclasses:
+ subclass._removeSuperclass(self)
+ self._subclasses = []
+ self._removeAllAssociations()
+ for a in self.attributes:
+ a._name = None
+ a._classifier = None
+ self._attributes = {}
+
+ def _getAllSuperclasses(self, iteratedClasses = None):
+ if iteratedClasses == None:
+ iteratedClasses = set()
+ result = set()
+ for sc in self.superclasses:
+ if not sc in iteratedClasses:
+ iteratedClasses.add(sc)
+ result.add(sc)
+ result.update(sc._getAllSuperclasses(iteratedClasses))
+ return result
+
+ def _getAllSubclasses(self, iteratedClasses = None):
+ if iteratedClasses == None:
+ iteratedClasses = set()
+ result = set()
+ for sc in self.subclasses:
+ if not sc in iteratedClasses:
+ iteratedClasses.add(sc)
+ result.add(sc)
+ result.update(sc._getAllSubclasses(iteratedClasses))
+ return result
+
+ def hasSubclass(self, cl):
+ return (cl in self._getAllSubclasses())
+
+ def hasSuperclass(self, cl):
+ return (cl in self._getAllSuperclasses())
+
+ @property
+ def associations(self):
+ return list(self._associations)
+
+ @property
+ def allAssociations(self):
+ allAssociations = self.associations
+ for sc in self.allSuperclasses:
+ for a in sc.associations:
+ if not a in allAssociations:
+ allAssociations.extend([a])
+ return allAssociations
+
+ def association(self, target, descriptor = None, **kwargs):
+ a = CAssociation(self, target, descriptor, **kwargs)
+ self._associations.append(a)
+ if self != target:
+ target._associations.append(a)
+ return a
+
+ def _computeConnected(self, context):
+ super()._computeConnected(context)
+ connectedCandidates = []
+ connected = []
+ for association in self.associations:
+ connectedCandidates.append(association._getOppositeClass(self))
+ connectedCandidates = self.superclasses + self.subclasses + connectedCandidates
+ for c in connectedCandidates:
+ if not c in context.stopElementsExclusive:
+ connected.append(c)
+ self._appendConnected(context, connected)
+
+
+
diff --git a/codeableModels/cenum.py b/codeableModels/cenum.py
new file mode 100644
index 0000000..041be7d
--- /dev/null
+++ b/codeableModels/cenum.py
@@ -0,0 +1,37 @@
+from codeableModels.cbundlable import CBundlable
+from codeableModels.internal.commons import setKeywordArgs
+from codeableModels.cexception import CException
+
+class CEnum(CBundlable):
+ def __init__(self, name = None, **kwargs):
+ self._values = []
+ super().__init__(name, **kwargs)
+
+ def _initKeywordArgs(self, legalKeywordArgs = None, **kwargs):
+ if legalKeywordArgs == None:
+ legalKeywordArgs = []
+ legalKeywordArgs.append("values")
+ super()._initKeywordArgs(legalKeywordArgs, **kwargs)
+
+ @property
+ def values(self):
+ return list(self._values)
+
+ @values.setter
+ def values(self, values):
+ if values == 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 isLegalValue(self, value):
+ if value in self._values:
+ return True
+ return False
+
+ def delete(self):
+ if self._isDeleted == True:
+ return
+ self._values = []
+ super().delete()
\ No newline at end of file
diff --git a/codeableModels/cexception.py b/codeableModels/cexception.py
new file mode 100644
index 0000000..87dcdff
--- /dev/null
+++ b/codeableModels/cexception.py
@@ -0,0 +1,7 @@
+class CException(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return self.__repr__()
+ def __repr__(self):
+ return repr(self.value)
\ No newline at end of file
diff --git a/codeableModels/clink.py b/codeableModels/clink.py
new file mode 100644
index 0000000..44c6f53
--- /dev/null
+++ b/codeableModels/clink.py
@@ -0,0 +1,315 @@
+from codeableModels.internal.commons import setKeywordArgs, isCObject, isCClass, getCommonMetaclass, getCommonClassifier, checkIsCAssociation, checkIsCObject
+from codeableModels.cexception import CException
+from codeableModels.internal.taggedvalues import CTaggedValues
+from codeableModels.internal.stereotype_holders import CStereotypeInstancesHolder
+
+class CLink(object):
+ def __init__(self, association, sourceObject, targetObject, **kwargs):
+ self._isDeleted = False
+ self._source = sourceObject
+ self._target = targetObject
+ self.association = association
+ self._stereotypeInstancesHolder = CStereotypeInstancesHolder(self)
+ self._taggedValues = CTaggedValues()
+ super().__init__()
+ self._initKeywordArgs(**kwargs)
+
+ def _initKeywordArgs(self, legalKeywordArgs = None, **kwargs):
+ if legalKeywordArgs == None:
+ legalKeywordArgs = []
+ legalKeywordArgs.append("stereotypeInstances")
+ setKeywordArgs(self, legalKeywordArgs, **kwargs)
+
+ def _getOppositeObject(self, object):
+ if object == self._source:
+ return self._target
+ else:
+ return self._source
+
+ @property
+ def roleName(self):
+ return self.association.roleName
+
+ @property
+ def sourceRoleName(self):
+ return self.association.sourceRoleName
+
+ @property
+ def source(self):
+ if self._source._classObjectClass != None:
+ return self._source._classObjectClass
+ return self._source
+
+ @property
+ def target(self):
+ if self._target._classObjectClass != None:
+ return self._target._classObjectClass
+ return self._target
+
+ def delete(self):
+ if self._isDeleted == True:
+ return
+ self._isDeleted = True
+ for si in self.stereotypeInstances:
+ si._extendedInstances.remove(self)
+ self._stereotypeInstancesHolder._stereotypes = []
+ if self._source != self._target:
+ self._target._linkObjects.remove(self)
+ self._source._linkObjects.remove(self)
+
+ @property
+ def stereotypeInstances(self):
+ return self._stereotypeInstancesHolder.stereotypes
+
+ @stereotypeInstances.setter
+ def stereotypeInstances(self, elements):
+ self._stereotypeInstancesHolder.stereotypes = elements
+
+ def getTaggedValue(self, name, stereotype = None):
+ return self._taggedValues.getTaggedValue(name, self._stereotypeInstancesHolder.getStereotypeInstancePath(), stereotype)
+
+ def setTaggedValue(self, name, value, stereotype = None):
+ self._taggedValues.setTaggedValue(name, value, self._stereotypeInstancesHolder.getStereotypeInstancePath(), stereotype)
+
+ def _removeTaggedValue(self, attributeName, stereotype):
+ self._taggedValues.removeTaggedValue(attributeName, stereotype)
+
+def _getTargetObjectsFromDefinition(targets, isClassLinks):
+ if targets == None:
+ targets = []
+ elif not isinstance(targets, list):
+ targets = [targets]
+ newTargets = []
+ for t in targets:
+ if isCClass(t):
+ if not isClassLinks:
+ raise CException(f"link target '{t!s}' is a class, but source is an object")
+ newTargets.append(t._classObject)
+ elif isCObject(t):
+ if isClassLinks and t._classObjectClass == None:
+ raise CException(f"link target '{t!s}' is an object, but source is an class")
+ if not isClassLinks and t._classObjectClass != None:
+ raise CException(f"link target '{t!s}' is an class, but source is an object")
+ newTargets.append(t)
+ else:
+ raise CException(f"link target '{t!s}' is neither an object nor a class")
+ return newTargets
+
+def _checkLinkDefinitionAndReplaceClasses(linkDefinitions):
+ if not isinstance(linkDefinitions, dict):
+ raise CException("link definitions should be of the form {: , ..., : }")
+
+ newDefs = {}
+ for source in linkDefinitions:
+ sourceObj = source
+ if source == None or source == []:
+ raise CException("link should not contain an empty source")
+ if isCClass(source):
+ sourceObj = source._classObject
+ elif not isCObject(source):
+ raise CException(f"link source '{source!s}' is neither an object nor a class")
+ targets = _getTargetObjectsFromDefinition(linkDefinitions[source], sourceObj._classObjectClass != None)
+ newDefs[sourceObj] = targets
+
+ return newDefs
+
+def _determineMatchingAssociationAndSetContextInfo(context, source, targets):
+ if source._classObjectClass != None:
+ context.targetClassifier = getCommonMetaclass([co._classObjectClass for co in targets])
+ context.sourceClassifier = source._classObjectClass.metaclass
+ else:
+ context.targetClassifier = getCommonClassifier(targets)
+ context.sourceClassifier = source.classifier
+
+ if context.association != None and context.targetClassifier == None:
+ if context.sourceClassifier.conformsToType(context.association.source):
+ context.targetClassifier = context.association.target
+ context.sourceClassifier = context.association.source
+ elif context.sourceClassifier.conformsToType(context.association.target):
+ context.targetClassifier = context.association.source
+ context.sourceClassifier = context.association.target
+
+ associations = context.sourceClassifier.allAssociations
+ if context.association != None:
+ associations = [context.association]
+ matchesAssociationOrder = []
+ matchesReverseAssociationOrder = []
+ for association in associations:
+ if (association._matchesTarget(context.targetClassifier, context.roleName) and
+ association._matchesSource(context.sourceClassifier, None)):
+ matchesAssociationOrder.append(association)
+ elif (association._matchesSource(context.targetClassifier, context.roleName) and
+ association._matchesTarget(context.sourceClassifier, None)):
+ matchesReverseAssociationOrder.append(association)
+ matches = len(matchesAssociationOrder) + len(matchesReverseAssociationOrder)
+ if matches == 1:
+ if len(matchesAssociationOrder) == 1:
+ context.association = matchesAssociationOrder[0]
+ context.matchesInOrder[source] = True
+ else:
+ context.association = matchesReverseAssociationOrder[0]
+ context.matchesInOrder[source] = False
+ elif matches == 0:
+ raise CException(f"matching association not found for source '{source!s}' and targets '{[str(item) for item in targets]!s}'")
+ else:
+ raise CException(f"link specification ambiguous, multiple matching associations found for source '{source!s}' and targets '{[str(item) for item in targets]!s}'")
+
+
+def _linkObjects(context, source, targets):
+ newLinks = []
+ sourceObj = source
+ if isCClass(source):
+ sourceObj = source._classObject
+ for t in targets:
+ target = t
+ if isCClass(t):
+ target = t._classObject
+
+ sourceForLink = sourceObj
+ targetForLink = target
+ if not context.matchesInOrder[sourceObj]:
+ sourceForLink = target
+ targetForLink = sourceObj
+ for existingLink in sourceObj._linkObjects:
+ if (existingLink._source == sourceForLink and existingLink._target == targetForLink
+ and existingLink.association == context.association):
+ for link in newLinks:
+ link.delete()
+ raise CException(f"trying to link the same link twice '{source!s} -> {target!s}'' twice for the same association")
+ link = CLink(context.association, sourceForLink, targetForLink)
+
+ newLinks.append(link)
+ sourceObj._linkObjects.append(link)
+ # for links from this object to itself, store only one link object
+ if sourceObj != target:
+ target._linkObjects.append(link)
+ if context.stereotypeInstances != None:
+ link.stereotypeInstances = context.stereotypeInstances
+ return newLinks
+
+def _removeLinksForAssociations(context, source, targets):
+ if not source in context.objectLinksHaveBeenRemoved:
+ context.objectLinksHaveBeenRemoved.append(source)
+ for link in source._getLinksForAssociation(context.association):
+ link.delete()
+ for target in targets:
+ if not target in context.objectLinksHaveBeenRemoved:
+ context.objectLinksHaveBeenRemoved.append(target)
+ for link in target._getLinksForAssociation(context.association):
+ link.delete()
+
+
+def setLinks(linkDefinitions, addLinks = False, **kwargs):
+ context = LinkKeywordsContext(**kwargs)
+ linkDefinitions = _checkLinkDefinitionAndReplaceClasses(linkDefinitions)
+
+ newLinks = []
+ for source in linkDefinitions:
+ targets = linkDefinitions[source]
+ _determineMatchingAssociationAndSetContextInfo(context, source, targets)
+ if not addLinks:
+ _removeLinksForAssociations(context, source, targets)
+ try:
+ newLinks.extend(_linkObjects(context, source, targets))
+ except CException as e:
+ for link in newLinks:
+ link.delete()
+ raise e
+ try:
+ for source in linkDefinitions:
+ targets = linkDefinitions[source]
+ sourceLen = len(source._getLinksForAssociation(context.association))
+ if len(targets) == 0:
+ context.association._checkMultiplicity(source, sourceLen, 0, context.matchesInOrder[source])
+ else:
+ for target in targets:
+ targetLen = len(target._getLinksForAssociation(context.association))
+ context.association._checkMultiplicity(source, sourceLen, targetLen, context.matchesInOrder[source])
+ context.association._checkMultiplicity(target, targetLen, sourceLen, not context.matchesInOrder[source])
+ except CException as e:
+ for link in newLinks:
+ link.delete()
+ raise e
+ return newLinks
+
+def addLinks(linkDefinitions, **kwargs):
+ return setLinks(linkDefinitions, True, **kwargs)
+
+def deleteLinks(linkDefinitions, **kwargs):
+ # stereotypeInstances is not supported for delete links
+ if "stereotypeInstances" in kwargs:
+ raise CException(f"unknown keywords argument")
+
+ context = LinkKeywordsContext(**kwargs)
+ linkDefinitions = _checkLinkDefinitionAndReplaceClasses(linkDefinitions)
+
+ for source in linkDefinitions:
+ targets = linkDefinitions[source]
+ #_determineMatchingAssociationAndSetContextInfo(context, source, targets)
+
+ for target in targets:
+ matchingLink = None
+ for link in source._linkObjects:
+ if context.association != None and link.association != context.association:
+ continue
+ # if ((context.matchesInOrder[source] and source == link._source and target == link._target) or
+ # (not context.matchesInOrder[source] and target == link._source and source == link._target)):
+ matchesInOrder = True
+ matches = False
+ if (source == link._source and target == link._target):
+ matches = True
+ if context.roleName != None and not link.association.roleName == context.roleName:
+ matches = False
+ if (target == link._source and source == link._target):
+ matches = True
+ matchesInOrder = False
+ if context.roleName != None and not link.association.sourceRoleName == context.roleName:
+ matches = False
+ if matches:
+ if matchingLink == None:
+ matchingLink = link
+ else:
+ raise CException(f"link definition in delete links ambiguous for link '{source!s}->{target!s}': found multiple matches")
+ if matchingLink == None:
+ roleNameString = ""
+ if context.roleName != None:
+ roleNameString = f" for given role name '{context.roleName!s}'"
+ associationString = ""
+ if context.association != None:
+ associationString = f" for given association"
+ if roleNameString != "":
+ associationString = " and" + associationString
+ raise CException(f"no link found for '{source!s} -> {target!s}' in delete links" + roleNameString + associationString)
+ else:
+ sourceLen = len(source._getLinksForAssociation(matchingLink.association)) - 1
+ targetLen = len(target._getLinksForAssociation(matchingLink.association)) - 1
+ matchingLink.association._checkMultiplicity(source, sourceLen, targetLen, matchesInOrder)
+ matchingLink.association._checkMultiplicity(target, targetLen, sourceLen, not matchesInOrder)
+ matchingLink.delete()
+
+ def delete(self):
+ if self._isDeleted == True:
+ return
+ self._isDeleted = True
+ for si in self.stereotypeInstances:
+ si._extendedInstances.remove(self)
+ self._stereotypeInstancesHolder._stereotypes = []
+ if self._source != self._target:
+ self._target._linkObjects.remove(self)
+ self._source._linkObjects.remove(self)
+
+
+
+class LinkKeywordsContext(object):
+ def __init__(self, **kwargs):
+ self.roleName = kwargs.pop("roleName", None)
+ self.association = kwargs.pop("association", None)
+ self.stereotypeInstances = kwargs.pop("stereotypeInstances", None)
+ if len(kwargs) != 0:
+ raise CException(f"unknown keywords argument")
+ if self.association != None:
+ checkIsCAssociation(self.association)
+ self.sourceClassifier = None
+ self.targetClassifier = None
+ self.matchesInOrder = {}
+ self.objectLinksHaveBeenRemoved = []
diff --git a/codeableModels/cmetaclass.py b/codeableModels/cmetaclass.py
new file mode 100644
index 0000000..49c96f2
--- /dev/null
+++ b/codeableModels/cmetaclass.py
@@ -0,0 +1,100 @@
+from codeableModels.cclassifier import CClassifier
+from codeableModels.cexception import CException
+from codeableModels.internal.commons import checkIsCClass, setKeywordArgs, checkIsCStereotype, isCStereotype, checkNamedElementIsNotDeleted
+from codeableModels.internal.stereotype_holders import CStereotypesHolder
+
+class CMetaclass(CClassifier):
+ def __init__(self, name=None, **kwargs):
+ self._classes = []
+ self._stereotypesHolder = CStereotypesHolder(self)
+ super().__init__(name, **kwargs)
+
+ def _initKeywordArgs(self, legalKeywordArgs = None, **kwargs):
+ if legalKeywordArgs == None:
+ legalKeywordArgs = []
+ legalKeywordArgs.append("stereotypes")
+ super()._initKeywordArgs(legalKeywordArgs, **kwargs)
+
+ @property
+ def classes(self):
+ return list(self._classes)
+
+ @property
+ def allClasses(self):
+ allClasses = list(self._classes)
+ for scl in self.allSubclasses:
+ for cl in scl._classes:
+ allClasses.append(cl)
+ return allClasses
+
+ def getClasses(self, name):
+ return list(cl for cl in self.classes if cl.name == name)
+ def getClass(self, name):
+ l = self.getClasses(name)
+ return None if len(l) == 0 else l[0]
+
+ def getStereotypes(self, name):
+ return list(cl for cl in self.stereotypes if cl.name == name)
+ def getStereotype(self, name):
+ l = self.getStereotypes(name)
+ return None if len(l) == 0 else l[0]
+
+ def _addClass(self, cl):
+ checkIsCClass(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 _removeClass(self, cl):
+ if not cl 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 delete(self):
+ if self._isDeleted == True:
+ return
+ classesToDelete = list(self._classes)
+ for cl in classesToDelete:
+ cl.delete()
+ self._classes = []
+ for s in self._stereotypesHolder._stereotypes:
+ s._extended.remove(self)
+ self._stereotypesHolder._stereotypes = []
+ super().delete()
+
+ @property
+ def stereotypes(self):
+ return self._stereotypesHolder.stereotypes
+
+ @stereotypes.setter
+ def stereotypes(self, elements):
+ self._stereotypesHolder.stereotypes = elements
+
+ def _updateDefaultValuesOfClassifier(self, attribute = None):
+ for i in self.allClasses:
+ attrItems = self._attributes.items()
+ if attribute != None:
+ attrItems = {attribute._name: attribute}.items()
+ for attrName, attr in attrItems:
+ if attr.default != None:
+ if i.getValue(attrName, self) == None:
+ i.setValue(attrName, attr.default, self)
+
+ def _removeAttributeValuesOfClassifier(self, attributesToKeep):
+ for i in self.allClasses:
+ for attrName in self.attributeNames:
+ if not attrName in attributesToKeep:
+ i._removeValue(attrName, self)
+
+ def association(self, target, descriptor = None, **kwargs):
+ if not isinstance(target, CMetaclass):
+ raise CException(f"metaclass '{self!s}' is not compatible with association target '{target!s}'")
+ return super(CMetaclass, self).association(target, descriptor, **kwargs)
+
+ def _computeConnected(self, context):
+ super()._computeConnected(context)
+ connected = []
+ for s in self.stereotypes:
+ if not s in context.stopElementsExclusive:
+ connected.append(s)
+ self._appendConnected(context, connected)
\ No newline at end of file
diff --git a/codeableModels/cnamedelement.py b/codeableModels/cnamedelement.py
new file mode 100644
index 0000000..4b9e341
--- /dev/null
+++ b/codeableModels/cnamedelement.py
@@ -0,0 +1,37 @@
+from codeableModels.internal.commons import setKeywordArgs, checkNamedElementIsNotDeleted
+from codeableModels.cexception import CException
+
+class CNamedElement(object):
+ def __init__(self, name, **kwargs):
+ self.name = name
+ super().__init__()
+ self._isDeleted = False
+ if name != None and not isinstance(name, str):
+ raise CException(f"is not a name string: '{name!r}'")
+ self._initKeywordArgs(**kwargs)
+
+ def __str__(self):
+ if (self.name == None):
+ return ""
+ return self.name
+ def __repr__(self):
+ result = super().__repr__()
+ if (self.name != None):
+ result = f"{result}: {self.name!s}"
+ return result
+
+ def _initKeywordArgs(self, legalKeywordArgs = None, **kwargs):
+ if legalKeywordArgs == None:
+ legalKeywordArgs = []
+ setKeywordArgs(self, legalKeywordArgs, **kwargs)
+
+ def delete(self):
+ if self._isDeleted == True:
+ return
+ self.name = None
+ self._isDeleted = True
+
+
+
+
+
diff --git a/codeableModels/cobject.py b/codeableModels/cobject.py
new file mode 100644
index 0000000..82c0983
--- /dev/null
+++ b/codeableModels/cobject.py
@@ -0,0 +1,215 @@
+from codeableModels.cbundlable import CBundlable
+from codeableModels.cmetaclass import CMetaclass
+from codeableModels.cexception import CException
+from codeableModels.clink import CLink, LinkKeywordsContext, addLinks, deleteLinks
+from codeableModels.internal.commons import (checkIsCClass, isCClass, setKeywordArgs, isCMetaclass,
+ checkNamedElementIsNotDeleted, getCommonClassifier, checkIsCommonClassifier, checkIsCAssociation,
+ checkIsCClassifier, getCommonMetaclass)
+
+class CObject(CBundlable):
+ def __init__(self, cl, name=None, **kwargs):
+ self._classObjectClass = None
+ if '_classObjectClass' in kwargs:
+ classObjectClass = kwargs.pop('_classObjectClass', None)
+ self._classObjectClass = classObjectClass
+ else:
+ # don't check if this is a class object, as classifier is then a metaclass
+ checkIsCClass(cl)
+ if cl != None:
+ checkNamedElementIsNotDeleted(cl)
+ super().__init__(name, **kwargs)
+ self._classifier = cl
+ if self._classObjectClass == None:
+ # don't add instance if this is a class object
+ self._classifier._addObject(self)
+ self._initAttributeValues()
+ self._linkObjects = []
+
+ def _initAttributeValues(self):
+ self._attributeValues = {}
+ # init default values of attributes
+ for cl in self.classPath:
+ for attrName, attr in cl._attributes.items():
+ if attr.default != None:
+ self.setValue(attrName, attr.default, cl)
+
+ @property
+ def classifier(self):
+ return self._classifier
+
+ @classifier.setter
+ def classifier(self, cl):
+ checkIsCClass(cl)
+ if (self._classifier != None):
+ self._classifier._removeObject(self)
+ if cl != None:
+ checkNamedElementIsNotDeleted(cl)
+ self._classifier = cl
+ self._classifier._addObject(self)
+
+ def _getClassPath(self, classifier = None):
+ if classifier == None:
+ classifier = self.classifier
+ classPath = [classifier]
+ for sc in classifier.superclasses:
+ for cl in self._getClassPath(sc):
+ if not cl in classPath:
+ classPath.append(cl)
+ return classPath
+
+ @property
+ def classPath(self):
+ return self._getClassPath()
+
+ def delete(self):
+ if self._isDeleted == True:
+ return
+ if not isinstance(self.classifier, CMetaclass):
+ # for class objects, the class cleanup removes the instance
+ self._classifier._removeObject(self)
+ self._classifier = None
+ super().delete()
+
+ def instanceOf(self, cl):
+ if self.classifier == None:
+ return False
+ if cl == None:
+ raise CException(f"'None' is not a valid argument")
+ if isinstance(self.classifier, CMetaclass):
+ # this is a class object
+ if not isCMetaclass(cl):
+ raise CException(f"'{cl!s}' is not a metaclass")
+ else:
+ if not isCClass(cl):
+ raise CException(f"'{cl!s}' is not a class")
+
+ if self.classifier == cl:
+ return True
+ if cl in self.classifier.allSuperclasses:
+ return True
+ return False
+
+ def _checkIsAttributeOfOwnClassifier(self, attributeName, classifier):
+ attribute = classifier.getAttribute(attributeName)
+ if attribute == None:
+ raise CException(f"attribute '{attributeName!s}' unknown for '{classifier!s}'")
+ if not classifier in self.classPath:
+ raise CException(f"'{classifier!s}' is not a classifier of '{self!s}'")
+ return attribute
+
+ def getValue(self, attributeName, classifier = None):
+ if self._isDeleted:
+ raise CException(f"can't get value '{attributeName!s}' on deleted object")
+ if classifier == None:
+ # search on class path
+ for cl in self.classPath:
+ if cl.getAttribute(attributeName) != None:
+ return self.getValue(attributeName, cl)
+ raise CException(f"attribute '{attributeName!s}' unknown for object '{self!s}'")
+ else:
+ # search only on specified classifier
+ attribute = self._checkIsAttributeOfOwnClassifier(attributeName, classifier)
+ attribute.checkAttributeTypeIsNotDeleted()
+ try:
+ attributeValuesClassifier = self._attributeValues[classifier]
+ except KeyError:
+ return None
+ try:
+ return attributeValuesClassifier[attributeName]
+ except KeyError:
+ return None
+
+ def setValue(self, attributeName, value, classifier = None):
+ if self._isDeleted:
+ raise CException(f"can't set value '{attributeName!s}' on deleted object")
+ if classifier == None:
+ # search on class path
+ for cl in self.classPath:
+ if cl.getAttribute(attributeName) != None:
+ return self.setValue(attributeName, value, cl)
+ raise CException(f"attribute '{attributeName!s}' unknown for object '{self!s}'")
+ else:
+ # search only on specified classifier
+ attribute = self._checkIsAttributeOfOwnClassifier(attributeName, classifier)
+ attribute.checkAttributeTypeIsNotDeleted()
+ attribute.checkAttributeValueType(attributeName, value)
+ try:
+ self._attributeValues[classifier].update({attributeName: value})
+ except KeyError:
+ self._attributeValues[classifier] = {attributeName: value}
+
+ def _removeValue(self, attributeName, classifier):
+ try:
+ self._attributeValues[classifier].pop(attributeName, None)
+ except KeyError:
+ return
+
+ @property
+ def linkObjects(self):
+ return list(self._linkObjects)
+
+ @property
+ def links(self):
+ result = []
+ for link in self._linkObjects:
+ if self._classObjectClass != None:
+ result.append(link._getOppositeObject(self)._classObjectClass)
+ else:
+ result.append(link._getOppositeObject(self))
+ return result
+
+ def _getLinksForAssociation(self, association):
+ associationLinks = []
+ for link in list(self._linkObjects):
+ if link.association == association:
+ associationLinks.extend([link])
+ return associationLinks
+
+ def _removeLinksForAssociations(self, association, matchesinAssociationsDirection, targets):
+ for link in self._getLinksForAssociation(association):
+ link.delete()
+ for t in targets:
+ for link in t._getLinksForAssociation(association):
+ link.delete()
+
+ def getLinks(self, **kwargs):
+ context = LinkKeywordsContext(**kwargs)
+
+ linkedObjs = []
+ for l in self._linkObjects:
+ append = True
+ if context.association != None:
+ if l.association != context.association:
+ append = False
+ if context.roleName != None:
+ if ((self == l._source and not l.association.roleName == context.roleName) or
+ (self == l._target and not l.association.sourceRoleName == context.roleName)):
+ append = False
+ if append:
+ if self == l._source:
+ if self._classObjectClass != None:
+ linkedObjs.append(l._target._classObjectClass)
+ else:
+ linkedObjs.append(l._target)
+ else:
+ if self._classObjectClass != None:
+ linkedObjs.append(l._source._classObjectClass)
+ else:
+ linkedObjs.append(l._source)
+ return linkedObjs
+
+ def addLinks(self, links, **kwargs):
+ return addLinks({self: links}, **kwargs)
+
+ def deleteLinks(self, links, **kwargs):
+ return deleteLinks({self: links}, **kwargs)
+
+ def _computeConnected(self, context):
+ super()._computeConnected(context)
+ connected = []
+ for link in self._linkObjects:
+ opposite = link._getOppositeObject(self)
+ if not opposite in context.stopElementsExclusive:
+ connected.append(opposite)
+ self._appendConnected(context, connected)
+
diff --git a/codeableModels/cstereotype.py b/codeableModels/cstereotype.py
new file mode 100644
index 0000000..bdc7dcd
--- /dev/null
+++ b/codeableModels/cstereotype.py
@@ -0,0 +1,142 @@
+from codeableModels.cclassifier import CClassifier
+from codeableModels.cexception import CException
+from codeableModels.cmetaclass import CMetaclass
+from codeableModels.cassociation import CAssociation
+from codeableModels.internal.commons import setKeywordArgs, checkIsCMetaclass, isCClass, isCMetaclass, checkNamedElementIsNotDeleted, isCLink, isCAssociation, checkIsCAssociation
+
+class CStereotype(CClassifier):
+ def __init__(self, name=None, **kwargs):
+ self._extended = []
+ self._extendedInstances = []
+ self._extendedType = None
+ super().__init__(name, **kwargs)
+
+ def _initKeywordArgs(self, legalKeywordArgs = None, **kwargs):
+ if legalKeywordArgs == None:
+ legalKeywordArgs = []
+ legalKeywordArgs.append("extended")
+ super()._initKeywordArgs(legalKeywordArgs, **kwargs)
+
+ def _determineExtendedTypeOfList(self, elements):
+ if len(elements) == 0:
+ return
+ if isCMetaclass(elements[0]):
+ self._extendedType = CMetaclass
+ return
+ if isCAssociation(elements[0]):
+ self._extendedType = CAssociation
+ return
+ raise CException(f"unknown type of extend element: '{elements[0]!s}'")
+
+ @property
+ def extended(self):
+ return list(self._extended)
+
+ @extended.setter
+ def extended(self, elements):
+ if elements == None:
+ elements = []
+ for e in self._extended:
+ e._stereotypesHolder._stereotypes.remove(self)
+ self._extended = []
+ if isCMetaclass(elements):
+ self._extendedType = CMetaclass
+ elements = [elements]
+ elif isCAssociation(elements):
+ self._extendedType = CAssociation
+ elements = [elements]
+ elif not isinstance(elements, list):
+ raise CException(f"extended requires a list or a metaclass as input")
+ else:
+ self._determineExtendedTypeOfList(elements)
+
+ for e in elements:
+ if self._extendedType == CMetaclass:
+ checkIsCMetaclass(e)
+ elif self._extendedType == CAssociation:
+ checkIsCAssociation(e)
+ else:
+ raise CException(f"type of extend element incompatible: '{e!s}'")
+ checkNamedElementIsNotDeleted(e)
+ if e in self._extended:
+ raise CException(f"'{e.name!s}' is already extended by stereotype '{self.name!s}'")
+ self._extended.append(e)
+ e._stereotypesHolder._stereotypes.append(self)
+
+ @property
+ def extendedInstances(self):
+ return list(self._extendedInstances)
+
+ @property
+ def allExtendedInstances(self):
+ allInstances = list(self._extendedInstances)
+ for scl in self.allSubclasses:
+ for cl in scl._extendedInstances:
+ allInstances.append(cl)
+ return allInstances
+
+ def delete(self):
+ if self._isDeleted == True:
+ return
+ for e in self._extended:
+ e._stereotypesHolder._stereotypes.remove(self)
+ self._extended = []
+ super().delete()
+
+ def _updateDefaultValuesOfClassifier(self, attribute = None):
+ allClasses = [self] + list(self.allSubclasses)
+ for sc in allClasses:
+ for i in sc._extendedInstances:
+ attrItems = self._attributes.items()
+ if attribute != None:
+ attrItems = {attribute._name: attribute}.items()
+ for attrName, attr in attrItems:
+ if attr.default != None:
+ if i.getTaggedValue(attrName, self) == None:
+ i.setTaggedValue(attrName, attr.default, self)
+
+ def _removeAttributeValuesOfClassifier(self, attributesToKeep):
+ for i in self._extendedInstances:
+ for attrName in self.attributeNames:
+ if not attrName in attributesToKeep:
+ i._removeTaggedValue(attrName, self)
+
+ def isMetaclassExtendedByThisStereotype(self, metaclass):
+ if metaclass in self._extended:
+ return True
+ for mcSuperclass in metaclass._getAllSuperclasses():
+ if mcSuperclass in self._extended:
+ return True
+ return False
+
+ def isElementExtendedByStereotype(self, element):
+ if isCClass(element):
+ if self.isMetaclassExtendedByThisStereotype(element.metaclass):
+ return True
+ for superclass in self._getAllSuperclasses():
+ if superclass.isMetaclassExtendedByThisStereotype(element.metaclass):
+ return True
+ return False
+ elif isCLink(element):
+ if element.association in self.extended:
+ return True
+ for superclass in self._getAllSuperclasses():
+ if element.association in superclass.extended:
+ return True
+ return False
+ raise CException("element is neither a metaclass nor an association")
+
+ def association(self, target, descriptor = None, **kwargs):
+ if not isinstance(target, CStereotype):
+ raise CException(f"stereotype '{self!s}' is not compatible with association target '{target!s}'")
+ return super(CStereotype, self).association(target, descriptor, **kwargs)
+
+ def _computeConnected(self, context):
+ super()._computeConnected(context)
+ if context.processStereotypes == False:
+ return
+ connected = []
+ for e in self.extended:
+ if not e in context.stopElementsExclusive:
+ connected.append(e)
+ self._appendConnected(context, connected)
\ No newline at end of file
diff --git a/codeableModels/internal/commons.py b/codeableModels/internal/commons.py
new file mode 100644
index 0000000..305fb22
--- /dev/null
+++ b/codeableModels/internal/commons.py
@@ -0,0 +1,188 @@
+from codeableModels.cexception import CException
+
+def setKeywordArgs(object, allowedValues, **kwargs):
+ for key in kwargs:
+ if key in allowedValues:
+ setattr(object, key, kwargs[key])
+ else:
+ raise CException(f"unknown keyword argument '{key!s}', should be one of: {allowedValues!s}")
+
+def getAttributeType(attr):
+ if isinstance(attr, str):
+ return str
+ elif isinstance(attr, bool):
+ return bool
+ elif isinstance(attr, int):
+ return int
+ elif isinstance(attr, float):
+ return float
+ elif isCObject(attr):
+ checkNamedElementIsNotDeleted(attr)
+ return attr.classifier
+ return None
+
+def isKnownAttributeType(type):
+ if isCNamedElement(type):
+ return False
+ return (type == str or type == bool or type == int or type == float)
+
+def isCEnum(elt):
+ from codeableModels.cenum import CEnum
+ if isinstance(elt, CEnum):
+ return True
+ return False
+
+def isCClassifier(elt):
+ from codeableModels.cclassifier import CClassifier
+ if isinstance(elt, CClassifier):
+ return True
+ return False
+
+def isCNamedElement(elt):
+ from codeableModels.cnamedelement import CNamedElement
+ if isinstance(elt, CNamedElement):
+ return True
+ return False
+
+def isCAttribute(elt):
+ from codeableModels.cattribute import CAttribute
+ if isinstance(elt, CAttribute):
+ return True
+ return False
+
+def isCObject(elt):
+ from codeableModels.cobject import CObject
+ if isinstance(elt, CObject):
+ return True
+ return False
+
+def isCClass(elt):
+ from codeableModels.cclass import CClass
+ if isinstance(elt, CClass):
+ return True
+ return False
+
+def isCMetaclass(elt):
+ from codeableModels.cmetaclass import CMetaclass
+ if isinstance(elt, CMetaclass):
+ return True
+ return False
+
+def isCStereotype(elt):
+ from codeableModels.cstereotype import CStereotype
+ if isinstance(elt, CStereotype):
+ return True
+ return False
+
+def isCBundle(elt):
+ from codeableModels.cbundle import CBundle
+ if isinstance(elt, CBundle):
+ return True
+ return False
+
+def isCBundlable(elt):
+ from codeableModels.cbundlable import CBundlable
+ if isinstance(elt, CBundlable):
+ return True
+ return False
+
+def isCAssociation(elt):
+ from codeableModels.cassociation import CAssociation
+ if isinstance(elt, CAssociation):
+ return True
+ return False
+
+def isCLink(elt):
+ from codeableModels.clink import CLink
+ if isinstance(elt, CLink):
+ return True
+ return False
+
+def checkIsCMetaclass(elt):
+ if not isCMetaclass(elt):
+ raise CException(f"'{elt!s}' is not a metaclass")
+
+def checkIsCClassifier(elt):
+ if not isCClassifier(elt):
+ raise CException(f"'{elt!s}' is not a classifier")
+
+def checkIsCClass(elt):
+ if not isCClass(elt):
+ raise CException(f"'{elt!s}' is not a class")
+
+def checkIsCStereotype(elt):
+ if not isCStereotype(elt):
+ raise CException(f"'{elt!s}' is not a stereotype")
+
+def checkIsCObject(elt):
+ if not isCObject(elt):
+ raise CException(f"'{elt!s}' is not an object")
+
+def checkIsCBundle(elt):
+ if not isCBundle(elt):
+ raise CException(f"'{elt!s}' is not a bundle")
+
+def checkIsCAssociation(elt):
+ if not isCAssociation(elt):
+ raise CException(f"'{elt!s}' is not a association")
+
+def checkNamedElementIsNotDeleted(namedElement):
+ if namedElement._isDeleted == True:
+ raise CException(f"cannot access named element that has been deleted")
+
+# get the common (top level) classifier in a list of objects
+def getCommonClassifier(objects):
+ commonClassifier = None
+ for o in objects:
+ if o == None or not isCObject(o):
+ raise CException(f"not an object: '{o!s}'")
+ if commonClassifier == None:
+ commonClassifier = o.classifier
+ else:
+ if commonClassifier == o.classifier:
+ continue
+ if commonClassifier in o.classifier.allSuperclasses:
+ continue
+ if commonClassifier in o.classifier.allSubclasses:
+ commonClassifier = o.classifier
+ continue
+ raise CException(f"object '{o!s}' has an incompatible classifier")
+ return commonClassifier
+
+def checkIsCommonClassifier(classifier, objects):
+ for o in objects:
+ if not o.instanceOf(classifier):
+ raise CException(f"object '{o!s}' not compatible with classifier '{classifier!s}'")
+
+
+# get the common (top level) metaclass in a list of classes
+def getCommonMetaclass(classes):
+ commonMetaclass = None
+ for c in classes:
+ if c == None or not isCClass(c):
+ raise CException(f"not a class: '{c!s}'")
+ if commonMetaclass == None:
+ commonMetaclass = c.metaclass
+ else:
+ if commonMetaclass == c.metaclass:
+ continue
+ if commonMetaclass in c.metaclass.allSuperclasses:
+ continue
+ if commonMetaclass in c.metaclass.allSubclasses:
+ commonMetaclass = c.metaclass
+ continue
+ raise CException(f"class '{c!s}' has an incompatible classifier")
+ return commonMetaclass
+
+
+def getLinkObjects(objList):
+ result = []
+ for o in objList:
+ obj = o
+ if not isCObject(o):
+ if isCClass(o):
+ obj = o.classObject
+ else:
+ raise CException(f"'{o!s}' is not an object")
+ result.extend(obj.linkObjects)
+ return result
\ No newline at end of file
diff --git a/codeableModels/internal/stereotype_holders.py b/codeableModels/internal/stereotype_holders.py
new file mode 100644
index 0000000..0f0cf96
--- /dev/null
+++ b/codeableModels/internal/stereotype_holders.py
@@ -0,0 +1,99 @@
+from codeableModels.cexception import CException
+from codeableModels.internal.commons import checkIsCClass, isCClass, isCLink, setKeywordArgs, checkIsCStereotype, isCStereotype, checkNamedElementIsNotDeleted
+
+class CStereotypesHolder:
+ def __init__(self, element):
+ self._stereotypes = []
+ self.element = element
+
+ @property
+ def stereotypes(self):
+ return list(self._stereotypes)
+
+ # methods to be overridden in subclass
+ def _removeFromStereotype(self):
+ for s in self._stereotypes:
+ s._extended.remove(self.element)
+ def _appendOnStereotype(self, stereotype):
+ stereotype._extended.append(self.element)
+ def _checkStereotypeCanBeAdded(self, stereotype):
+ if stereotype in self._stereotypes:
+ raise CException(f"'{stereotype.name!s}' is already a stereotype of '{self.element.name!s}'")
+ def _initExtendedElement(self, stereotype):
+ pass
+
+ # template method
+ def _setStereotypes(self, elements):
+ if elements == None:
+ elements = []
+ self._removeFromStereotype()
+ self._stereotypes = []
+ if isCStereotype(elements):
+ elements = [elements]
+ elif not isinstance(elements, list):
+ raise CException(f"a list or a stereotype is required as input")
+ for s in elements:
+ checkIsCStereotype(s)
+ if s != None:
+ checkNamedElementIsNotDeleted(s)
+ self._checkStereotypeCanBeAdded(s)
+ self._stereotypes.append(s)
+ self._appendOnStereotype(s)
+ self._initExtendedElement(s)
+
+ @stereotypes.setter
+ def stereotypes(self, elements):
+ self._setStereotypes(elements)
+
+
+
+
+class CStereotypeInstancesHolder(CStereotypesHolder):
+ def __init__(self, element):
+ super().__init__(element)
+
+ def _setAllDefaultTaggedValuesOfStereotype(self, stereotype):
+ for a in stereotype.attributes:
+ if a.default != None:
+ self.element.setTaggedValue(a._name, a.default, stereotype)
+
+ def _getStereotypeInstancePathSuperclasses(self, stereotype):
+ stereotypePath = [stereotype]
+ for superclass in stereotype.superclasses:
+ for superclassStereotype in self._getStereotypeInstancePathSuperclasses(superclass):
+ if not superclassStereotype in stereotypePath:
+ stereotypePath.append(superclassStereotype)
+ return stereotypePath
+
+ def getStereotypeInstancePath(self):
+ stereotypePath = []
+ for stereotypeOfThisElement in self.stereotypes:
+ for stereotype in self._getStereotypeInstancePathSuperclasses(stereotypeOfThisElement):
+ if not stereotype in stereotypePath:
+ stereotypePath.append(stereotype)
+ return stereotypePath
+
+ def _removeFromStereotype(self):
+ for s in self._stereotypes:
+ s._extendedInstances.remove(self.element)
+
+ def _appendOnStereotype(self, stereotype):
+ stereotype._extendedInstances.append(self.element)
+
+ def _getElementNameString(self):
+ if isCClass(self.element):
+ return f"'{self.element.name!s}'"
+ elif isCLink(self.element):
+ return f"link from '{self.element.source!s}' to '{self.element.target!s}'"
+ raise CException(f"unexpected element type: {self.element!r}")
+
+ def _checkStereotypeCanBeAdded(self, stereotype):
+ if stereotype in self._stereotypes:
+ raise CException(f"'{stereotype.name!s}' is already a stereotype instance on {self._getElementNameString()!s}")
+ if not stereotype.isElementExtendedByStereotype(self.element):
+ raise CException(f"stereotype '{stereotype!s}' cannot be added to {self._getElementNameString()!s}: no extension by this stereotype found")
+
+ def _initExtendedElement(self, stereotype):
+ self._setAllDefaultTaggedValuesOfStereotype(stereotype)
+ for sc in stereotype.allSuperclasses:
+ self._setAllDefaultTaggedValuesOfStereotype(sc)
\ No newline at end of file
diff --git a/codeableModels/internal/taggedvalues.py b/codeableModels/internal/taggedvalues.py
new file mode 100644
index 0000000..38b13c2
--- /dev/null
+++ b/codeableModels/internal/taggedvalues.py
@@ -0,0 +1,59 @@
+from codeableModels.cexception import CException
+
+class CTaggedValues:
+ def __init__(self):
+ self._taggedValues = {}
+
+ def setTaggedValue(self, taggedValueName, value, legalStereotypes, stereotype = None):
+ if stereotype == None:
+ for s in legalStereotypes:
+ if s.getAttribute(taggedValueName) != None:
+ self._setValueUsingSpecifiedStereotype(taggedValueName, value, s)
+ return
+ raise CException(f"tagged value '{taggedValueName!s}' unknown")
+ else:
+ if not stereotype in legalStereotypes:
+ raise CException(f"stereotype '{stereotype!s}' is not a stereotype of element")
+ return self._setValueUsingSpecifiedStereotype(taggedValueName, value, stereotype)
+
+ def _setValueUsingSpecifiedStereotype(self, name, value, stereotype):
+ attribute = stereotype.getAttribute(name)
+ if attribute == None:
+ raise CException(f"tagged value '{name!s}' unknown for stereotype '{stereotype!s}'")
+ attribute.checkAttributeTypeIsNotDeleted()
+ attribute.checkAttributeValueType(name, value)
+ try:
+ self._taggedValues[stereotype].update({name: value})
+ except KeyError:
+ self._taggedValues[stereotype] = {name: value}
+
+ def getTaggedValue(self, taggedValueName, legalStereotypes, stereotype = None):
+ if stereotype == None:
+ for s in legalStereotypes:
+ if s.getAttribute(taggedValueName) != None:
+ return self._getValueUsingSpecifiedStereotype(taggedValueName, s)
+ raise CException(f"tagged value '{taggedValueName!s}' unknown")
+ else:
+ if not stereotype in legalStereotypes:
+ raise CException(f"stereotype '{stereotype!s}' is not a stereotype of element")
+ return self._getValueUsingSpecifiedStereotype(taggedValueName, stereotype)
+
+ def _getValueUsingSpecifiedStereotype(self, name, stereotype):
+ attribute = stereotype.getAttribute(name)
+ if attribute == None:
+ raise CException(f"tagged value '{name!s}' unknown for stereotype '{stereotype!s}'")
+ attribute.checkAttributeTypeIsNotDeleted()
+ try:
+ taggedValuesClassifier = self._taggedValues[stereotype]
+ except KeyError:
+ return None
+ try:
+ return taggedValuesClassifier[name]
+ except KeyError:
+ return None
+
+ def removeTaggedValue(self, attributeName, stereotype):
+ try:
+ self._taggedValues[stereotype].pop(attributeName, None)
+ except KeyError:
+ return
diff --git a/src/codeableModels/CAssociation.java b/src/codeableModels/CAssociation.java
deleted file mode 100644
index 57abffb..0000000
--- a/src/codeableModels/CAssociation.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package codeableModels;
-
-import java.util.*;
-
-public interface CAssociation extends CNamedElement, CStereotypedElement {
-
- // for Aggregation/Composition we assume end1 is the aggregating end
- boolean isAggregation();
-
- void setAggregation(boolean isAggregation);
-
- boolean isComposition();
-
- void setComposition(boolean isComposition);
-
- List getEnds();
-
- boolean hasEndType(CClassifier type);
-
- CAssociationEnd getEndByRoleName(String roleName);
-
- CAssociationEnd getEndByClassifier(CClassifier classifier);
-
- CAssociationEnd getOtherEnd(CAssociationEnd end) throws CException;
-
- CLink addLink(CAssociationEnd targetEnd, CObject object1, CObject object2) throws CException;
-
- List getLinksByObject(CAssociationEnd targetEnd, CObject object);
-
- void removeLink(CAssociationEnd targetEnd, CObject object1, CObject object2) throws CException;
-
- void removeLink(CLink linkToBeRemoved);
-
- List getLinks();
-
-}
\ No newline at end of file
diff --git a/src/codeableModels/CAssociationEnd.java b/src/codeableModels/CAssociationEnd.java
deleted file mode 100644
index 6e9ff81..0000000
--- a/src/codeableModels/CAssociationEnd.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package codeableModels;
-
-public interface CAssociationEnd {
-
- CClassifier getClassifier();
-
- String getRoleName();
-
- boolean isNavigable();
-
- void setNavigable(boolean isNavigable);
-
- CMultiplicity getMultiplicity();
-
- String getMultiplicityString();
-}
\ No newline at end of file
diff --git a/src/codeableModels/CAttribute.java b/src/codeableModels/CAttribute.java
deleted file mode 100644
index 311b4a0..0000000
--- a/src/codeableModels/CAttribute.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package codeableModels;
-
-public interface CAttribute {
-
- String getName();
-
- void setName(String name);
-
- String getType() throws CException;
-
- CClassifier getTypeClassifier();
-
- CEnum getEnumType();
-
- Object getDefaultValue();
-
- void setDefaultValue(Object defaultValue) throws CException;
-
-}
\ No newline at end of file
diff --git a/src/codeableModels/CClass.java b/src/codeableModels/CClass.java
deleted file mode 100644
index 3854a59..0000000
--- a/src/codeableModels/CClass.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package codeableModels;
-
-import java.util.*;
-
-public interface CClass extends CClassifier, CObject, CStereotypedElementInstance {
-
- List getInstances();
-
- List getAllInstances();
-
- CMetaclass getMetaclass();
-
- @Override
- CClass addSuperclass(CClassifier superclass) throws CException;
-
- @Override
- CClass addSuperclass(String superclassString) throws CException;
-
- @Override
- CClass deleteSuperclass(String name) throws CException;
-
- @Override
- CClass deleteSuperclass(CClassifier superclass) throws CException;
-
- CClass addObjectAttribute(String name, CClass classifier) throws CException;
-
- @Override
- CClass addObjectAttribute(String name, String classifierName) throws CException;
-
- @Override
- CClass addStringAttribute(String name) throws CException;
-
- @Override
- CClass addIntAttribute(String name) throws CException;
-
- @Override
- CClass addBooleanAttribute(String name) throws CException;
-
- @Override
- CClass addFloatAttribute(String name) throws CException;
-
- @Override
- CClass addDoubleAttribute(String name) throws CException;
-
- @Override
- CClass addLongAttribute(String name) throws CException;
-
- @Override
- CClass addCharAttribute(String name) throws CException;
-
- @Override
- CClass addByteAttribute(String name) throws CException;
-
- @Override
- CClass addShortAttribute(String name) throws CException;
-
- @Override
- CClass addEnumAttribute(String name, CEnum enumType) throws CException;
-
- @Override
- CClass addAttribute(String name, Object defaultValue) throws CException;
-
- @Override
- CClass setAttributeDefaultValue(String name, Object defaultValue) throws CException;
-
- @Override
- CClass deleteAttribute(String name) throws CException;
-}
\ No newline at end of file
diff --git a/src/codeableModels/CClassifier.java b/src/codeableModels/CClassifier.java
deleted file mode 100644
index 678cb24..0000000
--- a/src/codeableModels/CClassifier.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package codeableModels;
-
-import java.util.*;
-
-public interface CClassifier extends CNamedElement {
-
- CAssociationEnd createEnd(String roleName, String multiplicity) throws CException;
-
- CAssociationEnd createEnd(String multiplicity) throws CException;
-
- CAssociationEnd createEnd(String roleName, String multiplicity, boolean isNavigable) throws CException;
-
- List getAssociations();
-
- CAssociation getAssociationByRoleName(String roleName);
-
- CAssociation getAssociationByName(String name);
-
- CAssociation getAssociationByEnd(CAssociationEnd associationEnd);
-
- List getAttributes();
-
- List getAttributeNames();
-
- CAttribute getAttribute(String name);
-
- CClassifier addObjectAttribute(String name, CClassifier classifier) throws CException;
-
- CClassifier addObjectAttribute(String name, String classifierName) throws CException;
-
- CClassifier addStringAttribute(String name) throws CException;
-
- CClassifier addIntAttribute(String name) throws CException;
-
- CClassifier addBooleanAttribute(String name) throws CException;
-
- CClassifier addFloatAttribute(String name) throws CException;
-
- CClassifier addDoubleAttribute(String name) throws CException;
-
- CClassifier addLongAttribute(String name) throws CException;
-
- CClassifier addCharAttribute(String name) throws CException;
-
- CClassifier addByteAttribute(String name) throws CException;
-
- CClassifier addShortAttribute(String name) throws CException;
-
- CClassifier addEnumAttribute(String name, CEnum enumType) throws CException;
-
- CClassifier addAttribute(String name, Object defaultValue) throws CException;
-
- CClassifier setAttributeDefaultValue(String name, Object defaultValue) throws CException;
-
- CClassifier deleteAttribute(String name) throws CException;
-
- CMetaclass asMetaclass() throws CException;
-
- CStereotype asStereotype() throws CException;
-
- CClass asClass() throws CException;
-
- List getSuperclasses();
-
- List getSubclasses();
-
- List getAllSuperclasses();
-
- List getAllSubclasses();
-
- CClassifier addSuperclass(String name) throws CException;
-
- CClassifier addSuperclass(CClassifier superclass) throws CException;
-
- CClassifier deleteSuperclass(CClassifier superclass) throws CException;
-
- CClassifier deleteSuperclass(String name) throws CException;
-
- boolean hasSuperclass(CClassifier cl);
-
- boolean hasSubclass(CClassifier cl);
-
- boolean hasSuperclass(String clName);
-
- boolean hasSubclass(String clName);
-}
\ No newline at end of file
diff --git a/src/codeableModels/CElement.java b/src/codeableModels/CElement.java
deleted file mode 100644
index cc0649f..0000000
--- a/src/codeableModels/CElement.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package codeableModels;
-
-public interface CElement {
-
- CModel getModel();
-
- void setModel(CModel model);
-
-}
\ No newline at end of file
diff --git a/src/codeableModels/CEnum.java b/src/codeableModels/CEnum.java
deleted file mode 100644
index f9812cd..0000000
--- a/src/codeableModels/CEnum.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package codeableModels;
-
-import java.util.*;
-
-public interface CEnum extends CNamedElement {
-
- List getValues();
-
- boolean isLegalValue(String value);
-
-}
\ No newline at end of file
diff --git a/src/codeableModels/CException.java b/src/codeableModels/CException.java
deleted file mode 100644
index 74ff520..0000000
--- a/src/codeableModels/CException.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package codeableModels;
-
-public class CException extends Exception {
- private static final long serialVersionUID = 1L;
-
- public CException(String m) {
- super(m);
- }
-}
diff --git a/src/codeableModels/CLink.java b/src/codeableModels/CLink.java
deleted file mode 100644
index c075cb1..0000000
--- a/src/codeableModels/CLink.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package codeableModels;
-
-import java.util.*;
-
-public interface CLink extends CStereotypedElementInstance {
-
- CAssociation getAssociation();
-
- List getLinkedObjects();
-
- CObject getLinkedObjectByName(String objectName);
-
- CObject getLinkedObjectByClassifier(CClassifier classifier);
-
- CObject getLinkedObjectAtTargetEnd(CAssociationEnd targetEnd) throws CException;
-}
\ No newline at end of file
diff --git a/src/codeableModels/CMetaclass.java b/src/codeableModels/CMetaclass.java
deleted file mode 100644
index e98d898..0000000
--- a/src/codeableModels/CMetaclass.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package codeableModels;
-
-import java.util.*;
-
-public interface CMetaclass extends CClassifier, CStereotypedElement {
-
- List getClassInstances();
-
- List getAllClassInstances();
-
- @Override
- CMetaclass addSuperclass(CClassifier superclass) throws CException;
-
- @Override
- CMetaclass addSuperclass(String superclassString) throws CException;
-
- @Override
- CMetaclass deleteSuperclass(String name) throws CException;
-
- @Override
- CMetaclass deleteSuperclass(CClassifier superclass) throws CException;
-
- @Override
- CMetaclass addObjectAttribute(String name, CClassifier classifier) throws CException;
-
- @Override
- CMetaclass addObjectAttribute(String name, String classifierName) throws CException;
-
- @Override
- CMetaclass addStringAttribute(String name) throws CException;
-
- @Override
- CMetaclass addIntAttribute(String name) throws CException;
-
- @Override
- CMetaclass addBooleanAttribute(String name) throws CException;
-
- @Override
- CMetaclass addFloatAttribute(String name) throws CException;
-
- @Override
- CMetaclass addDoubleAttribute(String name) throws CException;
-
- @Override
- CMetaclass addLongAttribute(String name) throws CException;
-
- @Override
- CMetaclass addCharAttribute(String name) throws CException;
-
- @Override
- CMetaclass addByteAttribute(String name) throws CException;
-
- @Override
- CMetaclass addShortAttribute(String name) throws CException;
-
- @Override
- CMetaclass addEnumAttribute(String name, CEnum enumType) throws CException;
-
- @Override
- CMetaclass addAttribute(String name, Object defaultValue) throws CException;
-
- @Override
- CMetaclass setAttributeDefaultValue(String name, Object defaultValue) throws CException;
-
- @Override
- CMetaclass deleteAttribute(String name) throws CException;
-
-}
\ No newline at end of file
diff --git a/src/codeableModels/CModel.java b/src/codeableModels/CModel.java
deleted file mode 100644
index 6b8e24e..0000000
--- a/src/codeableModels/CModel.java
+++ /dev/null
@@ -1,108 +0,0 @@
-package codeableModels;
-
-import java.util.*;
-
-public interface CModel {
-
- CMetaclass createMetaclass(String name) throws CException;
-
- CMetaclass createMetaclass() throws CException;
-
- CClass createClass(CMetaclass metaclass, String name) throws CException;
-
- CClass createClass(CMetaclass metaclass) throws CException;
-
- CClass createClass(String metaclassName, String name) throws CException;
-
- CClass createClass(String metaclassName) throws CException;
-
- CStereotype createStereotype(String name) throws CException;
-
- CStereotype createStereotype() throws CException;
-
- CStereotype createStereotype(String name, CStereotypedElement stereotypedElement) throws CException;
-
- CStereotype createStereotype(String name, String stereotypedElementName) throws CException;
-
- CObject createObject(CClass cl, String name) throws CException;
-
- CObject createObject(CClass cl) throws CException;
-
- CObject createObject(String className, String name) throws CException;
-
- CObject createObject(String className) throws CException;
-
- CAssociation createAssociation(String name, CAssociationEnd end1, CAssociationEnd end2) throws CException;
-
- CAssociation createComposition(String name, CAssociationEnd composingEnd, CAssociationEnd composedEnd) throws
- CException;
-
- CAssociation createAggregation(String name, CAssociationEnd aggregatingEnd, CAssociationEnd aggregatedEnd) throws
- CException;
-
- CAssociation createAssociation(CAssociationEnd end1, CAssociationEnd end2) throws CException;
-
- CAssociation createComposition(CAssociationEnd end1, CAssociationEnd end2) throws CException;
-
- CAssociation createAggregation(CAssociationEnd end1, CAssociationEnd end2) throws CException;
-
- CModel importModel(CModel model);
-
- CClassifier getClassifier(String name);
-
- CMetaclass getMetaclass(String name) throws CException;
-
- CStereotype getStereotype(String name) throws CException;
-
- CClass getClass(String name) throws CException;
-
- CObject getObject(String name);
-
- CClassifier lookupClassifier(String name);
-
- CMetaclass lookupMetaclass(String name) throws CException;
-
- CClass lookupClass(String name) throws CException;
-
- CStereotype lookupStereotype(String name) throws CException;
-
- List getClassifierNames();
-
- List getClassifiers();
-
- List getMetaclasses();
-
- List getClasses();
-
- List getStereotypes();
-
- List getMetaclassNames();
-
- List getStereotypeNames();
-
- List getClassNames();
-
- void deleteClassifier(CClassifier cl) throws CException;
-
- List getObjectNames();
-
- List getObjects();
-
- void deleteObject(CObject o) throws CException;
-
- List getImportedModels();
-
- List getFullModelList();
-
- List getAssociations();
-
- void deleteAssociation(CAssociation assoc) throws CException;
-
- // returns an array list, as association names don't have to be unique,
- // including null == association with no name
- List getAssociationsByName(String name);
-
- CEnum createEnum(String name, List enumValues);
-
- List getAssociationsForType(CClassifier type);
-}
\ No newline at end of file
diff --git a/src/codeableModels/CMultiplicity.java b/src/codeableModels/CMultiplicity.java
deleted file mode 100644
index 2e56fbe..0000000
--- a/src/codeableModels/CMultiplicity.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package codeableModels;
-
-public interface CMultiplicity {
-
- int STAR_MULTIPLICITY = -1;
-
- String getMultiplicity();
-
- int getUpperMultiplicity();
-
- int getLowerMultiplicity();
-
-}
\ No newline at end of file
diff --git a/src/codeableModels/CNamedElement.java b/src/codeableModels/CNamedElement.java
deleted file mode 100644
index 4934d65..0000000
--- a/src/codeableModels/CNamedElement.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package codeableModels;
-
-public interface CNamedElement extends CElement {
-
- String getName();
-
-}
\ No newline at end of file
diff --git a/src/codeableModels/CObject.java b/src/codeableModels/CObject.java
deleted file mode 100644
index 7a35f35..0000000
--- a/src/codeableModels/CObject.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package codeableModels;
-
-import java.util.*;
-
-public interface CObject extends CNamedElement {
-
- CClassifier getClassifier();
-
- boolean instanceOf(CClassifier classifier);
-
- boolean instanceOf(String classifierName);
-
- // Terminology as in UML: Associations represent relationships between classes; links represent relationships between objects.
-
- List addLinks(CAssociationEnd targetEnd, List