diff --git a/experiments/exp_plantuml.py b/experiments/exp_plantuml.py new file mode 100644 index 0000000..2f14472 --- /dev/null +++ b/experiments/exp_plantuml.py @@ -0,0 +1,36 @@ +from state.devstate import DevState +from bootstrap.scd import bootstrap_scd +from uuid import UUID +from services.scd import SCD +from framework.conformance import Conformance +from services.od import OD +from transformation.ramify import ramify +from transformation import rewriter +from services.bottom.V0 import Bottom +from services.primitives.integer_type import Integer +from pattern_matching import mvs_adapter +from pattern_matching.matcher import MatcherVF2 +from renderer import plantuml + +def main(): + state = DevState() + root = state.read_root() # id: 0 + + scd_mm_id = bootstrap_scd(state) + + uml = "" + + # Render SCD Meta-Model as Object Diagram + uml += plantuml.render_package("Object Diagram", plantuml.render_object_diagram(state, scd_mm_id, scd_mm_id, prefix_ids="od_")) + + # Render SCD Meta-Model as Class Diagram + uml += plantuml.render_package("Class Diagram", plantuml.render_class_diagram(state, scd_mm_id, prefix_ids="cd_")) + + # Render conformance + uml += plantuml.render_trace_conformance(state, scd_mm_id, scd_mm_id, prefix_inst_ids="od_", prefix_type_ids="cd_") + + print(uml) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/experiments/exp_scd.py b/experiments/exp_scd.py index d9a2e51..1a9bc7f 100644 --- a/experiments/exp_scd.py +++ b/experiments/exp_scd.py @@ -149,47 +149,91 @@ def main(): print(guest.vtxs) print(guest.edges) - print("matching...") - matcher = MatcherVF2(host, guest, mvs_adapter.RAMCompare(Bottom(state), dsl_m_od)) - for m in matcher.match(): - print("\nMATCH:\n", m) - name_mapping = {} - id_mapping = {} - for guest_vtx, host_vtx in m.mapping_vtxs.items(): - if isinstance(guest_vtx, mvs_adapter.NamedNode) and isinstance(host_vtx, mvs_adapter.NamedNode): - id_mapping[guest_vtx.node_id] = host_vtx.node_id - name_mapping[guest_vtx.name] = host_vtx.name - print(name_mapping) - #rewriter.rewrite(state, lhs_id, rhs_id, ramified_mm_id, name_mapping, dsl_m_id, dsl_mm_id) - break - print("DONE") + def render_ramification(): + uml = ("" + # Render original and RAMified meta-models + + plantuml.render_package("Meta-Model", plantuml.render_class_diagram(state, dsl_mm_id)) + + plantuml.render_package("RAMified Meta-Model", plantuml.render_class_diagram(state, ramified_mm_id)) + + # Render RAMification traceability links + + plantuml.render_trace_ramifies(state, dsl_mm_id, ramified_mm_id) + ) + + # Render pattern + uml += plantuml.render_package("LHS", plantuml.render_object_diagram(state, lhs_id, ramified_mm_id)) + uml += plantuml.render_trace_conformance(state, lhs_id, ramified_mm_id) + + # Render pattern + uml += plantuml.render_package("RHS", plantuml.render_object_diagram(state, rhs_id, ramified_mm_id)) + uml += plantuml.render_trace_conformance(state, rhs_id, ramified_mm_id) + + return uml + + def render_all_matches(): + uml = render_ramification() + # Render host graph (before rewriting) + uml += plantuml.render_package("Model (before rewrite)", plantuml.render_object_diagram(state, dsl_m_id, dsl_mm_id)) + # Render conformance + uml += plantuml.render_trace_conformance(state, dsl_m_id, dsl_mm_id) + + print("matching...") + matcher = MatcherVF2(host, guest, mvs_adapter.RAMCompare(Bottom(state), dsl_m_od)) + for m, color in zip(matcher.match(), ["red", "orange"]): + print("\nMATCH:\n", m) + name_mapping = {} + # id_mapping = {} + for guest_vtx, host_vtx in m.mapping_vtxs.items(): + if isinstance(guest_vtx, mvs_adapter.NamedNode) and isinstance(host_vtx, mvs_adapter.NamedNode): + # id_mapping[guest_vtx.node_id] = host_vtx.node_id + name_mapping[guest_vtx.name] = host_vtx.name + print(name_mapping) + + # Render every match + uml += plantuml.render_trace_match(state, name_mapping, lhs_id, dsl_m_id, color) + + print("DONE") + return uml + + def render_rewrite(): + uml = render_ramification() + + matcher = MatcherVF2(host, guest, mvs_adapter.RAMCompare(Bottom(state), dsl_m_od)) + for m in matcher.match(): + name_mapping = {} + # id_mapping = {} + for guest_vtx, host_vtx in m.mapping_vtxs.items(): + if isinstance(guest_vtx, mvs_adapter.NamedNode) and isinstance(host_vtx, mvs_adapter.NamedNode): + # id_mapping[guest_vtx.node_id] = host_vtx.node_id + name_mapping[guest_vtx.name] = host_vtx.name + print(name_mapping) + + + rewriter.rewrite(state, lhs_id, rhs_id, ramified_mm_id, name_mapping, dsl_m_id, dsl_mm_id) + + # Render match + uml_match = plantuml.render_trace_match(state, name_mapping, rhs_id, dsl_m_id) + + # Stop matching after rewrite + break + + # Render host graph (after rewriting) + uml += plantuml.render_package("Model (after rewrite)", plantuml.render_object_diagram(state, dsl_m_id, dsl_mm_id)) + # Render conformance + uml += plantuml.render_trace_conformance(state, dsl_m_id, dsl_mm_id) + + uml += uml_match + + return uml conf5 = Conformance(state, dsl_m_id, dsl_mm_id) print("Updated model conforms?", conf5.check_nominal(log=True)) + print() + print("==============================================") + print(render_all_matches()) + # print(render_rewrite()) - # Render original and RAMified meta-models - print(plantuml.render_package("Meta-Model", plantuml.render_class_diagram(state, dsl_mm_id))) - print(plantuml.render_package("RAMified Meta-Model", plantuml.render_class_diagram(state, ramified_mm_id))) - - # Render RAMification traceability links - print(plantuml.render_trace_ramifies(state, dsl_mm_id, ramified_mm_id)) - - # Render host graph - print(plantuml.render_package("Model", plantuml.render_object_diagram(state, dsl_m_id, dsl_mm_id))) - - # Render conformance host -> MM - print(plantuml.render_trace_conformance(state, dsl_m_id, dsl_mm_id)) - - # Render pattern - print(plantuml.render_package("LHS", plantuml.render_object_diagram(state, lhs_id, ramified_mm_id))) - - # Render pattern -> RAM-MM - print(plantuml.render_trace_conformance(state, lhs_id, ramified_mm_id)) - - print(plantuml.render_trace_match(state, id_mapping)) - if __name__ == "__main__": main() diff --git a/renderer/plantuml.py b/renderer/plantuml.py index 190c647..1d0cf55 100644 --- a/renderer/plantuml.py +++ b/renderer/plantuml.py @@ -2,12 +2,16 @@ from services import scd, od from services.bottom.V0 import Bottom from transformation import ramify import json +from uuid import UUID -def render_class_diagram(state, model): +def render_class_diagram(state, model, prefix_ids=""): bottom = Bottom(state) model_scd = scd.SCD(model, state) model_od = od.OD(od.get_scd_mm(bottom), model, state) + def make_id(uuid) -> str: + return prefix_ids+str(uuid).replace('-','_') + output = "" # Render classes @@ -69,11 +73,14 @@ def render_class_diagram(state, model): return output -def render_object_diagram(state, m, mm, render_attributes=True): +def render_object_diagram(state, m, mm, render_attributes=True, prefix_ids=""): bottom = Bottom(state) mm_scd = scd.SCD(mm, state) m_od = od.OD(mm, m, state) + def make_id(uuid) -> str: + return prefix_ids+str(uuid).replace('-','_') + output = "" # Render objects @@ -101,7 +108,7 @@ def render_object_diagram(state, m, mm, render_attributes=True): src_name = m_od.get_object_name(src_obj) tgt_name = m_od.get_object_name(tgt_obj) - output += f"\n{make_id(src_obj)} -> {make_id(tgt_obj)} : {assoc_name}" + output += f"\n{make_id(src_obj)} -> {make_id(tgt_obj)} : :{assoc_name}" return output @@ -112,19 +119,24 @@ def render_package(name, contents): output += '\n}' return output -def render_trace_ramifies(state, mm, ramified_mm, render_attributes=True): +def render_trace_ramifies(state, mm, ramified_mm, render_attributes=True, prefix_ram_ids="", prefix_orig_ids=""): bottom = Bottom(state) mm_scd = scd.SCD(mm, state) ramified_mm_scd = scd.SCD(ramified_mm, state) + def make_ram_id(uuid) -> str: + return prefix_ram_ids+str(uuid).replace('-','_') + def make_orig_id(uuid) -> str: + return prefix_orig_ids+str(uuid).replace('-','_') + output = "" # Render RAMifies-edges between classes for ram_name, ram_class_node in ramified_mm_scd.get_classes().items(): original_class, = bottom.read_outgoing_elements(ram_class_node, ramify.RAMIFIES_LABEL) 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" + output += f"\n{make_ram_id(ram_class_node)} ..> {make_orig_id(original_class)} #line:green;text:green : RAMifies" if render_attributes: # and between attributes @@ -133,16 +145,21 @@ def render_trace_ramifies(state, mm, ramified_mm, render_attributes=True): 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" + output += f"\n{make_ram_id(ram_class_node)}::{ram_attr_name} ..> {make_orig_id(orig_class_node)}::{orig_attr_name} #line:green;text:green : RAMifies" return output -def render_trace_conformance(state, m, mm, render_attributes=True): +def render_trace_conformance(state, m, mm, render_attributes=True, prefix_inst_ids="", prefix_type_ids=""): bottom = Bottom(state) mm_scd = scd.SCD(mm, state) m_od = od.OD(mm, m, state) + def make_inst_id(uuid) -> str: + return prefix_inst_ids+str(uuid).replace('-','_') + def make_type_id(uuid) -> str: + return prefix_type_ids+str(uuid).replace('-','_') + output = "" # Render objects @@ -152,39 +169,50 @@ def render_trace_conformance(state, m, mm, render_attributes=True): 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" + output += f"\n{make_inst_id(obj_node)} ..> {make_type_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 += f"\n{make_inst_id(obj_node)}::{attr_name} ..> {make_type_id(class_node)}::{attr_name} #line:blue;text:blue : instanceOf" output += '\n' return output -def render_trace_match(state, mapping): +def render_trace_match(state, name_mapping: dict, pattern_m: UUID, host_m: UUID, color="grey", prefix_pattern_ids="", prefix_host_ids=""): bottom = Bottom(state) class_type = od.get_scd_mm_class_node(bottom) attr_link_type = od.get_scd_mm_attributelink_node(bottom) + def make_pattern_id(uuid) -> str: + return prefix_pattern_ids+str(uuid).replace('-','_') + def make_host_id(uuid) -> str: + return prefix_host_ids+str(uuid).replace('-','_') + output = "" - for pattern_el, host_el in mapping.items(): + render_suffix = f"#line:{color};line.dotted;text:{color} : matchedWith" + + for pattern_el_name, host_el_name in name_mapping.items(): + print(pattern_el_name, host_el_name) + try: + pattern_el, = bottom.read_outgoing_elements(pattern_m, pattern_el_name) + host_el, = bottom.read_outgoing_elements(host_m, host_el_name) + except: + continue # only render 'match'-edges between objects (= those elements where the type of the type is 'Class'): 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" + output += f"\n{make_pattern_id(pattern_el)} ..> {make_host_id(host_el)} {render_suffix}" 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" + output += f"\n{make_pattern_id(pattern_obj)}::{pattern_attr_name} ..> {make_host_id(host_obj)}::{host_attr_name} {render_suffix}" return output -def make_id(uuid) -> str: - return (str(uuid).replace('-','_')) \ No newline at end of file diff --git a/transformation/rewriter.py b/transformation/rewriter.py index f902c3d..b77e3bb 100644 --- a/transformation/rewriter.py +++ b/transformation/rewriter.py @@ -23,7 +23,9 @@ def process_rule(state, lhs: UUID, rhs: UUID): return to_delete, to_create, common -def rewrite(state, lhs: UUID, rhs: UUID, rhs_mm: UUID, match_mapping: dict, m_to_transform: UUID, mm: UUID) -> UUID: +# Rewrite is performed in-place +# Also updates the mapping in-place, to become RHS -> host +def rewrite(state, lhs: UUID, rhs: UUID, rhs_mm: UUID, match_mapping: dict, m_to_transform: UUID, mm: UUID) -> dict: bottom = Bottom(state) scd_metamodel_id = state.read_dict(state.read_root(), "SCD") @@ -50,8 +52,10 @@ def rewrite(state, lhs: UUID, rhs: UUID, rhs_mm: UUID, match_mapping: dict, m_to element_to_delete, = bottom.read_outgoing_elements(m_to_transform, model_element_name_to_delete) # Delete bottom.delete_element(element_to_delete) + # Remove from mapping + del match_mapping[pattern_name_to_delete] - extended_mapping = dict(match_mapping) # will be extended with created elements + # extended_mapping = dict(match_mapping) # will be extended with created elements edges_to_create = [] # postpone creation of edges after creation of nodes # Perform creations @@ -74,7 +78,7 @@ def rewrite(state, lhs: UUID, rhs: UUID, rhs_mm: UUID, match_mapping: dict, m_to # It's type is typed by Class -> it's an object print(' -> creating object') o = m_od._create_object(model_element_name_to_create, original_type) - extended_mapping[pattern_name_to_create] = model_element_name_to_create + match_mapping[pattern_name_to_create] = model_element_name_to_create elif od.is_typed_by(bottom, original_type, attr_link_type): print(' -> postpone (is attribute link)') edges_to_create.append((pattern_name_to_create, rhs_element_to_create, original_type, 'attribute link', rhs_type, model_element_name_to_create)) @@ -98,10 +102,10 @@ def rewrite(state, lhs: UUID, rhs: UUID, rhs_mm: UUID, match_mapping: dict, m_to m_od.create_integer_value(model_element_name_to_create, result) elif isinstance(result, str): m_od.create_string_value(model_element_name_to_create, result) - extended_mapping[pattern_name_to_create] = model_element_name_to_create + match_mapping[pattern_name_to_create] = model_element_name_to_create - print('extended_mapping:', extended_mapping) + print('match_mapping:', match_mapping) print("create edges....") for pattern_name_to_create, rhs_element_to_create, original_type, original_type_name, rhs_type, model_element_name_to_create in edges_to_create: @@ -112,24 +116,24 @@ def rewrite(state, lhs: UUID, rhs: UUID, rhs_mm: UUID, match_mapping: dict, m_to src_name = od.get_object_name(bottom, rhs, src) tgt = bottom.read_edge_target(rhs_element_to_create) tgt_name = od.get_object_name(bottom, rhs, tgt) - obj_name = extended_mapping[src_name] # name of object in host graph to create slot for + obj_name = match_mapping[src_name] # name of object in host graph to create slot for attr_name = od.get_object_name(bottom, mm, original_type) class_name = m_od.get_class_of_object(obj_name) # Just when you thought the code couldn't get any dirtier: attribute_name = attr_name[len(class_name)+1:] - # print(attribute_name, obj_name, extended_mapping[tgt_name]) - m_od.create_slot(attribute_name, obj_name, extended_mapping[tgt_name]) + # print(attribute_name, obj_name, match_mapping[tgt_name]) + m_od.create_slot(attribute_name, obj_name, match_mapping[tgt_name]) elif original_type_name == 'link': print(' -> creating link') src = bottom.read_edge_source(rhs_element_to_create) src_name = od.get_object_name(bottom, rhs, src) tgt = bottom.read_edge_target(rhs_element_to_create) tgt_name = od.get_object_name(bottom, rhs, tgt) - obj_name = extended_mapping[src_name] # name of object in host graph to create slot for + obj_name = match_mapping[src_name] # name of object in host graph to create slot for attr_name = od.get_object_name(bottom, mm, original_type) class_name = m_od.get_class_of_object(obj_name) - # print(attr_name, obj_name, extended_mapping[tgt_name]) - m_od.create_link(model_element_name_to_create, attr_name, obj_name, extended_mapping[tgt_name]) + # print(attr_name, obj_name, match_mapping[tgt_name]) + m_od.create_link(model_element_name_to_create, attr_name, obj_name, match_mapping[tgt_name]) # Perform updates for pattern_element_name in common: @@ -149,7 +153,7 @@ def rewrite(state, lhs: UUID, rhs: UUID, rhs_mm: UUID, match_mapping: dict, m_to result = eval(expr, {}, {'v': old_value}) # print('eval result=', result) if isinstance(result, int): - # overwrite the old value + # overwrite the old value, in-place referred_model_id = UUID(bottom.read_value(model_element)) Integer(referred_model_id, state).create(result) else: