Browse Source

check if local transformation breaks conformance for attributes, nodes and edges. If it does, repair it automatically

Lucas Heer 7 years ago
parent
commit
4e5cfe72ff
6 changed files with 144 additions and 114 deletions
  1. 87 0
      commons.py
  2. 10 4
      evolution/attribute_ops.py
  3. 41 26
      evolution/edge_ops.py
  4. 1 1
      evolution/node_ops.py
  5. 4 82
      verifier.py
  6. 1 1
      wrappers/modelverse_SCCD.py

+ 87 - 0
commons.py

@@ -2,6 +2,30 @@ import re
 from wrappers import modelverse as mv
 from wrappers import modelverse as mv
 
 
 
 
+class Edge(object):
+    """
+    Small helper class for association validation.
+    Represents an edge as a connection between two nodes (n1, n2).
+    Does not assume any direction. As such, the eq and hash implementations
+    do not care about the ordering and an Edge("a", "b") object is equal to Edge("b", "a").
+    """
+    def __init__(self, n1, n2):
+        self.n1 = n1
+        self.n2 = n2
+
+    def __eq__(self, other):
+        # type: (Edge) -> bool
+        if other.n1 == self.n1 or other.n1 == self.n2 and other.n2 == self.n1 or other.n2 == self.n2:
+            return True
+        return False
+
+    def __hash__(self):
+        return hash(self.n1) ^ hash(self.n2)
+
+    def __repr__(self):
+        return "{}-{}".format(self.n1, self.n2)
+
+
 class Attribute(object):
 class Attribute(object):
     """
     """
     Helper class for attribute representation as key value pair
     Helper class for attribute representation as key value pair
@@ -19,6 +43,7 @@ class Attribute(object):
         return "{}={}".format(self.key, self.val)
         return "{}={}".format(self.key, self.val)
 
 
 
 
+
 def all_models():
 def all_models():
     """ Returns a list of paths of all example- and instance models """
     """ Returns a list of paths of all example- and instance models """
     return all_instance_models() + all_example_models()
     return all_instance_models() + all_example_models()
@@ -233,3 +258,65 @@ def get_nodes_with_attribute(model, attr_key, node_type):
             if attr.key == attr_key:
             if attr.key == attr_key:
                 ret.append(node_id)
                 ret.append(node_id)
     return ret
     return ret
+
+def has_attribute_key(model, node_id, attr_key):
+    """
+    Helper function: True if node with node_id has an attribute with key attr_key, False otherwise.
+    """
+    attrs = get_attributes_of_node(model, node_id)
+    for attr in attrs:
+        if attr.key == attr_key:
+            return True
+    return False
+
+def is_attribute_mandatory(node_type, attr_key):
+    """
+    Helper for attribute check that returns True if the attribute attr_key of type node_type is mandatory by
+    looking at all example models.
+    """
+    # iterate through all example models: If every node with type node_type has the attribute with attr_key,
+    # it is mandatory
+    for exm in all_example_models():
+        nodes_of_type = all_nodes_with_type(exm, node_type)
+        for node in nodes_of_type:
+            if not has_attribute_key(exm, node, attr_key):
+                return False
+    return True
+
+def get_mandtory_edges():
+    """
+    Returns a list of mandatory edges from all example models.
+    """
+    example_models = all_example_models()
+    all_edges = set()
+
+    for exm in example_models:
+        all_links = mv.all_instances(exm, "Edge")
+        for link in all_links:
+            src_id = mv.read_association_source(exm, link)[0]
+            dest_id = mv.read_association_destination(exm, link)[0]
+            src_type = get_node_type(exm, src_id)
+            dest_type = get_node_type(exm, dest_id)
+            all_edges.add(Edge(src_type, dest_type))
+    edge_mandatory = {e: True for e in all_edges}
+
+    for cand_edge, _ in edge_mandatory.iteritems():
+        # check every example model if it contains the required types
+        # and if there are two noes of the type which are not connected -> not mandatory
+        found = False
+        for exm in example_models:
+            if found:
+                break
+            typed_nodes_a = all_nodes_with_type(exm, cand_edge.n1)
+            typed_nodes_b = all_nodes_with_type(exm, cand_edge.n2)
+            if not typed_nodes_a or not typed_nodes_b:
+                # example model does not contain the two types
+                continue
+            for src_node in typed_nodes_a:
+                # if this node is not connected to a node typed by the required type, the edge is not mandatory
+                if not has_edge_to_type(exm, src_node, cand_edge.n2):
+                    edge_mandatory[cand_edge] = False
+                    found = True
+
+    mandatory_edges = [edge for edge, mandatory in edge_mandatory.iteritems() if mandatory]
+    return mandatory_edges

