Browse Source

maintain conformance when adding nodes and deleting attributes

Lucas Heer 7 years ago
parent
commit
5b14d5fdf3

+ 13 - 0
commons.py

@@ -220,3 +220,16 @@ def new_example_model():
     mv.attr_assign(exm, mid, "is_example", True)
     mv.attr_assign(exm, mid, "descr", "")
     return exm
+
+def get_nodes_with_attribute(model, attr_key, node_type):
+    """
+    Returns all nodes which have an attribute identified by attr_key and are typed by node_type
+    """
+    ret = []
+    typed_nodes = all_nodes_with_type(model, node_type)
+    for node_id in typed_nodes:
+        attrs = get_attributes_of_node(model, node_id)
+        for attr in attrs:
+            if attr.key == attr_key:
+                ret.append(node_id)
+    return ret

+ 17 - 1
evolution/attribute_ops.py

@@ -25,6 +25,7 @@ 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
         else:
             for m in commons.all_models():
                 nodes = commons.all_nodes_with_type(m, self._node_type)
@@ -44,7 +45,7 @@ class AttributeDelete(object):
         self._node_id = ""
         self._node_type = ""
 
-    def execute(self, model, node_id, key, local):
+    def execute(self, model, node_id, key, local, check_if_last=False):
         """
         Deletes an attribute identified by its key from node_id in model.
         """
@@ -57,6 +58,21 @@ class AttributeDelete(object):
         if local:
             mv.transformation_execute_MANUAL("graph_ops/del_attribute", {"gm":model}, {"gm":model},
                                              callback=self._callback)
+        if check_if_last:
+            # a local attribute delete can break the conformance for instance models if the attribute was the last in
+            # all example models -> check and correct this if necessary
+            for exm in commons.all_example_models():
+                all_attrs = commons.get_all_attributes_of_type(exm, self._node_type)
+                for attr in all_attrs:
+                    if attr.key == key:
+                        # wasn't the last, we're done here
+                        return
+            # it was the last -> delete this attribute in all instance models to maintain conformance
+            print("Attribute {} was the last, deleting it from all instance models to maintain conformance ...".format(key))
+            for im in commons.all_instance_models():
+                nodes = commons.get_nodes_with_attribute(im, key, self._node_type)
+                for node in nodes:
+                    self.execute(im, node, key, local=True, check_if_last=False)
         else:
             # delete all attributes with key
             for m in commons.all_models():

+ 1 - 0
evolution/edge_ops.py

@@ -16,6 +16,7 @@ class EdgeAdd(object):
         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
         else:
             from_type = get_node_type(model, self._from_node)
             to_type = get_node_type(model, self._to_node)

+ 21 - 13
evolution/node_ops.py

@@ -1,14 +1,14 @@
 import sys
 sys.path.append("../wrappers")
 from wrappers import modelverse as mv
-from commons import *
+import commons
 
 
 class NodeAdd(object):
     def __init__(self):
         self._node_type = ""
 
-    def execute(self, model, node_type, local):
+    def execute(self, model, node_type, local, check_if_last=False):
         """
         Add a new node with type node_type to model model.
         If local is true, the node is only added to the model.
@@ -18,8 +18,16 @@ class NodeAdd(object):
         if local:
             mv.transformation_execute_MANUAL("graph_ops/add_node", {"gm":model}, {"gm":model},
                                              callback=self._callback)
+            # 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))
+                    # 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)
+
         else:
-            for m in all_models():
+            for m in commons.all_models():
                 self.execute(m, node_type, local=True)
 
     def _callback(self, model):
@@ -35,7 +43,7 @@ class NodeDelete(object):
 
     def execute(self, model, node, local, check_if_last=False):
         self._node = node
-        self._node_type = get_node_type(model, node)
+        self._node_type = commons.get_node_type(model, node)
 
         if local:
             mv.transformation_execute_MANUAL("graph_ops/del_node", {"gm":model}, {"gm":model},
@@ -44,18 +52,18 @@ class NodeDelete(object):
                 # check if we just deleted the last instance of the node in all example models
                 # if yes, delete it in instance models as well to preserve their validity
                 remaining_instances = 0
-                for m in all_example_models():
-                    remaining_instances += len(all_nodes_with_type(m, self._node_type))
+                for m in commons.all_example_models():
+                    remaining_instances += len(commons.all_nodes_with_type(m, self._node_type))
                 if remaining_instances == 0:
                     # it was indeed the last one, delete from instance models
                     self._was_last = True
-                    for m in all_instance_models():
-                        nodes_to_delete = all_nodes_with_type(m, self._node_type)
+                    for m in commons.all_instance_models():
+                        nodes_to_delete = commons.all_nodes_with_type(m, self._node_type)
                         for node in nodes_to_delete:
                             self.execute(m, node, local=True)
         else:
-            for m in all_models():
-                nodes_to_delete = all_nodes_with_type(m, self._node_type)
+            for m in commons.all_models():
+                nodes_to_delete = commons.all_nodes_with_type(m, self._node_type)
                 for node in nodes_to_delete:
                     self.execute(m, node, local=True)
 
@@ -83,9 +91,9 @@ class NodeRetype(object):
             mv.transformation_execute_MANUAL("graph_ops/retype_node", {"gm":model}, {"gm":model},
                                              callback=self._callback)
         else:
-            node_type = get_node_type(model, node)
-            for m in all_models():
-                for node in all_nodes_with_type(m, node_type):
+            node_type = commons.get_node_type(model, node)
+            for m in commons.all_models():
+                for node in commons.all_nodes_with_type(m, node_type):
                     self.execute(m, node, new_type, local=True)
 
     def _callback(self, model):

+ 17 - 7
sketchUI/exm_mainwindow.py

@@ -10,7 +10,7 @@ from sketchUI.graphics_node_item import GraphicsNodeItem
 from sketchUI.graphics_edge_item import GraphicsEdgeItem
 from wrappers.modelverse import element_list_nice
 import commons
-from evolution.node_ops import NodeDelete
+from evolution.node_ops import NodeDelete, NodeAdd
 from evolution.attribute_ops import AttributeDelete
 
 
@@ -260,10 +260,20 @@ class EXMMainWindow(QMainWindow, Ui_MainWindow):
 
     def _on_list_item_clicked(self, event):
         # add node to model
-        node_id = mvops.add_node(self._cur_model, event.text())
-        # render node
-        self._add_node_to_scene(node_id, event.text())
-        self.plainTextEdit.appendPlainText("Added node of type {} to model".format(event.text()))
+        self.plainTextEdit.appendPlainText("Adding node of type {} to model".format(event.text()))
+        add_handler = NodeAdd()
+
+        # ask if global or local delete
+        scope, ok = QInputDialog.getItem(self.parent(), "Select scope", "Scope", ["Local", "Global"])
+        if not ok:
+            return
+
+        if scope == "Global":
+            add_handler.execute(self._cur_model, event.text(), local=False, check_if_last=False)
+        else:
+            add_handler.execute(self._cur_model, event.text(), local=True, check_if_last=True)
+        # render node by reloading the whole model (since we cannot get the node id if the newly added model
+        self._load_model()
 
     def _on_attribute_edited(self, item):
         # type: (QTableWidgetItem) -> None
@@ -278,7 +288,7 @@ class EXMMainWindow(QMainWindow, Ui_MainWindow):
 
         if not attr_val:
             # ask if global or local delete
-            scope, ok = QInputDialog.getItem(self._parent, "Select scope", "Scope", ["Local", "Global"])
+            scope, ok = QInputDialog.getItem(self.parent(), "Select scope", "Scope", ["Local", "Global"])
             if not ok:
                 return
             del_handler = AttributeDelete()
@@ -287,7 +297,7 @@ class EXMMainWindow(QMainWindow, Ui_MainWindow):
             if scope == "Global":
                 del_handler.execute(self._cur_model, node.node_id, attr_key, local=False)
             else:
-                del_handler.execute(self._cur_model, node.node_id, attr_key, local=True)
+                del_handler.execute(self._cur_model, node.node_id, attr_key, local=True, check_if_last=True)
 
             # update view
             self.tableWidget.removeRow(row)

+ 2 - 2
sketchUI/exm_scene.py

@@ -308,9 +308,9 @@ class SketchScene(QGraphicsScene):
         # add the node to the model
         add_handler = NodeAdd()
         if scope == "Global":
-            add_handler.execute(self._cur_model, node_type, local=False)
+            add_handler.execute(self._cur_model, node_type, local=False, check_if_last=False)
         else:
-            add_handler.execute(self._cur_model, node_type, local=True)
+            add_handler.execute(self._cur_model, node_type, local=True, check_if_last=True)
         # Get node id of newly added node in current model
         nodeid = commons.all_nodes_with_type(self._cur_model, node_type)[0]
 

+ 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 11:42:36 2018
+Date:   Sun May  6 17:04:56 2018
 
 Model author: Yentl Van Tendeloo
 Model name:   MvK Server