-
-
Notifications
You must be signed in to change notification settings - Fork 187
/
modelfactory.py
398 lines (322 loc) · 10.8 KB
/
modelfactory.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
"""
UML model support functions.
Functions collected in this module allow to
- create more complex UML model structures
- perform specific searches and manipulations
"""
import itertools
from typing import Iterable, Sequence
from gaphor.UML.uml2 import (
Association,
Class,
Classifier,
Component,
Dependency,
Element,
Extension,
ExtensionEnd,
Generalization,
Implementation,
InstanceSpecification,
Interface,
Message,
MessageOccurrenceSpecification,
Property,
Realization,
Slot,
Stereotype,
Usage,
)
def stereotypes_str(element: Element, stereotypes: Sequence[str] = ()):
"""
Identify stereotypes of an UML metamodel instance and return coma
separated stereotypes as string.
:Parameters:
element
Element having stereotypes, can be None.
stereotypes
List of additional stereotypes, can be empty.
"""
# generate string with stereotype names separated by coma
if element:
applied: Iterable[str] = (
stereotype_name(st) for st in get_applied_stereotypes(element)
)
else:
applied = ()
s = ", ".join(itertools.chain(stereotypes, applied))
if s:
return f"«{s}»"
else:
return ""
def stereotype_name(stereotype):
"""
Return stereotype name suggested by UML specification. First will be
character lowercase unless the second character is uppercase.
:Parameters:
stereotype
Stereotype UML metamodel instance.
"""
name = stereotype.name
if not name:
return ""
elif len(name) > 1 and name[1].isupper():
return name
else:
return name[0].lower() + name[1:]
def apply_stereotype(element, stereotype):
"""
Apply a stereotype to an element.
:Parameters:
element
UML metamodel class instance.
stereotype
UML metamodel stereotype instance.
"""
assert (
element.model is stereotype.model
), "Element and Stereotype are from different models"
model = element.model
obj = model.create(InstanceSpecification)
obj.classifier = stereotype
element.appliedStereotype = obj
return obj
def find_instances(element):
"""
Find instance specification which extend classifier `element`.
"""
model = element.model
return model.select(
lambda e: e.isKindOf(InstanceSpecification)
and e.classifier
and e.classifier[0] == element
)
def remove_stereotype(element, stereotype):
"""
Remove a stereotype from an element.
:Parameters:
element
UML metamodel element instance.
stereotype
UML metamodel stereotype instance.
"""
for obj in element.appliedStereotype:
if obj.classifier and obj.classifier[0] is stereotype:
del element.appliedStereotype[obj]
obj.unlink()
break
def get_stereotypes(element):
"""
Get sorted collection of possible stereotypes for specified element.
"""
model = element.model
# UML specs does not allow to extend stereotypes with stereotypes
if isinstance(element, Stereotype):
return ()
cls = type(element)
# find out names of classes, which are superclasses of element class
names = {c.__name__ for c in cls.__mro__ if issubclass(c, Element)}
# find stereotypes that extend element class
classes = model.select(lambda e: e.isKindOf(Class) and e.name in names)
stereotypes = {ext.ownedEnd.type for cls in classes for ext in cls.extension}
return sorted(stereotypes, key=lambda st: st.name)
def get_applied_stereotypes(element):
"""
Get collection of applied stereotypes to an element.
"""
return element.appliedStereotype[:].classifier
def create_extension(metaclass: Class, stereotype: Stereotype) -> Extension:
"""
Create an Extension association between an metaclass and a stereotype.
"""
assert (
metaclass.model is stereotype.model
), "Metaclass and Stereotype are from different models"
model = metaclass.model
ext: Extension = model.create(Extension)
p = model.create(Property)
ext_end = model.create(ExtensionEnd)
ext.memberEnd = p
ext.memberEnd = ext_end
ext.ownedEnd = ext_end
ext_end.type = stereotype
ext_end.aggregation = "composite"
p.type = metaclass
p.name = "baseClass"
stereotype.ownedAttribute = p
metaclass.ownedAttribute = ext_end
return ext
def is_metaclass(element):
return (
(not isinstance(element, Stereotype))
and hasattr(element, "extension")
and bool(element.extension)
)
def add_slot(instance, definingFeature):
"""
Add slot to instance specification for an attribute.
"""
assert (
instance.model is definingFeature.model
), "Instance and Defining feature are from different models"
model = instance.model
slot = model.create(Slot)
slot.definingFeature = definingFeature
instance.slot = slot
return slot
def create_dependency(supplier, client):
assert (
supplier.model is client.model
), "Supplier and Client are from different models"
model = supplier.model
dep = model.create(Dependency)
dep.supplier = supplier
dep.client = client
return dep
def create_realization(realizingClassifier, abstraction):
assert (
realizingClassifier.model is abstraction.model
), "Realizing classifier and Abstraction are from different models"
model = realizingClassifier.model
dep = model.create(Realization)
dep.realizingClassifier = realizingClassifier
dep.abstraction = abstraction
return dep
def create_generalization(general, specific):
assert (
general.model is specific.model
), "General and Specific are from different models"
model = general.model
gen = model.create(Generalization)
gen.general = general
gen.specific = specific
return gen
def create_implementation(contract, implementatingClassifier):
assert (
contract.model is implementatingClassifier.model
), "Contract and Implementating classifier are from different models"
model = contract.model
impl = model.create(Implementation)
impl.contract = contract
impl.implementatingClassifier = implementatingClassifier
return impl
def create_association(type_a, type_b):
"""
Create an association between two items.
"""
assert type_a.model is type_b.model, "Head and Tail end are from different models"
model = type_a.model
assoc = model.create(Association)
end_a = model.create(Property)
end_b = model.create(Property)
assoc.memberEnd = end_a
assoc.memberEnd = end_b
end_a.type = type_a
end_b.type = type_b
# set default navigability (unknown)
set_navigability(assoc, end_a, None)
set_navigability(assoc, end_b, None)
return assoc
def set_navigability(assoc, end, nav):
"""
Set navigability of an association end (property).
There are three possible values for ``nav`` parameter
True
association end is navigable
False
association end is not navigable
None
association end navigability is unkown
There are two ways of specifing that an end is navigable
- an end is in Association.navigableOwnedEnd collection
- an end is class (interface) attribute (stored in Class.ownedAttribute
collection)
Let's consider the graph::
A -----> B
y x
There two association ends A.x and B.y, A.x is navigable.
Therefore navigable association ends are constructed in following way
- if A is a class or an interface, then A.x is an attribute owned by A
- if A is other classifier, then association is more general
relationship; it may mean that participating instance of B can be
"accessed efficiently"
- i.e. when A is a Component, then association may be some compositing
relationship
- when A and B are instances of Node class, then it is a
communication path
Therefore navigable association end may be stored as one of
- {Class,Interface}.ownedAttribute due to their capabilities of
editing owned members
- Association.navigableOwnedEnd
When an end has unknown (unspecified) navigability, then it is owned by
association (but not by classifier).
When an end is non-navigable, then it is just member of an association.
"""
# remove "navigable" and "unspecified" navigation indicators first
if type(end.type) in (Class, Interface):
owner = end.opposite.type
if end in owner.ownedAttribute:
owner.ownedAttribute.remove(end)
if end in assoc.ownedEnd:
assoc.ownedEnd.remove(end)
if end in assoc.navigableOwnedEnd:
assoc.navigableOwnedEnd.remove(end)
assert end not in assoc.ownedEnd
assert end not in assoc.navigableOwnedEnd
if nav is True:
if type(end.type) in (Class, Interface):
owner = end.opposite.type
owner.ownedAttribute = end
else:
assoc.navigableOwnedEnd = end
elif nav is None:
assoc.ownedEnd = end
# elif nav is False, non-navigable
def dependency_type(client, supplier):
"""
Determine dependency type between client (tail) and supplier
(arrowhead).
There can be different dependencies detected automatically
- usage when supplier is an interface
- realization when client is component and supplier is a classifier
If none of above is detected then standard dependency is determined.
"""
dt = Dependency
# test interface first as it is a classifier
if isinstance(supplier, Interface):
dt = Usage
elif isinstance(client, Component) and isinstance(supplier, Classifier):
dt = Realization
return dt
def clone_message(msg, inverted=False):
"""
Create new message based on speciied message.
If inverted is set to True, then inverted message is created.
"""
model = msg.model
message = model.create(Message)
send = None
receive = None
if msg.sendEvent:
send = model.create(MessageOccurrenceSpecification)
send.covered = msg.sendEvent.covered
if msg.receiveEvent:
receive = model.create(MessageOccurrenceSpecification)
receive.covered = msg.receiveEvent.covered
if inverted:
# inverted message goes in different direction, than original
# message
message.sendEvent = receive
message.receiveEvent = send
else:
message.sendEvent = send
message.receiveEvent = receive
return message
def swap_element(element, new_class):
"""
A "trick" to swap the element type.
Used in certain cases where the underlaying element type
may change.
"""
if element.__class__ is not new_class:
element.__class__ = new_class