+ 10 - 4
evolution/attribute_ops.py

@@ -11,7 +11,7 @@ class AttributeAdd(object):
         self._node_id = ""
         self._node_id = ""
         self._node_type = ""
         self._node_type = ""
 
 
-    def execute(self, model, node_id, key, value, local):
+    def execute(self, model, node_id, key, value, local, check_if_last=False):
         """
         """
         Add a new attribute (key, value) to node with node_id in model.
         Add a new attribute (key, value) to node with node_id in model.
         """
         """
@@ -25,12 +25,18 @@ class AttributeAdd(object):
         if local:
         if local:
             mv.transformation_execute_MANUAL("graph_ops/add_attribute", {"gm":model}, {"gm":model},
             mv.transformation_execute_MANUAL("graph_ops/add_attribute", {"gm":model}, {"gm":model},
                                              callback=self._callback)
                                              callback=self._callback)
-            # TODO: local add can make attribute mandatory and break conformance relationship of instance models
+            if check_if_last:
+                if commons.is_attribute_mandatory(self._node_type, key):
+                    print("Attribute {} for type {} became mandatory, adding it to instance models ...".format(key, self._node_type))
+                    for im in commons.all_instance_models():
+                        nodes = commons.all_nodes_with_type(im, self._node_type)
+                        for nid in nodes:
+                            self.execute(im, nid, key, value, local=True, check_if_last=False)
         else:
         else:
             for m in commons.all_models():
             for m in commons.all_models():
                 nodes = commons.all_nodes_with_type(m, self._node_type)
                 nodes = commons.all_nodes_with_type(m, self._node_type)
                 for nid in nodes:
                 for nid in nodes:
-                    self.execute(m, nid, key, value, local=True)
+                    self.execute(m, nid, key, value, local=True, check_if_last=False)
 
 
     def _callback(self, model):
     def _callback(self, model):
         attr_id = mv.instantiate(model, "gm/Attribute")
         attr_id = mv.instantiate(model, "gm/Attribute")
@@ -74,7 +80,7 @@ class AttributeDelete(object):
                 for node in nodes:
                 for node in nodes:
                     self.execute(im, node, key, local=True, check_if_last=False)
                     self.execute(im, node, key, local=True, check_if_last=False)
         else:
         else:
-            # delete all attributes with key
+            # delete all attributes with key in all example models
             for m in commons.all_models():
             for m in commons.all_models():
                 nodes = commons.all_nodes_with_type(m, self._node_type)
                 nodes = commons.all_nodes_with_type(m, self._node_type)
                 for nid in nodes:
                 for nid in nodes:

+ 41 - 26
evolution/edge_ops.py

@@ -1,32 +1,47 @@
 import sys
 import sys
 sys.path.append("../wrappers")
 sys.path.append("../wrappers")
 from wrappers import modelverse as mv
 from wrappers import modelverse as mv
-from commons import *
+import commons
 
 
 
 
 class EdgeAdd(object):
 class EdgeAdd(object):
     def __init__(self):
     def __init__(self):
         self._from_node = ""
         self._from_node = ""
         self._to_node = ""
         self._to_node = ""
+        self._from_type = ""
+        self._to_type = ""
 
 
-    def execute(self, model, from_node, to_node, local):
+    def execute(self, model, from_node, to_node, local, check_if_last=False):
         self._from_node = from_node
         self._from_node = from_node
         self._to_node = to_node
         self._to_node = to_node
 
 
+        if not self._from_type:
+            self._from_type = commons.get_node_type(model, from_node)
+        if not self._to_type:
+            self._to_type = commons.get_node_type(model, to_node)
+
         if local:
         if local:
             mv.transformation_execute_MANUAL("graph_ops/add_edge", {"gm":model}, {"gm":model},
             mv.transformation_execute_MANUAL("graph_ops/add_edge", {"gm":model}, {"gm":model},
                                              callback=self._callback)
                                              callback=self._callback)
