123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- 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
- self._example_models = None
- self._available_types = None
- def set_instance_model(self, model):
- self._instance_model = model
- def get_instance_model(self):
- return self._instance_model
- def init(self):
- """
- Inits the verifier. Loads required data from the modelverse
- to avoid reloading it for every step.
- """
- self._example_models = commons.all_example_models()
- self._available_types = commons.get_available_types()
- def verify_node_typing(self):
- """
- Checks if every node in instance model is typed by a node in any example model.
- Should actually not be neccessary since instance modeling only allows types from
- example models.
- """
- # Check for every node in instance model if it has a valid type
- all_nodes = mv.all_instances(self._instance_model, "Node")
- for node in all_nodes:
- node_typ = commons.get_node_type(self._instance_model, node)
- if node_typ not in self._available_types:
- return {"OK": False, "error": "Type {} from instance model not in example models".format(node_typ)}
- return {"OK":True, "error":None, "affected":[]}
- def verify_node_multiplicity(self):
- """
- A node is mandatory in the instance model if it occurs in every example model.
- More specifically, if a node type appears occ = [n0, n1, ... n_i] times in example model i,
- it needs to occur min(occ) in the instance model.
- """
- total_occurences = {k: [] for k in self._available_types}
- for node, occ_list in total_occurences.iteritems():
- for exm in self._example_models:
- num_occ = commons.count_occurences(node, exm)
- occ_list.append(num_occ)
- elem_cardinality = {k: min(v) for k, v in total_occurences.iteritems()}
- for el, min_occ in elem_cardinality.iteritems():
- if not commons.count_occurences(el, self._instance_model) >= min_occ:
- return {"OK": False, "error": "Node with type {} needs to occur at least {} times".format(el, min_occ)}
- 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, ...], ...},
- listing all attributes of every type from example models
- """
- attrs_of_types = {node_type:[] for node_type in self._available_types}
- for exm in self._example_models:
- for node_type, attrs in attrs_of_types.iteritems():
- attrs_of_type_in_exm = [x.key for x in commons.get_all_attributes_of_type(exm, node_type)]
- for attr in attrs_of_type_in_exm:
- if not attr in attrs:
- 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
- attribute key in some example model.
- 2. An attribute for a type is mandatory if it occurs in every example model.
- TODO: Would make more sense to change to: An attribute is mandatory if it occurs in every example model where the
- associated typed node occurs (as explained in the thesis text).
- """
- # Check attribute keys for every node in instance model
- all_nodes = mv.all_instances(self._instance_model, "Node")
- for node in all_nodes:
- attrs = commons.get_attributes_of_node(self._instance_model, node)
- if not attrs:
- continue
- node_typ = commons.get_node_type(self._instance_model, node)
- for attr in attrs:
- # check every example model if such an attribute key is present for the specific type
- for exm in self._example_models:
- exm_attrs = commons.get_all_attributes_of_type(exm, node_typ)
- if attr.key in [x.key for x in exm_attrs]:
- break
- else:
- # loop completed without break, so we did not find any key in example models
- return {"OK": False, "error": "No key {} for type {} in example models".format(attr.key, node_typ),
- "affected":[node]}
- # Check if mandatory attributes are present in instance model
- attr_mandatory = {node_type:{} for node_type in self._available_types}
- all_attrs = self._get_attributes_of_all_types()
- for typ_i, attr_list in all_attrs.iteritems():
- for typ_j, dic in attr_mandatory.iteritems():
- if typ_j == typ_i:
- for attr in attr_list:
- dic[attr] = False
- # have dict like {"PC": {"owner":False}, "Router": {"IP":False}, ...}
- # 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):
- attr_dict[attr] = True
- # for every node in instance model, check if it has the mandatory attributes
- for node in mv.all_instances(self._instance_model, "Node"):
- node_type = commons.get_node_type(self._instance_model, node)
- node_attrs = commons.get_attributes_of_node(self._instance_model, node)
- attr_mand = attr_mandatory[node_type]
- for attr, mand in attr_mand.iteritems():
- if mand:
- if not attr in [x.key for x in node_attrs]:
- return {"OK": False, "error":"Attribute {} for type {} mandatory".format(attr, node_type),
- "affected":[node]}
- return {"OK": True, "error": None, "affected":[]}
- def verify_associations(self):
- """
- 1. An edge between two types is mandatory if in every example model that contains nodes of both types, every
- node is connected with an edge to the other type.
- 2. For every association in the instance model, the types of the source and target must correspond
- to the types of an association in some example model. This check should not be necessary since this
- 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]
- # check if instance model contains the types and the edge
- for edge in mandatory_edges:
- if not commons.model_contains_type(self._instance_model, edge.n1):
- continue
- if not commons.model_contains_type(self._instance_model, edge.n2):
- continue
- # instance model contains both types -> are they connected?
- for node in commons.all_nodes_with_type(self._instance_model, edge.n1):
- if not commons.has_edge_to_type(self._instance_model, node, edge.n2):
- return {"OK":False, "error": "Edge between {} and {} mandatory".format(edge.n1, edge.n2),
- "affected":[]}
- # other way round
- for node in commons.all_nodes_with_type(self._instance_model, edge.n2):
- if not commons.has_edge_to_type(self._instance_model, node, edge.n1):
- return {"OK":False, "error": "Edge between {} and {} mandatory".format(edge.n2, edge.n1),
- "affected":[]}
- # lastly, check if all edges in the instance model are actually valid
- all_edges = mv.all_instances(self._instance_model, "Edge")
- for edge in all_edges:
- src_id = mv.read_association_source(self._instance_model, edge)[0]
- dest_id = mv.read_association_destination(self._instance_model, edge)[0]
- src_type = commons.get_node_type(self._instance_model, src_id)
- dest_type = commons.get_node_type(self._instance_model, dest_id)
- if not commons.is_edge_supported(src_type, dest_type):
- return {"OK":False, "error": "Edge between {} and {} not valid".format(edge.n1, edge.n2),
- "affected":[src_id, dest_id]}
- return {"OK": True, "error": None, "affected":[]}
|