rewriter.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. # Things you can do:
  2. # - Create/delete objects, associations, attributes
  3. # - Change attribute values
  4. # - ? that's it?
  5. from uuid import UUID
  6. from api.od import ODAPI, bind_api
  7. from services.bottom.V0 import Bottom
  8. from transformation import ramify
  9. from services import od
  10. from services.primitives.string_type import String
  11. from services.primitives.actioncode_type import ActionCode
  12. from services.primitives.integer_type import Integer
  13. from util.eval import exec_then_eval, simply_exec
  14. class TryAgainNextRound(Exception):
  15. pass
  16. # Rewrite is performed in-place (modifying `host_m`)
  17. def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, lhs_match: dict, host_m: UUID, host_mm: UUID):
  18. bottom = Bottom(state)
  19. # Need to come up with a new, unique name when creating new element in host-model:
  20. def first_available_name(prefix: str):
  21. i = 0
  22. while True:
  23. name = prefix + str(i)
  24. if len(bottom.read_outgoing_elements(host_m, name)) == 0:
  25. return name # found unique name
  26. i += 1
  27. # function that can be called from within RHS action code
  28. def matched_callback(pattern_name: str):
  29. host_name = lhs_match[pattern_name]
  30. return bottom.read_outgoing_elements(host_m, host_name)[0]
  31. scd_metamodel_id = state.read_dict(state.read_root(), "SCD")
  32. scd_metamodel = UUID(state.read_value(scd_metamodel_id))
  33. class_type = od.get_scd_mm_class_node(bottom)
  34. attr_link_type = od.get_scd_mm_attributelink_node(bottom)
  35. assoc_type = od.get_scd_mm_assoc_node(bottom)
  36. actioncode_type = od.get_scd_mm_actioncode_node(bottom)
  37. modelref_type = od.get_scd_mm_modelref_node(bottom)
  38. # To be replaced by ODAPI (below)
  39. host_od = od.OD(host_mm, host_m, bottom.state)
  40. rhs_od = od.OD(pattern_mm, rhs_m, bottom.state)
  41. host_odapi = ODAPI(state, host_m, host_mm)
  42. host_mm_odapi = ODAPI(state, host_mm, scd_metamodel)
  43. rhs_odapi = ODAPI(state, rhs_m, pattern_mm)
  44. rhs_mm_odapi = ODAPI(state, pattern_mm, scd_metamodel)
  45. lhs_keys = lhs_match.keys()
  46. rhs_keys = set(k for k in bottom.read_keys(rhs_m)
  47. # extremely dirty - should think of a better way
  48. if "GlobalCondition" not in k and not k.endswith("_condition") and not k.endswith(".condition"))
  49. common = lhs_keys & rhs_keys
  50. to_delete = lhs_keys - common
  51. to_create = rhs_keys - common
  52. # print("to delete:", to_delete)
  53. # print("to create:", to_create)
  54. # to be grown
  55. rhs_match = { name : lhs_match[name] for name in common }
  56. # 1. Perform creations - in the right order!
  57. remaining_to_create = list(to_create)
  58. while len(remaining_to_create) > 0:
  59. next_round = []
  60. for rhs_name in remaining_to_create:
  61. # Determine the type of the thing to create
  62. rhs_obj = rhs_odapi.get(rhs_name)
  63. rhs_type = rhs_odapi.get_type(rhs_obj)
  64. host_type = ramify.get_original_type(bottom, rhs_type)
  65. # for debugging:
  66. if host_type != None:
  67. host_type_name = host_odapi.get_name(host_type)
  68. else:
  69. host_type_name = ""
  70. def get_src_tgt():
  71. src = rhs_odapi.get_source(rhs_obj)
  72. tgt = rhs_odapi.get_target(rhs_obj)
  73. src_name = rhs_odapi.get_name(src)
  74. tgt_name = rhs_odapi.get_name(tgt)
  75. try:
  76. host_src_name = rhs_match[src_name]
  77. host_tgt_name = rhs_match[tgt_name]
  78. except KeyError:
  79. # some creations (e.g., edges) depend on other creations
  80. raise TryAgainNextRound()
  81. host_src = host_odapi.get(host_src_name)
  82. host_tgt = host_odapi.get(host_tgt_name)
  83. return (host_src_name, host_tgt_name, host_src, host_tgt)
  84. try:
  85. if od.is_typed_by(bottom, rhs_type, class_type):
  86. obj_name = first_available_name(rhs_name)
  87. host_od._create_object(obj_name, host_type)
  88. host_odapi._ODAPI__recompute_mappings()
  89. rhs_match[rhs_name] = obj_name
  90. elif od.is_typed_by(bottom, rhs_type, assoc_type):
  91. _, _, host_src, host_tgt = get_src_tgt()
  92. link_name = first_available_name(rhs_name)
  93. host_od._create_link(link_name, host_type, host_src, host_tgt)
  94. host_odapi._ODAPI__recompute_mappings()
  95. rhs_match[rhs_name] = link_name
  96. elif od.is_typed_by(bottom, rhs_type, attr_link_type):
  97. host_src_name, _, host_src, host_tgt = get_src_tgt()
  98. host_attr_link = ramify.get_original_type(bottom, rhs_type)
  99. host_attr_name = host_mm_odapi.get_slot_value(host_attr_link, "name")
  100. link_name = f"{host_src_name}_{host_attr_name}" # must follow naming convention here
  101. host_od._create_link(link_name, host_type, host_src, host_tgt)
  102. host_odapi._ODAPI__recompute_mappings()
  103. rhs_match[rhs_name] = link_name
  104. elif rhs_type == rhs_mm_odapi.get("ActionCode"):
  105. # If we encounter ActionCode in our RHS, we assume that the code computes the value of an attribute...
  106. # This will be the *value* of an attribute. The attribute-link (connecting an object to the attribute) will be created as an edge later.
  107. # Problem: attributes must follow the naming pattern '<obj_name>.<attr_name>'
  108. # So we must know the host-object-name, and the host-attribute-name.
  109. # However, all we have access to here is the name of the attribute in the RHS.
  110. # We cannot even see the link to the RHS-object.
  111. # But, assuming the RHS-attribute is also named '<RAMified_obj_name>.<RAMified_attr_name>', we can:
  112. rhs_src_name, rhs_attr_name = rhs_name.split('.')
  113. try:
  114. host_src_name = rhs_match[rhs_src_name]
  115. except KeyError:
  116. # unmet dependency - object to which attribute belongs not created yet
  117. raise TryAgainNextRound()
  118. rhs_src_type = rhs_odapi.get_type(rhs_odapi.get(rhs_src_name))
  119. rhs_src_type_name = rhs_mm_odapi.get_name(rhs_src_type)
  120. rhs_attr_link_name = f"{rhs_src_type_name}_{rhs_attr_name}"
  121. rhs_attr_link = rhs_mm_odapi.get(rhs_attr_link_name)
  122. host_attr_link = ramify.get_original_type(bottom, rhs_attr_link)
  123. host_attr_name = host_mm_odapi.get_slot_value(host_attr_link, "name")
  124. val_name = f"{host_src_name}.{host_attr_name}"
  125. python_expr = ActionCode(UUID(bottom.read_value(rhs_obj)), bottom.state).read()
  126. result = exec_then_eval(python_expr, _globals={
  127. **bind_api(host_odapi),
  128. 'matched': matched_callback,
  129. })
  130. host_odapi.create_primitive_value(val_name, result, is_code=False)
  131. rhs_match[rhs_name] = val_name
  132. else:
  133. rhs_type_name = rhs_odapi.get_name(rhs_type)
  134. raise Exception(f"Host type {host_type_name} of pattern element '{rhs_name}:{rhs_type_name}' is not a class, association or attribute link. Don't know what to do with it :(")
  135. except TryAgainNextRound:
  136. next_round.append(rhs_name)
  137. if len(next_round) == len(remaining_to_create):
  138. raise Exception("Creation of objects did not make any progress - there must be some kind of cyclic dependency?!")
  139. remaining_to_create = next_round
  140. # 2. Perform updates (only on values)
  141. for common_name in common:
  142. host_obj_name = rhs_match[common_name]
  143. host_obj = host_odapi.get(host_obj_name)
  144. host_type = host_odapi.get_type(host_obj)
  145. if od.is_typed_by(bottom, host_type, class_type):
  146. # nothing to do
  147. pass
  148. elif od.is_typed_by(bottom, host_type, assoc_type):
  149. # nothing to do
  150. pass
  151. elif od.is_typed_by(bottom, host_type, attr_link_type):
  152. # nothing to do
  153. pass
  154. elif od.is_typed_by(bottom, host_type, modelref_type):
  155. rhs_obj = rhs_odapi.get(common_name)
  156. python_expr = ActionCode(UUID(bottom.read_value(rhs_obj)), bottom.state).read()
  157. result = exec_then_eval(python_expr,
  158. _globals={
  159. **bind_api(host_odapi),
  160. 'matched': matched_callback,
  161. },
  162. _locals={'this': host_obj}) # 'this' can be used to read the previous value of the slot
  163. host_odapi.overwrite_primitive_value(host_obj_name, result, is_code=False)
  164. else:
  165. msg = f"Don't know what to do with element '{common_name}' -> '{host_obj_name}:{host_type}')"
  166. # print(msg)
  167. raise Exception(msg)
  168. # 3. Perform deletions
  169. # This way, action code can read from elements that are deleted...
  170. # Even better would be to not modify the model in-place, but use copy-on-write...
  171. for pattern_name_to_delete in to_delete:
  172. # For every name in `to_delete`, look up the name of the matched element in the host graph
  173. model_el_name_to_delete = lhs_match[pattern_name_to_delete]
  174. # print('deleting', model_el_name_to_delete)
  175. # Look up the matched element in the host graph
  176. el_to_delete, = bottom.read_outgoing_elements(host_m, model_el_name_to_delete)
  177. # Delete
  178. bottom.delete_element(el_to_delete)
  179. # 4. Object-level actions
  180. # Iterate over the (now complete) mapping RHS -> Host
  181. for rhs_name, host_name in rhs_match.items():
  182. host_obj = host_odapi.get(host_name)
  183. rhs_obj = rhs_odapi.get(rhs_name)
  184. rhs_type = rhs_odapi.get_type(rhs_obj)
  185. rhs_type_of_type = rhs_mm_odapi.get_type(rhs_type)
  186. rhs_type_of_type_name = rhs_mm_odapi.get_name(rhs_type_of_type)
  187. if rhs_mm_odapi.cdapi.is_subtype(super_type_name="Class", sub_type_name=rhs_type_of_type_name):
  188. # rhs_obj is an object or link (because association is subtype of class)
  189. python_code = rhs_odapi.get_slot_value_default(rhs_obj, "condition", default="")
  190. simply_exec(python_code,
  191. _globals={
  192. **bind_api(host_odapi),
  193. 'matched': matched_callback,
  194. },
  195. _locals={'this': host_obj})
  196. # 5. Execute global actions
  197. for cond_name, cond in rhs_odapi.get_all_instances("GlobalCondition"):
  198. python_code = rhs_odapi.get_slot_value(cond, "condition")
  199. simply_exec(python_code, _globals={
  200. **bind_api(host_odapi),
  201. 'matched': matched_callback,
  202. })
  203. return rhs_match