From a926de1998ff910af9986df39c686ee135080593 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Thu, 12 Sep 2024 17:10:18 +0200 Subject: [PATCH] PlantUML: also render 'matchedWith' traceability links between attributes --- renderer/plantuml.py | 70 +++++++++++++++++++++++++++----------- services/od.py | 47 ++++++++++++++----------- services/scd.py | 1 - transformation/rewriter.py | 15 ++------ 4 files changed, 81 insertions(+), 52 deletions(-) diff --git a/renderer/plantuml.py b/renderer/plantuml.py index abfdff7..190c647 100644 --- a/renderer/plantuml.py +++ b/renderer/plantuml.py @@ -1,6 +1,7 @@ from services import scd, od from services.bottom.V0 import Bottom from transformation import ramify +import json def render_class_diagram(state, model): bottom = Bottom(state) @@ -11,14 +12,19 @@ def render_class_diagram(state, model): # Render classes for name, class_node in model_scd.get_classes().items(): - if model_od.read_slot_boolean(class_node, "abstract"): + is_abstract = False + slot = model_od.get_slot(class_node, "abstract") + if slot != None: + is_abstract = od.read_primitive_value(bottom, slot, model_od.type_model) + + if is_abstract: output += f"\nabstract class \"{name}\" as {make_id(class_node)}" else: output += f"\nclass \"{name}\" as {make_id(class_node)}" # Render attributes output += " {" - for (attr_name, attr_edge) in od.get_attributes(bottom, class_node): + for attr_name, attr_edge in od.get_attributes(bottom, class_node): tgt_name = model_scd.get_class_name(bottom.read_edge_target(attr_edge)) output += f"\n {attr_name} : {tgt_name}" output += "\n}" @@ -29,8 +35,6 @@ def render_class_diagram(state, model): for inh_node in model_scd.get_inheritances().values(): src_node = bottom.read_edge_source(inh_node) tgt_node = bottom.read_edge_target(inh_node) - # src_name = model_scd.get_class_name(bottom.read_edge_source(inh_node)) - # tgt_name = model_scd.get_class_name(bottom.read_edge_target(inh_node)) output += f"\n{make_id(tgt_node)} <|-- {make_id(src_node)}" output += "\n" @@ -39,8 +43,6 @@ def render_class_diagram(state, model): for assoc_name, assoc_edge in model_scd.get_associations().items(): src_node = bottom.read_edge_source(assoc_edge) tgt_node = bottom.read_edge_target(assoc_edge) - # src_name = model_scd.get_class_name(bottom.read_edge_source(assoc_edge)) - # tgt_name = model_scd.get_class_name(bottom.read_edge_target(assoc_edge)) src_lower_card, src_upper_card, tgt_lower_card, tgt_upper_card = model_scd.get_assoc_cardinalities(assoc_edge) @@ -67,7 +69,7 @@ def render_class_diagram(state, model): return output -def render_object_diagram(state, m, mm): +def render_object_diagram(state, m, mm, render_attributes=True): bottom = Bottom(state) mm_scd = scd.SCD(mm, state) m_od = od.OD(mm, m, state) @@ -76,8 +78,18 @@ def render_object_diagram(state, m, mm): # Render objects for class_name, class_node in mm_scd.get_classes().items(): + if render_attributes: + attributes = od.get_attributes(bottom, class_node) + for obj_name, obj_node in m_od.get_objects(class_node).items(): - output += f"\nobject \"{obj_name} : {class_name}\" as {make_id(obj_node)}" + output += f"\nmap \"{obj_name} : {class_name}\" as {make_id(obj_node)} {{" + + if render_attributes: + for attr_name, attr_edge in attributes: + slot = m_od.get_slot(obj_node, attr_name) + if slot != None: + output += f"\n{attr_name} => {json.dumps(od.read_primitive_value(bottom, slot, mm))}" + output += '\n}' output += '\n' @@ -100,7 +112,7 @@ def render_package(name, contents): output += '\n}' return output -def render_trace_ramifies(state, mm, ramified_mm): +def render_trace_ramifies(state, mm, ramified_mm, render_attributes=True): bottom = Bottom(state) mm_scd = scd.SCD(mm, state) @@ -114,18 +126,19 @@ def render_trace_ramifies(state, mm, ramified_mm): original_name = mm_scd.get_class_name(original_class) output += f"\n{make_id(ram_class_node)} ..> {make_id(original_class)} #line:green;text:green : RAMifies" - # and between attributes - for (ram_attr_name, ram_attr_edge) in od.get_attributes(bottom, ram_class_node): - orig_attr_edge, = bottom.read_outgoing_elements(ram_attr_edge, ramify.RAMIFIES_LABEL) - orig_class_node = bottom.read_edge_source(orig_attr_edge) - # dirty AF: - orig_attr_name = mm_scd.get_class_name(orig_attr_edge)[len(original_name)+1:] - output += f"\n{make_id(ram_class_node)}::{ram_attr_name} ..> {make_id(orig_class_node)}::{orig_attr_name} #line:green;text:green : RAMifies" + if render_attributes: + # and between attributes + for (ram_attr_name, ram_attr_edge) in od.get_attributes(bottom, ram_class_node): + orig_attr_edge, = bottom.read_outgoing_elements(ram_attr_edge, ramify.RAMIFIES_LABEL) + orig_class_node = bottom.read_edge_source(orig_attr_edge) + # dirty AF: + orig_attr_name = mm_scd.get_class_name(orig_attr_edge)[len(original_name)+1:] + output += f"\n{make_id(ram_class_node)}::{ram_attr_name} ..> {make_id(orig_class_node)}::{orig_attr_name} #line:green;text:green : RAMifies" return output -def render_trace_conformance(state, m, mm): +def render_trace_conformance(state, m, mm, render_attributes=True): bottom = Bottom(state) mm_scd = scd.SCD(mm, state) m_od = od.OD(mm, m, state) @@ -134,9 +147,19 @@ def render_trace_conformance(state, m, mm): # Render objects for class_name, class_node in mm_scd.get_classes().items(): + + if render_attributes: + attributes = od.get_attributes(bottom, class_node) + for obj_name, obj_node in m_od.get_objects(class_node).items(): output += f"\n{make_id(obj_node)} ..> {make_id(class_node)} #line:blue;text:blue : instanceOf" + if render_attributes: + for attr_name, attr_edge in attributes: + slot = m_od.get_slot(obj_node, attr_name) + if slot != None: + output += f"\n{make_id(obj_node)}::{attr_name} ..> {make_id(class_node)}::{attr_name} #line:blue;text:blue : instanceOf" + output += '\n' return output @@ -144,14 +167,23 @@ def render_trace_conformance(state, m, mm): def render_trace_match(state, mapping): bottom = Bottom(state) class_type = od.get_scd_mm_class_node(bottom) + attr_link_type = od.get_scd_mm_attributelink_node(bottom) output = "" for pattern_el, host_el in mapping.items(): # only render 'match'-edges between objects (= those elements where the type of the type is 'Class'): - if od.get_type(bottom, od.get_type(bottom, pattern_el)) == class_type: + pattern_el_type = od.get_type(bottom, pattern_el) + pattern_el_type_type = od.get_type(bottom, pattern_el_type) + if pattern_el_type_type == class_type: output += f"\n{make_id(pattern_el)} ..> {make_id(host_el)} #line:grey;text:grey : matchedWith" - + elif pattern_el_type_type == attr_link_type: + pattern_obj = bottom.read_edge_source(pattern_el) + pattern_attr_name = od.get_attr_name(bottom, pattern_el_type) + host_obj = bottom.read_edge_source(host_el) + host_el_type = od.get_type(bottom, host_el) + host_attr_name = od.get_attr_name(bottom, host_el_type) + output += f"\n{make_id(pattern_obj)}::{pattern_attr_name} ..> {make_id(host_obj)}::{host_attr_name} #line:grey;text:grey : matchedWith" return output def make_id(uuid) -> str: diff --git a/services/od.py b/services/od.py index 73f369a..e12b8b4 100644 --- a/services/od.py +++ b/services/od.py @@ -39,9 +39,12 @@ class OD: get_scd_mm(self.bottom), # the type model of our type model self.type_model, self.bottom.state) - is_abstract = mm_od.read_slot_boolean(class_node, "abstract") - if is_abstract: - raise Exception("Cannot instantiate abstract class!") + + slot = mm_od.get_slot(class_node, "abstract") + if slot != None: + is_abstract = read_primitive_value(self.bottom, slot, self.type_model) + if is_abstract: + raise Exception("Cannot instantiate abstract class!") object_node = self.bottom.create_node() self.bottom.create_edge(self.model, object_node, name) # attach to model @@ -49,10 +52,10 @@ class OD: return object_node - def read_slot_boolean(self, obj_node: str, attr_name: str): - slot = self.get_slot(obj_node, attr_name) - if slot != None: - return Boolean(slot, self.bottom.state).read() + # def read_slot_boolean(self, obj_node: str, attr_name: str): + # slot = self.get_slot(obj_node, attr_name) + # if slot != None: + # return Boolean(slot, self.bottom.state).read() def get_class_of_object(self, object_name: str): object_node, = self.bottom.read_outgoing_elements(self.model, object_name) # get the object @@ -82,8 +85,9 @@ class OD: for outgoing_edge in self.bottom.read_outgoing_edges(object_node): if type_edge in self.bottom.read_outgoing_elements(outgoing_edge, "Morphism"): slot_ref = self.bottom.read_edge_target(outgoing_edge) - slot_node = UUID(self.bottom.read_value(slot_ref)) - return slot_node + return slot_ref + # slot_node = UUID(self.bottom.read_value(slot_ref)) + # return slot_node def create_integer_value(self, name: str, value: int): from services.primitives.integer_type import Integer @@ -245,23 +249,24 @@ def find_cardinality(bottom, class_node: UUID, type_node: UUID): def get_attributes(bottom, class_node: UUID): attr_link_node = get_scd_mm_attributelink_node(bottom) - attr_link_name_node = get_scd_mm_attributelink_name_node(bottom) attr_edges = find_outgoing_typed_by(bottom, class_node, attr_link_node) result = [] for attr_edge in attr_edges: - name_edge, = find_outgoing_typed_by(bottom, attr_edge, attr_link_name_node) - if name_edge == None: - raise Exception("Expected attribute to have a name...") - ref_name = bottom.read_edge_target(name_edge) - string, = bottom.read_outgoing_elements( - navigate_modelref(bottom, ref_name), - "string") - attr_name = bottom.read_value(string) - # ref_type = bottom.read_edge_target(attr_edge) - # typ = navigate_modelref(bottom, ref_type) + attr_name = get_attr_name(bottom, attr_edge) result.append((attr_name, attr_edge)) return result +def get_attr_name(bottom, attr_edge: UUID): + attr_link_name_node = get_scd_mm_attributelink_name_node(bottom) + name_edge, = find_outgoing_typed_by(bottom, attr_edge, attr_link_name_node) + if name_edge == None: + raise Exception("Expected attribute to have a name...") + ref_name = bottom.read_edge_target(name_edge) + string, = bottom.read_outgoing_elements( + navigate_modelref(bottom, ref_name), + "string") + return bottom.read_value(string) + # We need the meta-model (`mm`) to find out how to read the `modelref` def read_primitive_value(bottom, modelref: UUID, mm: UUID): typ = get_type(bottom, modelref) @@ -273,6 +278,8 @@ def read_primitive_value(bottom, modelref: UUID, mm: UUID): return Integer(referred_model, bottom.state).read() elif typ_name == "String": return String(referred_model, bottom.state).read() + elif typ_name == "Boolean": + return Boolean(referred_model, bottom.state).read() else: raise Exception("Unimplemented type:", host_type_name) diff --git a/services/scd.py b/services/scd.py index 11292d1..1233c46 100644 --- a/services/scd.py +++ b/services/scd.py @@ -326,7 +326,6 @@ class SCD: # mapping from instance name to UUID return name_to_instance - def get_attributes(self, class_name: str): class_node, = self.bottom.read_outgoing_elements(self.model, class_name) return self._get_attributes(class_node) diff --git a/transformation/rewriter.py b/transformation/rewriter.py index d9b9238..f902c3d 100644 --- a/transformation/rewriter.py +++ b/transformation/rewriter.py @@ -136,8 +136,6 @@ def rewrite(state, lhs: UUID, rhs: UUID, rhs_mm: UUID, match_mapping: dict, m_to model_element_name = match_mapping[pattern_element_name] print('updating', model_element_name) model_element, = bottom.read_outgoing_elements(m_to_transform, model_element_name) - # old_value = bottom.read_value(model_element) - # print('old value:', old_value) host_type = od.get_type(bottom, model_element) if od.is_typed_by(bottom, host_type, class_type): print(' -> is classs') @@ -145,21 +143,14 @@ def rewrite(state, lhs: UUID, rhs: UUID, rhs_mm: UUID, match_mapping: dict, m_to print(' -> is attr link') elif od.is_typed_by(bottom, host_type, modelref_type): print(' -> is modelref') - referred_model_id = UUID(bottom.read_value(model_element)) - # referred_model_type = od.get_type(bottom, referred_model_id) # None - # print('referred_model_type:', referred_model_type) - - v = od.read_primitive_value(bottom, model_element, mm) - - # the referred model itself doesn't have a type, so we have to look at the type of the ModelRef element in the RHS-MM: + old_value = od.read_primitive_value(bottom, model_element, mm) rhs_element, = bottom.read_outgoing_elements(rhs, pattern_element_name) - expr = od.read_primitive_value(bottom, rhs_element, rhs_mm) - - result = eval(expr, {}, {'v': v}) + result = eval(expr, {}, {'v': old_value}) # print('eval result=', result) if isinstance(result, int): # overwrite the old value + referred_model_id = UUID(bottom.read_value(model_element)) Integer(referred_model_id, state).create(result) else: raise Exception("Unimplemented type. Value:", result)