浏览代码

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

Lucas Heer 7 年之前
父节点
当前提交
4e5cfe72ff
共有 6 个文件被更改,包括 144 次插入114 次删除
  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
 
 
+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):
     """
     Helper class for attribute representation as key value pair
@@ -19,6 +43,7 @@ class Attribute(object):
         return "{}={}".format(self.key, self.val)
 
 
+
 def all_models():
     """ Returns a list of paths of all example- and instance 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:
                 ret.append(node_id)
     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_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.
         """
@@ -25,12 +25,18 @@ class AttributeAdd(object):
         if local:
             mv.transformation_execute_MANUAL("graph_ops/add_attribute", {"gm":model}, {"gm":model},
                                              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:
             for m in commons.all_models():
                 nodes = commons.all_nodes_with_type(m, self._node_type)
                 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):
         attr_id = mv.instantiate(model, "gm/Attribute")
@@ -74,7 +80,7 @@ class AttributeDelete(object):
                 for node in nodes:
                     self.execute(im, node, key, local=True, check_if_last=False)
         else:
-            # delete all attributes with key
+            # delete all attributes with key in all example models
             for m in commons.all_models():
                 nodes = commons.all_nodes_with_type(m, self._node_type)
                 for nid in nodes:

+ 41 - 26
evolution/edge_ops.py

@@ -1,32 +1,47 @@
 import sys
 sys.path.append("../wrappers")
 from wrappers import modelverse as mv
-from commons import *
+import commons
 
 
 class EdgeAdd(object):
     def __init__(self):
         self._from_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._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:
             mv.transformation_execute_MANUAL("graph_ops/add_edge", {"gm":model}, {"gm":model},
                                              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:
-            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 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):
         mv.instantiate(model, "gm/Edge", ("gm/"+self._from_node, "gm/"+self._to_node))
@@ -40,8 +55,8 @@ class EdgeDel(object):
         self._edge = edge
         from_node = mv.read_association_source(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:
             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
             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 nt in nodes_to:
-                        edges = get_associations_between(m, nf, nt)
+                        edges = commons.get_associations_between(m, nf, nt)
                         remaining_instances += len(edges)
             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 nt in nodes_to:
-                            edges = get_associations_between(m, nf, nt)
+                            edges = commons.get_associations_between(m, nf, nt)
                             for edge in edges:
-                                self.execute(m, edge, local=True)
+                                self.execute(m, edge, local=True, check_if_last=False)
 
         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 nt in nodes_to:
-                        edges = get_associations_between(m, nf, nt)
+                        edges = commons.get_associations_between(m, nf, nt)
                         for edge in edges:
-                            self.execute(m, edge, local=True)
+                            self.execute(m, edge, local=True, check_if_last=False)
 
     def _callback(self, model):
         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
             if check_if_last:
                 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
                     for im in commons.all_instance_models():
                         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
 
 
-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):
     def __init__(self, instance_model):
         self._instance_model = instance_model
@@ -76,6 +52,8 @@ class Verifier(object):
             if not all_of_type:
                 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):
         """
         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)
         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):
         """
         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
         for node_type, attr_dict in attr_mandatory.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
 
         # 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
         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
         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)
 
-Date:   Sun May  6 17:04:56 2018
+Date:   Sun May  6 19:20:07 2018
 
 Model author: Yentl Van Tendeloo
 Model name:   MvK Server