Add example of 'woods' operational semantics. Clearer error messages. Implement full OD-API. Small refactoring of Conformance class.
This commit is contained in:
parent
f2dc299eab
commit
cd26a401fe
7 changed files with 449 additions and 63 deletions
92
api/cd.py
Normal file
92
api/cd.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
from services.bottom.V0 import Bottom
|
||||
from uuid import UUID
|
||||
|
||||
class CDAPI:
|
||||
def __init__(self, state, m: UUID):
|
||||
self.state = state
|
||||
self.bottom = Bottom(state)
|
||||
self.m = m
|
||||
self.mm = UUID(state.read_value(state.read_dict(state.read_root(), "SCD")))
|
||||
|
||||
# pre-compute some things
|
||||
|
||||
# element -> name
|
||||
self.type_model_names = {
|
||||
self.bottom.read_outgoing_elements(self.m, e)[0]
|
||||
: e for e in self.bottom.read_keys(self.m)
|
||||
}
|
||||
|
||||
|
||||
inh_type, = self.bottom.read_outgoing_elements(self.mm, "Inheritance")
|
||||
inh_links = []
|
||||
for tm_element, tm_name in self.type_model_names.items():
|
||||
types = self.bottom.read_outgoing_elements(tm_element, "Morphism")
|
||||
if inh_type in types:
|
||||
inh_links.append(tm_element)
|
||||
|
||||
# for each inheritance link we add the parent and child to the sub types map
|
||||
# name -> name
|
||||
self.direct_sub_types = { type_name: set() for type_name in self.bottom.read_keys(self.m) } # empty initially
|
||||
self.direct_super_types = { type_name: set() for type_name in self.bottom.read_keys(self.m) } # empty initially
|
||||
for link in inh_links:
|
||||
tm_source = self.bottom.read_edge_source(link)
|
||||
tm_target = self.bottom.read_edge_target(link)
|
||||
parent_name = self.type_model_names[tm_target]
|
||||
child_name = self.type_model_names[tm_source]
|
||||
self.direct_sub_types[parent_name].add(child_name)
|
||||
self.direct_super_types[child_name].add(parent_name)
|
||||
|
||||
def get_transitive_sub_types(type_name: str):
|
||||
# includes the type itself - reason: if we want to get all the instances of some type and its subtypes, we don't have to consider the type itself as an extra case
|
||||
return [type_name, *(sub_type for child_name in self.direct_sub_types.get(type_name, set()) for sub_type in get_transitive_sub_types(child_name) )]
|
||||
|
||||
def get_transitive_super_types(type_name: str):
|
||||
# includes the type itself - reason: if we want to check if something is an instance of a type, we check if its type or one of its super types is equal to the type we're looking for, without having to consider the instance's type itself as an extra case
|
||||
return [type_name, *(super_type for parent_name in self.direct_super_types.get(type_name, set()) for super_type in get_transitive_super_types(parent_name))]
|
||||
|
||||
|
||||
self.transitive_sub_types = { type_name: set(get_transitive_sub_types(type_name)) for type_name in self.direct_sub_types }
|
||||
self.transitive_super_types = { type_name: set(get_transitive_super_types(type_name)) for type_name in self.direct_super_types }
|
||||
|
||||
def get_type(type_name: str):
|
||||
return self.bottom.read_outgoing_elements(self.m, type_name)[0]
|
||||
|
||||
def is_direct_subtype(super_type_name: str, sub_type_name: str):
|
||||
return sub_type_name in self.direct_sub_types[super_type]
|
||||
|
||||
def is_direct_supertype(sub_type_name: str, super_type_name: str):
|
||||
return super_type_name in self.direct_super_types[sub_type_name]
|
||||
|
||||
def is_subtype(super_type_name: str, sub_type_name: str):
|
||||
return sub_type_name in self.transitive_sub_types[super_type]
|
||||
|
||||
def is_supertype(sub_type_name: str, super_type_name: str):
|
||||
return super_type_name in self.transitive_super_types[sub_type_name]
|
||||
|
||||
# # The edge connecting an object to the value of a slot must be named `{object_name}_{attr_name}`
|
||||
# def get_attr_link_name(self, class_name, attr_name):
|
||||
# assoc_name = f"{class_name}_{attr_name}"
|
||||
# type_edges = self.bottom.read_outgoing_elements(self.m, assoc_name)
|
||||
# if len(type_edges) == 1:
|
||||
# return assoc_name
|
||||
# else:
|
||||
# # look for attribute in the super-types
|
||||
# conf = Conformance(self.bottom.state, self.model, self.type_model)
|
||||
# conf.precompute_sub_types() # only need to know about subtypes
|
||||
# super_types = [s for s in conf.sub_types if class_name in conf.sub_types[s]]
|
||||
# for s in super_types:
|
||||
# assoc_name = f"{s}_{attr_name}"
|
||||
# if len(self.bottom.read_outgoing_elements(self.type_model, assoc_name)) == 1:
|
||||
# return assoc_name
|
||||
|
||||
# Attributes are inherited, so when we instantiate an attribute of a class, the AttributeLink may contain the name of the superclass
|
||||
def find_attribute_type(self, class_name: str, attr_name: str):
|
||||
assoc_name = f"{class_name}_{attr_name}"
|
||||
type_edges = self.bottom.read_outgoing_elements(self.m, assoc_name)
|
||||
if len(type_edges) == 1:
|
||||
return type_edges[0]
|
||||
else:
|
||||
for supertype in self.direct_super_types[class_name]:
|
||||
result = self.find_attribute_type(supertype, attr_name)
|
||||
if result != None:
|
||||
return result
|
||||
148
api/od.py
Normal file
148
api/od.py
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
from services import od
|
||||
from api import cd
|
||||
from services.bottom.V0 import Bottom
|
||||
from uuid import UUID
|
||||
from typing import Optional
|
||||
|
||||
NEXT_ID = 0
|
||||
|
||||
# Models map names to elements
|
||||
# This builds the inverse mapping, so we can quickly lookup the name of an element
|
||||
def build_name_mapping(state, m):
|
||||
mapping = {}
|
||||
bottom = Bottom(state)
|
||||
for name in bottom.read_keys(m):
|
||||
element, = bottom.read_outgoing_elements(m, name)
|
||||
mapping[element] = name
|
||||
return mapping
|
||||
|
||||
# Object Diagram API
|
||||
# Intended to replace the 'services.od.OD' class eventually
|
||||
class ODAPI:
|
||||
def __init__(self, state, m: UUID, mm: UUID):
|
||||
self.state = state
|
||||
self.bottom = Bottom(state)
|
||||
self.m = m
|
||||
self.mm = mm
|
||||
self.od = od.OD(mm, m, state)
|
||||
self.cd = cd.CDAPI(state, mm)
|
||||
|
||||
self.create_boolean_value = self.od.create_boolean_value
|
||||
self.create_integer_value = self.od.create_integer_value
|
||||
self.create_string_value = self.od.create_string_value
|
||||
self.create_actioncode_value = self.od.create_actioncode_value
|
||||
|
||||
self.__recompute_mappings()
|
||||
|
||||
# Called after every change - makes querying faster but modifying slower
|
||||
def __recompute_mappings(self):
|
||||
self.obj_to_name = {**build_name_mapping(self.state, self.m), **build_name_mapping(self.state, self.mm)}
|
||||
# self.obj_to_type = {}
|
||||
self.type_to_objs = { type_name : set() for type_name in self.bottom.read_keys(self.mm)}
|
||||
for m_name in self.bottom.read_keys(self.m):
|
||||
m_element, = self.bottom.read_outgoing_elements(self.m, m_name)
|
||||
tm_element = self.get_type(m_element)
|
||||
tm_name = self.obj_to_name[tm_element]
|
||||
# self.obj_to_type[m_name] = tm_name
|
||||
self.type_to_objs[tm_name].add(m_name)
|
||||
|
||||
def get_value(self, obj: UUID):
|
||||
return od.read_primitive_value(self.bottom, obj, self.mm)[0]
|
||||
|
||||
def get_target(self, link: UUID):
|
||||
return self.bottom.read_edge_target(link)
|
||||
|
||||
def get_source(self, link: UUID):
|
||||
return self.bottom.read_edge_source(link)
|
||||
|
||||
def get_slot(self, obj: UUID, attr_name: str):
|
||||
return self.od.get_slot(obj, attr_name)
|
||||
|
||||
def get_slot_link(self, obj: UUID, attr_name: str):
|
||||
return self.od.get_slot_link(obj, attr_name)
|
||||
|
||||
def get_outgoing(self, obj: UUID, assoc_name: str):
|
||||
return od.find_outgoing_typed_by(self.bottom, src=obj, type_node=self.bottom.read_outgoing_elements(self.mm, assoc_name)[0])
|
||||
|
||||
def get_incoming(self, obj: UUID, assoc_name: str):
|
||||
return od.find_incoming_typed_by(self.bottom, tgt=obj, type_node=self.bottom.read_outgoing_elements(self.mm, assoc_name)[0])
|
||||
|
||||
def get_all_instances(self, type_name: str, include_subtypes=True):
|
||||
obj_names = self.type_to_objs[type_name]
|
||||
return [(obj_name, self.bottom.read_outgoing_elements(self.m, obj_name)[0]) for obj_name in obj_names]
|
||||
|
||||
def get_type(self, obj: UUID):
|
||||
types = self.bottom.read_outgoing_elements(obj, "Morphism")
|
||||
if len(types) != 1:
|
||||
raise Exception(f"Expected obj to have 1 type, instead got {len(types)} types.")
|
||||
return types[0]
|
||||
|
||||
def get_name(self, obj: UUID):
|
||||
return (
|
||||
[name for name in self.bottom.read_keys(self.m) if self.bottom.read_outgoing_elements(self.m, name)[0] == obj] +
|
||||
[name for name in self.bottom.read_keys(self.mm) if self.bottom.read_outgoing_elements(self.mm, name)[0] == obj]
|
||||
)[0]
|
||||
return self.obj_to_name[obj]
|
||||
|
||||
def get(self, name: str):
|
||||
return self.bottom.read_outgoing_elements(self.m, name)[0]
|
||||
|
||||
def get_type_name(self, obj: UUID):
|
||||
return self.get_name(self.get_type(obj))
|
||||
|
||||
def is_instance(obj: UUID, type_name: str, include_subtypes=True):
|
||||
typ = self.cd.get_type(type_name)
|
||||
types = set(typ) if not include_subtypes else self.cd.transitive_subtypes[type_name]
|
||||
for type_of_obj in self.bottom.read_outgoing_elements(obj, "Morphism"):
|
||||
if type_of_obj in types:
|
||||
return True
|
||||
return False
|
||||
|
||||
def delete(self, obj: UUID):
|
||||
self.bottom.delete_element(obj)
|
||||
self.__recompute_mappings()
|
||||
|
||||
def get_slot_value(self, obj: UUID, attr_name: str):
|
||||
return self.get_value(self.get_slot(obj, attr_name))
|
||||
|
||||
def set_slot_value(self, obj: UUID, attr_name: str, new_value: any):
|
||||
obj_name = self.get_name(obj)
|
||||
|
||||
link_name = f"{obj_name}_{attr_name}"
|
||||
target_name = f"{obj_name}.{attr_name}"
|
||||
|
||||
old_slot_link = self.get_slot_link(obj, attr_name)
|
||||
if old_slot_link != None:
|
||||
old_target = self.get_target(old_slot_link)
|
||||
# if old_target != None:
|
||||
self.bottom.delete_element(old_target) # this also deletes the slot-link
|
||||
|
||||
new_target = self.create_primitive_value(target_name, new_value)
|
||||
slot_type = self.cd.find_attribute_type(self.get_type_name(obj), attr_name)
|
||||
new_link = self.od._create_link(link_name, slot_type, obj, new_target)
|
||||
self.__recompute_mappings()
|
||||
|
||||
def create_primitive_value(self, name: str, value: any, is_code=False):
|
||||
if isinstance(value, bool):
|
||||
tgt = self.create_boolean_value(name, value)
|
||||
elif isinstance(value, int):
|
||||
tgt = self.create_integer_value(name, value)
|
||||
elif isinstance(value, str):
|
||||
if is_code:
|
||||
tgt = self.create_actioncode_value(name, value)
|
||||
else:
|
||||
tgt = self.create_string_value(name, value)
|
||||
else:
|
||||
raise Exception("Unimplemented type "+value)
|
||||
self.__recompute_mappings()
|
||||
return tgt
|
||||
|
||||
def create_link(self, link_name: Optional[str], assoc_name: str, src: UUID, tgt: UUID):
|
||||
global NEXT_ID
|
||||
typ, = self.bottom.read_outgoing_elements(self.mm, assoc_name)
|
||||
if link_name == None:
|
||||
link_name = f"__{assoc_name}{NEXT_ID}"
|
||||
NEXT_ID += 1
|
||||
link_id = self.od._create_link(link_name, typ, src, tgt)
|
||||
self.__recompute_mappings()
|
||||
return link_id
|
||||
Loading…
Add table
Add a link
Reference in a new issue