|
| 1 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 2 | +# you may not use this file except in compliance with the License. |
| 3 | +# You may obtain a copy of the License at |
| 4 | +# |
| 5 | +# https://www.apache.org/licenses/LICENSE-2.0 |
| 6 | +# |
| 7 | +# Unless required by applicable law or agreed to in writing, software |
| 8 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 9 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 10 | +# See the License for the specific language governing permissions and |
| 11 | +# limitations under the License. |
| 12 | + |
| 13 | + |
| 14 | +""" |
| 15 | +Implement various strategies for adding implied relationships to the model. |
| 16 | +
|
| 17 | +Implied relationship strategies are used to add relationships to parents when a |
| 18 | +relationship is added to their children. For example, assuming systems A and B with |
| 19 | +child containers A1 and B1 respectively, then saying that A1 uses B1 implies that |
| 20 | +A also uses B (and A uses B1 and A1 uses B). Each strategy is represented as a |
| 21 | +function that can be set on `Model.implied_relationship_strategy`, with the default |
| 22 | +being to do nothing. |
| 23 | +""" |
| 24 | + |
| 25 | +from itertools import product |
| 26 | +from typing import List |
| 27 | + |
| 28 | +from .element import Element |
| 29 | +from .relationship import Relationship |
| 30 | +from .software_system import SoftwareSystem |
| 31 | + |
| 32 | + |
| 33 | +def default_implied_relationship_strategy(relationship: Relationship): |
| 34 | + """Don't create any implied relationships.""" |
| 35 | + pass |
| 36 | + |
| 37 | + |
| 38 | +def create_implied_relationships_unless_any_exist(relationship: Relationship): |
| 39 | + """ |
| 40 | + Create implied relationships unless there is any existing. |
| 41 | +
|
| 42 | + This strategy creates implied relationships between all valid combinations of the |
| 43 | + parent elements, unless any relationship already exists between them. |
| 44 | + """ |
| 45 | + source = relationship.source |
| 46 | + destination = relationship.destination |
| 47 | + ancestor_pairs = product(_get_ancestors(source), _get_ancestors(destination)) |
| 48 | + for new_source, new_destination in ancestor_pairs: |
| 49 | + if _implied_relationship_is_allowed(new_source, new_destination): |
| 50 | + if not any( |
| 51 | + r.destination is new_destination |
| 52 | + for r in new_source.get_efferent_relationships() |
| 53 | + ): |
| 54 | + _clone_relationship(relationship, new_source, new_destination) |
| 55 | + |
| 56 | + |
| 57 | +def create_implied_relationships_unless_same_exists(relationship: Relationship): |
| 58 | + """ |
| 59 | + Create implied relationships unless there is a existing one with the same description. |
| 60 | +
|
| 61 | + This strategy creates implied relationships between all valid combinations of the |
| 62 | + parent elements, unless any relationship already exists between them which has the |
| 63 | + same description as the original. |
| 64 | + """ |
| 65 | + source = relationship.source |
| 66 | + destination = relationship.destination |
| 67 | + ancestor_pairs = product(_get_ancestors(source), _get_ancestors(destination)) |
| 68 | + for new_source, new_destination in ancestor_pairs: |
| 69 | + if _implied_relationship_is_allowed(new_source, new_destination): |
| 70 | + if not any( |
| 71 | + r.destination is new_destination |
| 72 | + and r.description == relationship.description |
| 73 | + for r in new_source.get_efferent_relationships() |
| 74 | + ): |
| 75 | + _clone_relationship(relationship, new_source, new_destination) |
| 76 | + |
| 77 | + |
| 78 | +def _implied_relationship_is_allowed(source: Element, destination: Element): |
| 79 | + return source not in _get_ancestors( |
| 80 | + destination |
| 81 | + ) and destination not in _get_ancestors(source) |
| 82 | + |
| 83 | + |
| 84 | +def _get_ancestors(element: Element, include_self=True) -> List[Element]: |
| 85 | + """Get the ancestors of an element.""" |
| 86 | + result = [] |
| 87 | + current = element |
| 88 | + while current is not None: |
| 89 | + result.append(current) |
| 90 | + current = None if isinstance(current, SoftwareSystem) else current.parent |
| 91 | + if not include_self: |
| 92 | + result.remove(element) |
| 93 | + return result |
| 94 | + |
| 95 | + |
| 96 | +def _clone_relationship( |
| 97 | + relationship: Relationship, new_source: Element, new_destination: Element |
| 98 | +) -> Relationship: |
| 99 | + print(f"{new_source.name}->{new_destination.name}") |
| 100 | + return new_source.add_relationship( |
| 101 | + destination=new_destination, |
| 102 | + description=relationship.description, |
| 103 | + technology=relationship.technology, |
| 104 | + interaction_style=relationship.interaction_style, |
| 105 | + tags=relationship.tags, |
| 106 | + properties=relationship.properties, |
| 107 | + create_implied_relationships=False, |
| 108 | + ) |
0 commit comments