-            # TODO: local edge add can make an edge mandatory and break the conformance relationship of instance models
+            if check_if_last:
+                mandatory_edges = commons.get_mandtory_edges()
+                this_edge = commons.Edge(self._from_type, self._to_type)
+                if this_edge in mandatory_edges:
+                    print("Edge {} became mandatory, adding to instance models ...".format(this_edge))
+                    for im in commons.all_instance_models():
+                        nodes_from = commons.all_nodes_with_type(im, self._from_type)
+                        nodes_to = commons.all_nodes_with_type(im, self._to_type)
+                        for nf in nodes_from:
+                            for nt in nodes_to:
+                                self.execute(im, nf, nt, local=True, check_if_last=False)
+
         else:
         else:
-            from_type = get_node_type(model, self._from_node)
-            to_type = get_node_type(model, self._to_node)
-            for m in all_models():
-                nodes_from = all_nodes_with_type(m, from_type)
-                nodes_to = all_nodes_with_type(m, to_type)
+            for m in commons.all_models():
+                nodes_from = commons.all_nodes_with_type(m, self._from_type)
+                nodes_to = commons.all_nodes_with_type(m, self._to_type)
                 for nf in nodes_from:
                 for nf in nodes_from:
                     for nt in nodes_to:
                     for nt in nodes_to:
-                        self.execute(m, nf, nt, local=True)
-
+                        self.execute(m, nf, nt, local=True, check_if_last=False)
 
 
     def _callback(self, model):
     def _callback(self, model):
         mv.instantiate(model, "gm/Edge", ("gm/"+self._from_node, "gm/"+self._to_node))
         mv.instantiate(model, "gm/Edge", ("gm/"+self._from_node, "gm/"+self._to_node))
@@ -40,8 +55,8 @@ class EdgeDel(object):
         self._edge = edge
         self._edge = edge
         from_node = mv.read_association_source(model, edge)[0]
         from_node = mv.read_association_source(model, edge)[0]
         to_node = mv.read_association_destination(model, edge)[0]
         to_node = mv.read_association_destination(model, edge)[0]
-        from_type = get_node_type(model, from_node)
-        to_type = get_node_type(model, to_node)
+        from_type = commons.get_node_type(model, from_node)
+        to_type = commons.get_node_type(model, to_node)
 
 
         if local:
         if local:
             mv.transformation_execute_MANUAL("graph_ops/del_edge", {"gm":model}, {"gm":model},
             mv.transformation_execute_MANUAL("graph_ops/del_edge", {"gm":model}, {"gm":model},
@@ -51,32 +66,32 @@ class EdgeDel(object):
             # if yes, delete it from all instance models as well to preserve their validity
             # if yes, delete it from all instance models as well to preserve their validity
             remaining_instances = 0
             remaining_instances = 0
 
 
-            for m in all_example_models():
-                nodes_from = all_nodes_with_type(m, from_type)
-                nodes_to = all_nodes_with_type(m, to_type)
+            for m in commons.all_example_models():
+                nodes_from = commons.all_nodes_with_type(m, from_type)
+                nodes_to = commons.all_nodes_with_type(m, to_type)
                 for nf in nodes_from:
                 for nf in nodes_from:
                     for nt in nodes_to:
                     for nt in nodes_to:
-                        edges = get_associations_between(m, nf, nt)
+                        edges = commons.get_associations_between(m, nf, nt)
                         remaining_instances += len(edges)
                         remaining_instances += len(edges)
             if remaining_instances == 0:
             if remaining_instances == 0:
-                for m in all_instance_models():
-                    nodes_from = all_nodes_with_type(m, from_type)
-                    nodes_to = all_nodes_with_type(m, to_type)
+                for m in commons.all_instance_models():
+                    nodes_from = commons.all_nodes_with_type(m, from_type)
+                    nodes_to = commons.all_nodes_with_type(m, to_type)
                     for nf in nodes_from:
                     for nf in nodes_from:
                         for nt in nodes_to:
                         for nt in nodes_to:
-                            edges = get_associations_between(m, nf, nt)
+                            edges = commons.get_associations_between(m, nf, nt)
                             for edge in edges:
                             for edge in edges:
-                                self.execute(m, edge, local=True)
+                                self.execute(m, edge, local=True, check_if_last=False)
 
 
         else:
         else:
-            for m in all_models():
-                nodes_from = all_nodes_with_type(m, from_type)
-                nodes_to = all_nodes_with_type(m, to_type)
+            for m in commons.all_models():
+                nodes_from = commons.all_nodes_with_type(m, from_type)
+                nodes_to = commons.all_nodes_with_type(m, to_type)
                 for nf in nodes_from:
                 for nf in nodes_from:
                     for nt in nodes_to:
                     for nt in nodes_to:
-                        edges = get_associations_between(m, nf, nt)
+                        edges = commons.get_associations_between(m, nf, nt)
                         for edge in edges:
                         for edge in edges:
-                            self.execute(m, edge, local=True)
+                            self.execute(m, edge, local=True, check_if_last=False)
 
 
     def _callback(self, model):
     def _callback(self, model):
         mv.delete_element(model, "gm/"+self._edge)
         mv.delete_element(model, "gm/"+self._edge)

+ 1 - 1
evolution/node_ops.py

@@ -21,7 +21,7 @@ class NodeAdd(object):
             # Local add can make a type mandatory and therefore break conformance
             # Local add can make a type mandatory and therefore break conformance
             if check_if_last:
             if check_if_last:
                 if commons.is_type_mandatory(node_type):
                 if commons.is_type_mandatory(node_type):
-                    print("Type {} just became mandatory, adding it to instance models ...".format(node_type))
+                    print("Type {} became mandatory, adding it to instance models ...".format(node_type))
                     # local add made type mandatory, so need to add to instance models
                     # local add made type mandatory, so need to add to instance models
                     for im in commons.all_instance_models():
                     for im in commons.all_instance_models():
                         self.execute(im, node_type, local=True, check_if_last=False)
                         self.execute(im, node_type, local=True, check_if_last=False)

+ 4 - 82
verifier.py

@@ -2,30 +2,6 @@ import wrappers.modelverse as mv
 import commons
 import commons
 
 
 
 
-class Edge(object):
-    """
-    Small helper class for association validation.
-    Represents an edge as a connection between two nodes (n1, n2).
-    Does not assume any direction. As such, the eq and hash implementations
-    do not care about the ordering and an Edge("a", "b") object is equal to Edge("b", "a").
-    """
-    def __init__(self, n1, n2):
-        self.n1 = n1
-        self.n2 = n2
-
-    def __eq__(self, other):
-        # type: (Edge) -> bool
-        if other.n1 == self.n1 or other.n1 == self.n2 and other.n2 == self.n1 or other.n2 == self.n2:
-            return True
-        return False
-
-    def __hash__(self):
-        return hash(self.n1) ^ hash(self.n2)
-
-    def __repr__(self):
-        return "{}-{}".format(self.n1, self.n2)
-
-
 class Verifier(object):
 class Verifier(object):
     def __init__(self, instance_model):
     def __init__(self, instance_model):
         self._instance_model = instance_model
         self._instance_model = instance_model
@@ -76,6 +52,8 @@ class Verifier(object):
             if not all_of_type:
             if not all_of_type:
                 return {"OK": False, "error": "Mandatory node of type {} not found".format(mand_type), "affected":[]}
                 return {"OK": False, "error": "Mandatory node of type {} not found".format(mand_type), "affected":[]}
 
 
+        return {"OK":True, "error":None, "affected":[]}
+
     def _get_attributes_of_all_types(self):
     def _get_attributes_of_all_types(self):
         """
         """
         Helper for attribute check that returns a dictionary of the form {type:[attr_key_1, attr_key_2, ...], ...},
         Helper for attribute check that returns a dictionary of the form {type:[attr_key_1, attr_key_2, ...], ...},
@@ -90,31 +68,6 @@ class Verifier(object):
                         attrs.append(attr)
                         attrs.append(attr)
         return attrs_of_types
         return attrs_of_types
 
 
-    def _has_attribute_key(self, model, node_id, attr_key):
-        """
-        True if node with node_id has an attribute with key attr_key, False otherwise.
-        """
-        attrs = commons.get_attributes_of_node(model, node_id)
-        for attr in attrs:
-            if attr.key == attr_key:
-                return True
-        return False
-
-    def _is_attribute_mandatory(self, node_type, attr_key):
-        """
-        Helper for attribute check that returns True if the attribute attr_key of type node_type is mandatory by
-        looking at all example models.
-        """
-        # iterate through all example models: If every node with type node_type has the attribute with attr_key,
-        # it is mandatory
-        for exm in self._example_models:
-            nodes_of_type = commons.all_nodes_with_type(exm, node_type)
-            for node in nodes_of_type:
-                if not self._has_attribute_key(exm, node, attr_key):
-                    return False
-        return True
-
-
     def verify_attributes(self):
     def verify_attributes(self):
         """
         """
         1. For every attribute key of a typed node in the instance model, there must be a corresponding
         1. For every attribute key of a typed node in the instance model, there must be a corresponding
@@ -152,7 +105,7 @@ class Verifier(object):
         # now need to check which one is mandatory and set the boolean accordingly
         # now need to check which one is mandatory and set the boolean accordingly
         for node_type, attr_dict in attr_mandatory.iteritems():
         for node_type, attr_dict in attr_mandatory.iteritems():
             for attr, _ in attr_dict.iteritems():
             for attr, _ in attr_dict.iteritems():
-                if self._is_attribute_mandatory(node_type, attr):
+                if commons.is_attribute_mandatory(node_type, attr):
                     attr_dict[attr] = True
                     attr_dict[attr] = True
 
 
         # for every node in instance model, check if it has the mandatory attributes
         # for every node in instance model, check if it has the mandatory attributes
@@ -177,38 +130,7 @@ class Verifier(object):
         is already enforced while instance modeling (see im_scene.py, draw_edge() which checks this when trying
         is already enforced while instance modeling (see im_scene.py, draw_edge() which checks this when trying
         to connect two nodes).
         to connect two nodes).
         """
         """
-
-        # construct the set of mandatory edges
-        all_edges = set()
-        for exm in self._example_models:
-            all_links = mv.all_instances(exm, "Edge")
-            for link in all_links:
-                src_id = mv.read_association_source(exm, link)[0]
-                dest_id = mv.read_association_destination(exm, link)[0]
-                src_type = commons.get_node_type(exm, src_id)
-                dest_type = commons.get_node_type(exm, dest_id)
-                all_edges.add(Edge(src_type, dest_type))
-        edge_mandatory = {e:True for e in all_edges}
-
-        for cand_edge, _ in edge_mandatory.iteritems():
-            # check every example model if it contains the required types
-            # and if there are two noes of the type which are not connected -> not mandatory
-            found = False
-            for exm in self._example_models:
-                if found:
-                    break
-                typed_nodes_a = commons.all_nodes_with_type(exm, cand_edge.n1)
-                typed_nodes_b = commons.all_nodes_with_type(exm, cand_edge.n2)
-                if not typed_nodes_a or not typed_nodes_b:
-                    # example model does not contain the two types
-                    continue
-                for src_node in typed_nodes_a:
-                    # if this node is not connected to a node typed by the required type, the edge is not mandatory
-                    if not commons.has_edge_to_type(exm, src_node, cand_edge.n2):
-                        edge_mandatory[cand_edge] = False
-                        found = True
-
-        mandatory_edges = [edge for edge,mandatory in edge_mandatory.iteritems() if mandatory]
+        mandatory_edges = commons.get_mandtory_edges()
 
 
         # check if instance model contains the types and the edge
         # check if instance model contains the types and the edge
         for edge in mandatory_edges:
         for edge in mandatory_edges:

+ 1 - 1
wrappers/modelverse_SCCD.py

@@ -1,7 +1,7 @@
 """
 """
 Generated by Statechart compiler by Glenn De Jonghe, Joeri Exelmans, Simon Van Mierlo, and Yentl Van Tendeloo (for the inspiration)
 Generated by Statechart compiler by Glenn De Jonghe, Joeri Exelmans, Simon Van Mierlo, and Yentl Van Tendeloo (for the inspiration)
 
 
-Date:   Sun May  6 17:04:56 2018
+Date:   Sun May  6 19:20:07 2018
 
 
 Model author: Yentl Van Tendeloo
 Model author: Yentl Van Tendeloo
 Model name:   MvK Server
 Model name:   MvK Server