lutaml 0.10.4 → 0.10.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +8 -0
- data/.rubocop.yml +10 -0
- data/.rubocop_todo.yml +218 -94
- data/TODO.cleanups/01-resolve-production-todos.md +65 -0
- data/TODO.cleanups/02-reduce-metrics-offenses.md +37 -0
- data/TODO.cleanups/03-reduce-rspec-multiple-expectations.md +54 -0
- data/TODO.cleanups/04-reduce-rspec-example-length.md +45 -0
- data/TODO.cleanups/05-replace-marshal-load.md +37 -0
- data/TODO.cleanups/06-replace-eval-in-tests.md +41 -0
- data/TODO.cleanups/07-fix-lint-offenses.md +74 -0
- data/TODO.cleanups/08-reduce-memoized-helpers-and-nesting.md +43 -0
- data/TODO.cleanups/09-reduce-verified-doubles-and-rspec-style.md +57 -0
- data/TODO.cleanups/10-split-large-files.md +47 -0
- data/bin/console +0 -1
- data/exe/lutaml +1 -0
- data/lib/lutaml/cli/element_identifier.rb +3 -6
- data/lib/lutaml/cli/interactive_shell/bookmark_commands.rb +88 -0
- data/lib/lutaml/cli/interactive_shell/command_base.rb +32 -0
- data/lib/lutaml/cli/interactive_shell/export_handler.rb +67 -0
- data/lib/lutaml/cli/interactive_shell/help_display.rb +114 -0
- data/lib/lutaml/cli/interactive_shell/navigation_commands.rb +135 -0
- data/lib/lutaml/cli/interactive_shell/query_commands.rb +185 -0
- data/lib/lutaml/cli/interactive_shell.rb +116 -802
- data/lib/lutaml/cli/uml/build_command.rb +5 -5
- data/lib/lutaml/cli/uml/verify_command.rb +0 -1
- data/lib/lutaml/converter/xmi_to_uml.rb +3 -153
- data/lib/lutaml/converter/xmi_to_uml_generalization.rb +193 -0
- data/lib/lutaml/formatter/graphviz.rb +1 -2
- data/lib/lutaml/qea/database.rb +1 -47
- data/lib/lutaml/qea/factory/association_builder.rb +188 -0
- data/lib/lutaml/qea/factory/base_transformer.rb +0 -1
- data/lib/lutaml/qea/factory/class_transformer.rb +40 -590
- data/lib/lutaml/qea/factory/diagram_transformer.rb +0 -3
- data/lib/lutaml/qea/factory/generalization_builder.rb +211 -0
- data/lib/lutaml/qea/factory/package_transformer.rb +1 -2
- data/lib/lutaml/qea/factory/stereotype_loader.rb +34 -0
- data/lib/lutaml/qea/lookup_indexes.rb +54 -0
- data/lib/lutaml/qea/models/ea_datatype.rb +0 -2
- data/lib/lutaml/qea/validation/validation_engine.rb +0 -2
- data/lib/lutaml/uml/has_members.rb +0 -1
- data/lib/lutaml/uml/inheritance_walker.rb +92 -0
- data/lib/lutaml/uml/model_helpers.rb +129 -0
- data/lib/lutaml/uml/node/attribute.rb +3 -1
- data/lib/lutaml/uml/node/class_node.rb +3 -3
- data/lib/lutaml/uml/operation.rb +2 -0
- data/lib/lutaml/uml_repository/class_lookup_index.rb +40 -0
- data/lib/lutaml/uml_repository/exporters/markdown/class_page_builder.rb +179 -0
- data/lib/lutaml/uml_repository/exporters/markdown/formatting.rb +36 -0
- data/lib/lutaml/uml_repository/exporters/markdown/index_page_builder.rb +73 -0
- data/lib/lutaml/uml_repository/exporters/markdown/link_resolver.rb +40 -0
- data/lib/lutaml/uml_repository/exporters/markdown/package_page_builder.rb +107 -0
- data/lib/lutaml/uml_repository/exporters/markdown_exporter.rb +26 -538
- data/lib/lutaml/uml_repository/index_builder.rb +3 -271
- data/lib/lutaml/uml_repository/index_builders/association_index.rb +141 -0
- data/lib/lutaml/uml_repository/index_builders/class_index.rb +94 -0
- data/lib/lutaml/uml_repository/index_builders/package_index.rb +57 -0
- data/lib/lutaml/uml_repository/package_exporter.rb +10 -20
- data/lib/lutaml/uml_repository/package_loader.rb +37 -17
- data/lib/lutaml/uml_repository/repository/deprecated.rb +39 -0
- data/lib/lutaml/uml_repository/repository/loader.rb +112 -0
- data/lib/lutaml/uml_repository/repository.rb +7 -57
- data/lib/lutaml/uml_repository/static_site/association_serialization.rb +142 -0
- data/lib/lutaml/uml_repository/static_site/configuration.rb +0 -2
- data/lib/lutaml/uml_repository/static_site/data_transformer.rb +52 -873
- data/lib/lutaml/uml_repository/static_site/generator.rb +29 -8
- data/lib/lutaml/uml_repository/static_site/search_index_builder.rb +1 -4
- data/lib/lutaml/uml_repository/static_site/serializers/attribute_serializer.rb +78 -0
- data/lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb +124 -0
- data/lib/lutaml/uml_repository/static_site/serializers/diagram_serializer.rb +60 -0
- data/lib/lutaml/uml_repository/static_site/serializers/inheritance_resolver.rb +258 -0
- data/lib/lutaml/uml_repository/static_site/serializers/metadata_builder.rb +48 -0
- data/lib/lutaml/uml_repository/static_site/serializers/operation_serializer.rb +57 -0
- data/lib/lutaml/uml_repository/static_site/serializers/package_serializer.rb +94 -0
- data/lib/lutaml/uml_repository/static_site/serializers/package_tree_builder.rb +93 -0
- data/lib/lutaml/version.rb +1 -1
- data/lib/lutaml/xmi/liquid_drops/association_drop.rb +13 -35
- data/lib/lutaml/xmi/liquid_drops/attribute_drop.rb +12 -18
- data/lib/lutaml/xmi/liquid_drops/cardinality_drop.rb +14 -6
- data/lib/lutaml/xmi/liquid_drops/connector_drop.rb +0 -3
- data/lib/lutaml/xmi/liquid_drops/constraint_drop.rb +1 -3
- data/lib/lutaml/xmi/liquid_drops/data_type_drop.rb +13 -70
- data/lib/lutaml/xmi/liquid_drops/dependency_drop.rb +2 -5
- data/lib/lutaml/xmi/liquid_drops/diagram_drop.rb +5 -11
- data/lib/lutaml/xmi/liquid_drops/enum_drop.rb +8 -16
- data/lib/lutaml/xmi/liquid_drops/enum_owned_literal_drop.rb +3 -9
- data/lib/lutaml/xmi/liquid_drops/generalization_attribute_drop.rb +11 -13
- data/lib/lutaml/xmi/liquid_drops/generalization_drop.rb +27 -85
- data/lib/lutaml/xmi/liquid_drops/klass_drop.rb +39 -91
- data/lib/lutaml/xmi/liquid_drops/operation_drop.rb +3 -9
- data/lib/lutaml/xmi/liquid_drops/package_drop.rb +16 -44
- data/lib/lutaml/xmi/liquid_drops/root_drop.rb +3 -11
- data/lib/lutaml/xmi/liquid_drops/source_target_drop.rb +2 -5
- data/lib/lutaml/xmi/parsers/xmi_base.rb +2 -749
- data/lib/lutaml/xmi/parsers/xmi_class_members.rb +45 -0
- data/lib/lutaml/xmi/parsers/xmi_connector.rb +251 -0
- data/lib/lutaml/xmi/parsers/xml.rb +7 -120
- data/lib/lutaml/xmi/xmi_lookup_service.rb +42 -0
- data/lib/lutaml.rb +0 -1
- metadata +48 -21
- data/lib/lutaml/cli/commands/base_command.rb +0 -118
- data/lib/lutaml/command_line.rb +0 -272
- data/lib/lutaml/sysml/allocate.rb +0 -9
- data/lib/lutaml/sysml/allocated.rb +0 -9
- data/lib/lutaml/sysml/binding_connector.rb +0 -9
- data/lib/lutaml/sysml/block.rb +0 -32
- data/lib/lutaml/sysml/constraint_block.rb +0 -14
- data/lib/lutaml/sysml/copy.rb +0 -8
- data/lib/lutaml/sysml/derive_requirement.rb +0 -9
- data/lib/lutaml/sysml/nested_connector_end.rb +0 -13
- data/lib/lutaml/sysml/refine.rb +0 -9
- data/lib/lutaml/sysml/requirement.rb +0 -44
- data/lib/lutaml/sysml/requirement_related.rb +0 -9
- data/lib/lutaml/sysml/satisfy.rb +0 -9
- data/lib/lutaml/sysml/test_case.rb +0 -25
- data/lib/lutaml/sysml/trace.rb +0 -9
- data/lib/lutaml/sysml/verify.rb +0 -8
- data/lib/lutaml/sysml/xmi_file.rb +0 -486
- data/lib/lutaml/sysml.rb +0 -11
|
@@ -25,9 +25,6 @@ module Lutaml
|
|
|
25
25
|
diagram.name = ea_diagram.name
|
|
26
26
|
diagram.xmi_id = normalize_guid_to_xmi_format(ea_diagram.ea_guid,
|
|
27
27
|
"EAID")
|
|
28
|
-
# TODO: Fix diagram_type assignment -
|
|
29
|
-
# lutaml-model compatibility issue
|
|
30
|
-
# diagram.diagram_type = ea_diagram.diagram_type
|
|
31
28
|
|
|
32
29
|
# Map package relationship - use GUID not numeric ID
|
|
33
30
|
if ea_diagram.package_id
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_transformer"
|
|
4
|
+
require_relative "attribute_transformer"
|
|
5
|
+
require_relative "generalization_transformer"
|
|
6
|
+
require_relative "association_builder"
|
|
7
|
+
|
|
8
|
+
module Lutaml
|
|
9
|
+
module Qea
|
|
10
|
+
module Factory
|
|
11
|
+
class GeneralizationBuilder < BaseTransformer
|
|
12
|
+
def load_generalization(object_id, visited = Set.new, is_leaf = true) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity,Style/OptionalBooleanParameter
|
|
13
|
+
return nil if object_id.nil?
|
|
14
|
+
|
|
15
|
+
if visited.include?(object_id)
|
|
16
|
+
warn "Circular inheritance detected for object_id #{object_id}, " \
|
|
17
|
+
"stopping recursion"
|
|
18
|
+
return nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
visited = visited.dup.add(object_id)
|
|
22
|
+
|
|
23
|
+
current_obj = find_object_by_id(object_id)
|
|
24
|
+
return nil unless current_obj
|
|
25
|
+
|
|
26
|
+
ea_connector = database.connectors_for_object(object_id)
|
|
27
|
+
.find { |c| c.generalization? && c.start_object_id == object_id }
|
|
28
|
+
|
|
29
|
+
gen_transformer = GeneralizationTransformer.new(database)
|
|
30
|
+
generalization = if ea_connector.nil?
|
|
31
|
+
gen_transformer.transform(nil, current_obj)
|
|
32
|
+
else
|
|
33
|
+
gen_transformer.transform(ea_connector,
|
|
34
|
+
current_obj)
|
|
35
|
+
end
|
|
36
|
+
return nil unless generalization
|
|
37
|
+
|
|
38
|
+
current_attrs = load_attributes(object_id)
|
|
39
|
+
current_assoc_attrs = AssociationBuilder.new(database)
|
|
40
|
+
.load_association_attributes(object_id)
|
|
41
|
+
general_attrs = convert_to_general_attributes(
|
|
42
|
+
current_attrs + current_assoc_attrs,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
upper_klass = generalization.general_upper_klass
|
|
46
|
+
gen_name = generalization.general_name
|
|
47
|
+
general_attrs.each do |attr|
|
|
48
|
+
attr.gen_name = gen_name
|
|
49
|
+
name_ns = case attr.type_ns
|
|
50
|
+
when "core", "gml"
|
|
51
|
+
upper_klass
|
|
52
|
+
else
|
|
53
|
+
attr.type_ns
|
|
54
|
+
end
|
|
55
|
+
attr.name_ns = name_ns || upper_klass
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
generalization.general_attributes = general_attrs
|
|
59
|
+
.sort_by { |a| [a.name.to_s, a.id] }
|
|
60
|
+
|
|
61
|
+
generalization.attributes = transform_general_attributes(
|
|
62
|
+
generalization,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
generalization.owned_props = generalization.attributes
|
|
66
|
+
.reject(&:has_association)
|
|
67
|
+
generalization.assoc_props = generalization.attributes
|
|
68
|
+
.select(&:has_association)
|
|
69
|
+
|
|
70
|
+
parent_object_id = ea_connector&.end_object_id
|
|
71
|
+
if parent_object_id
|
|
72
|
+
parent_gen = load_generalization(parent_object_id, visited, false)
|
|
73
|
+
if parent_gen
|
|
74
|
+
generalization.general = parent_gen
|
|
75
|
+
generalization.has_general = true
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
if is_leaf && generalization.has_general
|
|
80
|
+
collect_inherited_properties(generalization)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
generalization
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def load_association_generalizations(object_id)
|
|
87
|
+
return [] if object_id.nil?
|
|
88
|
+
|
|
89
|
+
gen_connectors = database.connectors_for_object(object_id)
|
|
90
|
+
.select { |c| c.generalization? && c.start_object_id == object_id }
|
|
91
|
+
|
|
92
|
+
gen_connectors.filter_map do |ea_connector|
|
|
93
|
+
guid = ea_connector.ea_guid
|
|
94
|
+
parent_object_id = ea_connector.end_object_id
|
|
95
|
+
|
|
96
|
+
parent_obj = find_object_by_id(parent_object_id)
|
|
97
|
+
next unless parent_obj
|
|
98
|
+
|
|
99
|
+
Lutaml::Uml::AssociationGeneralization.new.tap do |ag|
|
|
100
|
+
ag.id = normalize_guid_to_xmi_format(guid, "EAID")
|
|
101
|
+
ag.type = "uml:Generalization"
|
|
102
|
+
ag.general = normalize_guid_to_xmi_format(parent_obj.ea_guid,
|
|
103
|
+
"EAID")
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def convert_to_general_attributes(attributes)
|
|
109
|
+
attributes.map do |attr|
|
|
110
|
+
Lutaml::Uml::GeneralAttribute.new.tap do |gen_attr|
|
|
111
|
+
gen_attr.id = attr.id
|
|
112
|
+
gen_attr.name = attr.name
|
|
113
|
+
gen_attr.type = attr.type
|
|
114
|
+
gen_attr.xmi_id = attr.xmi_id
|
|
115
|
+
gen_attr.is_derived = !!attr.is_derived
|
|
116
|
+
gen_attr.cardinality = attr.cardinality
|
|
117
|
+
gen_attr.definition = attr.definition&.strip
|
|
118
|
+
gen_attr.association = attr.association
|
|
119
|
+
gen_attr.has_association = !!attr.association
|
|
120
|
+
gen_attr.type_ns = attr.type_ns
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def convert_to_top_element_attributes(attributes)
|
|
126
|
+
attributes.map do |attr|
|
|
127
|
+
Lutaml::Uml::TopElementAttribute.new.tap do |top_attr|
|
|
128
|
+
top_attr.id = attr.id
|
|
129
|
+
top_attr.name = attr.name
|
|
130
|
+
top_attr.type = attr.type
|
|
131
|
+
top_attr.xmi_id = attr.xmi_id
|
|
132
|
+
top_attr.cardinality = attr.cardinality
|
|
133
|
+
top_attr.definition = attr.definition&.strip
|
|
134
|
+
top_attr.association = attr.association
|
|
135
|
+
top_attr.type_ns = attr.type_ns
|
|
136
|
+
top_attr.is_derived = !!attr.is_derived
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
private
|
|
142
|
+
|
|
143
|
+
def load_attributes(object_id)
|
|
144
|
+
return [] if object_id.nil?
|
|
145
|
+
|
|
146
|
+
ea_attributes = database.attributes_for_object(object_id)
|
|
147
|
+
.sort_by { |a| a.pos || 0 }
|
|
148
|
+
|
|
149
|
+
AttributeTransformer.new(database).transform_collection(ea_attributes)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def transform_general_attributes(generalization)
|
|
153
|
+
upper_klass = generalization.general_upper_klass
|
|
154
|
+
gen_name = generalization.general_name
|
|
155
|
+
gen_attrs = generalization.general_attributes
|
|
156
|
+
|
|
157
|
+
gen_attrs.map do |attr|
|
|
158
|
+
transformed = attr.dup
|
|
159
|
+
name_ns = case attr.type_ns
|
|
160
|
+
when "core", "gml"
|
|
161
|
+
upper_klass
|
|
162
|
+
else
|
|
163
|
+
attr.type_ns
|
|
164
|
+
end
|
|
165
|
+
name_ns = upper_klass if name_ns.nil?
|
|
166
|
+
transformed.name_ns = name_ns
|
|
167
|
+
transformed.gen_name = gen_name
|
|
168
|
+
transformed.name = "" if transformed.name.nil?
|
|
169
|
+
transformed
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def collect_inherited_properties(generalization)
|
|
174
|
+
inherited_props = []
|
|
175
|
+
inherited_assoc_props = []
|
|
176
|
+
level = 0
|
|
177
|
+
|
|
178
|
+
current_gen = generalization.general
|
|
179
|
+
while current_gen
|
|
180
|
+
[current_gen.general_attributes,
|
|
181
|
+
current_gen.attributes].each do |attr_list|
|
|
182
|
+
attr_list&.each do |attr|
|
|
183
|
+
attr.upper_klass = current_gen.general_upper_klass
|
|
184
|
+
attr.level = level
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
current_gen.attributes.reverse_each do |attr|
|
|
189
|
+
inherited_attr = attr.dup
|
|
190
|
+
inherited_attr.upper_klass = current_gen.general_upper_klass
|
|
191
|
+
inherited_attr.gen_name = current_gen.general_name
|
|
192
|
+
inherited_attr.level = level
|
|
193
|
+
|
|
194
|
+
if attr.has_association
|
|
195
|
+
inherited_assoc_props << inherited_attr
|
|
196
|
+
else
|
|
197
|
+
inherited_props << inherited_attr
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
level += 1
|
|
202
|
+
current_gen = current_gen.general
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
generalization.inherited_props = inherited_props.reverse
|
|
206
|
+
generalization.inherited_assoc_props = inherited_assoc_props.reverse
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
@@ -29,8 +29,7 @@ module Lutaml
|
|
|
29
29
|
ea_package.notes.nil? || ea_package.notes.empty?
|
|
30
30
|
|
|
31
31
|
# Load and transform tagged values
|
|
32
|
-
|
|
33
|
-
# pkg.tagged_values = load_tagged_values(ea_package.ea_guid)
|
|
32
|
+
pkg.tagged_values = load_tagged_values(ea_package.ea_guid)
|
|
34
33
|
|
|
35
34
|
# Load stereotype from t_xref
|
|
36
35
|
stereotype = load_stereotype(ea_package.ea_guid)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Qea
|
|
5
|
+
module Factory
|
|
6
|
+
class StereotypeLoader
|
|
7
|
+
def initialize(database)
|
|
8
|
+
@database = database
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def load_from_xref(ea_guid)
|
|
12
|
+
return nil if ea_guid.nil?
|
|
13
|
+
return nil unless @database.xrefs
|
|
14
|
+
|
|
15
|
+
xref = @database.xrefs.find do |x|
|
|
16
|
+
x.client == ea_guid && x.name == "Stereotypes" &&
|
|
17
|
+
x.type == "element property"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
return nil unless xref
|
|
21
|
+
|
|
22
|
+
description = xref.description
|
|
23
|
+
return nil if description.nil? || description.empty?
|
|
24
|
+
|
|
25
|
+
if description =~ /@STEREO;Name=([^;]+);/
|
|
26
|
+
return Regexp.last_match(1)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Qea
|
|
5
|
+
class Database
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
# Build a group index from a collection by a given attribute.
|
|
9
|
+
# BaseRepository overrides group_by to take a symbol (not a block),
|
|
10
|
+
# so we use each_with_object instead.
|
|
11
|
+
#
|
|
12
|
+
# @param collection [Array] Collection to index
|
|
13
|
+
# @param method [Symbol] Attribute method to group by
|
|
14
|
+
# @param single [Boolean] If true, return single object (last match)
|
|
15
|
+
# instead of array
|
|
16
|
+
# @return [Hash] Group index
|
|
17
|
+
def build_group_index(collection, method, single: false)
|
|
18
|
+
if single
|
|
19
|
+
collection.each_with_object({}) do |item, hash|
|
|
20
|
+
key = item.send(method)
|
|
21
|
+
hash[key] = item if key
|
|
22
|
+
end
|
|
23
|
+
else
|
|
24
|
+
collection.each_with_object({}) do |item, hash|
|
|
25
|
+
key = item.send(method)
|
|
26
|
+
(hash[key] ||= []) << item if key
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Eagerly build all lazy lookup indexes before freezing
|
|
32
|
+
def build_lookup_indexes
|
|
33
|
+
@objects_by_guid = build_group_index(objects, :ea_guid, single: true)
|
|
34
|
+
@attributes_by_object_id = build_group_index(attributes, :ea_object_id)
|
|
35
|
+
@operations_by_object_id = build_group_index(operations, :ea_object_id)
|
|
36
|
+
@operation_params_by_id = build_group_index(operation_params,
|
|
37
|
+
:operationid)
|
|
38
|
+
@connectors_by_start = build_group_index(connectors, :start_object_id)
|
|
39
|
+
@connectors_by_end = build_group_index(connectors, :end_object_id)
|
|
40
|
+
@packages_by_parent = build_group_index(packages, :parent_id)
|
|
41
|
+
@objects_by_package_id = build_group_index(objects, :package_id)
|
|
42
|
+
@diagrams_by_package_id = build_group_index(diagrams, :package_id)
|
|
43
|
+
@diagram_objects_by_id = build_group_index(diagram_objects, :diagram_id)
|
|
44
|
+
@diagram_links_by_id = build_group_index(diagram_links, :diagramid)
|
|
45
|
+
# Also build hash indexes for find_* methods
|
|
46
|
+
@packages_by_id = build_group_index(packages, :package_id, single: true)
|
|
47
|
+
@connectors_by_id = build_group_index(connectors, :connector_id,
|
|
48
|
+
single: true)
|
|
49
|
+
@diagrams_by_id = build_group_index(diagrams, :diagram_id, single: true)
|
|
50
|
+
@attributes_by_id = build_group_index(attributes, :id, single: true)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Uml
|
|
5
|
+
# Service class for safely walking UML generalization chains.
|
|
6
|
+
#
|
|
7
|
+
# Walks from a class up through its generalization hierarchy (parent classes),
|
|
8
|
+
# collecting information about each ancestor. Supports cycle detection to
|
|
9
|
+
# prevent infinite loops in malformed models.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# walker = InheritanceWalker.new(repository)
|
|
13
|
+
# walker.walk(klass) do |ancestor, level|
|
|
14
|
+
# puts "#{'-' * level} #{ancestor.name}"
|
|
15
|
+
# end
|
|
16
|
+
class InheritanceWalker
|
|
17
|
+
# @param repository [#supertype_of] Object responding to #supertype_of(class)
|
|
18
|
+
def initialize(repository)
|
|
19
|
+
@repository = repository
|
|
20
|
+
@visited = Set.new
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Walk the generalization chain from a starting class.
|
|
24
|
+
#
|
|
25
|
+
# @param klass [Lutaml::Uml::Class] The class to start from
|
|
26
|
+
# @yield [ancestor, level] Yields each ancestor class and its depth (1-based)
|
|
27
|
+
# @return [Array<[klass, level]>] Array of [ancestor_class, level] pairs in visiting order
|
|
28
|
+
#
|
|
29
|
+
# @example
|
|
30
|
+
# walker.walk(D.class) do |parent, level|
|
|
31
|
+
# puts "#{' ' * (level - 1)}Parent #{level}: #{parent.name}"
|
|
32
|
+
# end
|
|
33
|
+
def walk(klass)
|
|
34
|
+
return [] unless klass.respond_to?(:generalization) && klass.generalization
|
|
35
|
+
|
|
36
|
+
ancestors = []
|
|
37
|
+
@visited.clear
|
|
38
|
+
collect_ancestors(klass, ancestors)
|
|
39
|
+
ancestors.reverse_each.with_index(1) do |ancestor, level|
|
|
40
|
+
break if @visited.include?(ancestor.xmi_id)
|
|
41
|
+
|
|
42
|
+
@visited.add(ancestor.xmi_id)
|
|
43
|
+
yield(ancestor, level) if block_given?
|
|
44
|
+
end
|
|
45
|
+
ancestors.reverse_each.with_index(1)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Get the direct supertype (immediate parent) of a class.
|
|
49
|
+
#
|
|
50
|
+
# @param klass [Lutaml::Uml::Class]
|
|
51
|
+
# @return [Lutaml::Uml::Class, nil]
|
|
52
|
+
def supertype_of(klass)
|
|
53
|
+
@repository.supertype_of(klass)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Get all ancestors of a class in order (immediate parent first).
|
|
57
|
+
#
|
|
58
|
+
# @param klass [Lutaml::Uml::Class]
|
|
59
|
+
# @return [Array<Lutaml::Uml::Class>]
|
|
60
|
+
def ancestors_of(klass)
|
|
61
|
+
result = []
|
|
62
|
+
walk(klass) { |ancestor, _level| result << ancestor }
|
|
63
|
+
result
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
# Collect ancestors recursively into the result array.
|
|
69
|
+
# Uses a trail set for cycle detection (avoids Set mutation issues across calls).
|
|
70
|
+
def collect_ancestors(klass, result, trail = [])
|
|
71
|
+
return [] if trail.include?(klass.xmi_id) # cycle guard
|
|
72
|
+
return [] unless klass.respond_to?(:generalization) && klass.generalization
|
|
73
|
+
|
|
74
|
+
trail = trail.dup
|
|
75
|
+
trail << klass.xmi_id
|
|
76
|
+
|
|
77
|
+
general_id = klass.generalization.respond_to?(:general_id) ? klass.generalization.general_id : nil
|
|
78
|
+
parent = general_id ? find_class_by_id(general_id) : nil
|
|
79
|
+
|
|
80
|
+
return [] unless parent
|
|
81
|
+
|
|
82
|
+
result << parent
|
|
83
|
+
collect_ancestors(parent, result, trail)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def find_class_by_id(xmi_id)
|
|
87
|
+
@repository.indexes&.dig(:qualified_names, xmi_id) ||
|
|
88
|
+
@repository.send(:classes_index)&.find { |c| c.xmi_id == xmi_id }
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Uml
|
|
5
|
+
# Shared helper methods for UML model objects.
|
|
6
|
+
# These methods are used across transformers, serializers, and presenters
|
|
7
|
+
# to avoid duplication of common model traversal and formatting logic.
|
|
8
|
+
module ModelHelpers
|
|
9
|
+
# Normalize a stereotype value to a consistent Array format.
|
|
10
|
+
#
|
|
11
|
+
# @param stereotype [String, Array, nil, Symbol] The stereotype value
|
|
12
|
+
# @return [Array<String>] Array of stereotype strings
|
|
13
|
+
#
|
|
14
|
+
# @example
|
|
15
|
+
# normalize_stereotypes(nil) # => []
|
|
16
|
+
# normalize_stereotypes("enumeration") # => ["enumeration"]
|
|
17
|
+
# normalize_stereotypes(["a", "b"]) # => ["a", "b"]
|
|
18
|
+
# normalize_stereotypes(:enumeration) # => ["enumeration"]
|
|
19
|
+
def normalize_stereotypes(stereotype)
|
|
20
|
+
return [] if stereotype.nil?
|
|
21
|
+
|
|
22
|
+
case stereotype
|
|
23
|
+
when Array then stereotype.map(&:to_s)
|
|
24
|
+
when String then [stereotype]
|
|
25
|
+
when Symbol then [stereotype.to_s]
|
|
26
|
+
else [stereotype.to_s]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Build a fully qualified name by walking the namespace chain of a UML element.
|
|
31
|
+
#
|
|
32
|
+
# @param uml_element [Lutaml::Uml::TopElement, Lutaml::Uml::Package] Any element with a namespace
|
|
33
|
+
# @return [String] Fully qualified name joined by '::'
|
|
34
|
+
#
|
|
35
|
+
# @example
|
|
36
|
+
# qualified_name_for(class_in_package) # => "ModelName::PackageName::ClassName"
|
|
37
|
+
def qualified_name_for(uml_element)
|
|
38
|
+
return uml_element.name unless uml_element.respond_to?(:namespace)
|
|
39
|
+
|
|
40
|
+
parts = []
|
|
41
|
+
current = uml_element
|
|
42
|
+
|
|
43
|
+
while current
|
|
44
|
+
parts.unshift(current.name) if current.name
|
|
45
|
+
current = if current.respond_to?(:namespace) && current.namespace
|
|
46
|
+
current.namespace
|
|
47
|
+
else
|
|
48
|
+
break
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
parts.join("::")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Build a package-only namespace path (no class names).
|
|
56
|
+
#
|
|
57
|
+
# @param uml_element [Lutaml::Uml::TopElement, Lutaml::Uml::Package]
|
|
58
|
+
# @return [String] Package path joined by '::'
|
|
59
|
+
#
|
|
60
|
+
# @example
|
|
61
|
+
# package_path_for(class_in_nested_package) # => "ModelName::ParentPackage::ChildPackage"
|
|
62
|
+
def package_path_for(uml_element)
|
|
63
|
+
return uml_element.name unless uml_element.respond_to?(:namespace)
|
|
64
|
+
|
|
65
|
+
parts = []
|
|
66
|
+
current = uml_element
|
|
67
|
+
|
|
68
|
+
while current
|
|
69
|
+
if current.is_a?(Lutaml::Uml::Package)
|
|
70
|
+
parts.unshift(current.name) if current.name
|
|
71
|
+
current = current.namespace if current.respond_to?(:namespace)
|
|
72
|
+
elsif current.is_a?(Lutaml::Uml::TopElement)
|
|
73
|
+
# Stop at the first TopElement (class, enum, etc.) — namespace above is package
|
|
74
|
+
current = current.namespace if current.respond_to?(:namespace)
|
|
75
|
+
else
|
|
76
|
+
break
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
parts.join("::")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Extract the leaf class name from a fully-qualified class name or class object.
|
|
84
|
+
#
|
|
85
|
+
# @param uml_class [String, Class] A class name string or a Lutaml::Uml::* class instance
|
|
86
|
+
# @return [String] The leaf class name
|
|
87
|
+
#
|
|
88
|
+
# @example
|
|
89
|
+
# class_type_for("Lutaml::Uml::Class") # => "Class"
|
|
90
|
+
# class_type_for(some_enum_object) # => "Enumeration"
|
|
91
|
+
def class_type_for(uml_class)
|
|
92
|
+
case uml_class
|
|
93
|
+
when String then uml_class.split("::").last
|
|
94
|
+
when Class then uml_class.name.split("::").last
|
|
95
|
+
else uml_class.class.name.split("::").last
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Format a cardinality object as a "min..max" string.
|
|
100
|
+
#
|
|
101
|
+
# @param cardinality [Lutaml::Uml::Cardinality, Hash, nil]
|
|
102
|
+
# @return [String, nil] Formatted cardinality string or nil
|
|
103
|
+
#
|
|
104
|
+
# @example
|
|
105
|
+
# format_cardinality(Lutaml::Uml::Cardinality.new(min: 0, max: 5)) # => "0..5"
|
|
106
|
+
# format_cardinality({min: 1, max: nil}) # => "1..*"
|
|
107
|
+
def format_cardinality(cardinality)
|
|
108
|
+
return nil unless cardinality
|
|
109
|
+
|
|
110
|
+
min = cardinality.respond_to?(:min) ? cardinality.min : cardinality[:min]
|
|
111
|
+
max = cardinality.respond_to?(:max) ? cardinality.max : cardinality[:max]
|
|
112
|
+
return nil if min.nil? && max.nil?
|
|
113
|
+
|
|
114
|
+
min_str = min.nil? ? "0" : min.to_s
|
|
115
|
+
max_str = max.nil? ? "*" : max.to_s
|
|
116
|
+
"#{min_str}..#{max_str}"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Parse a cardinality string or hash into a {min:, max:} hash.
|
|
120
|
+
#
|
|
121
|
+
# @param min [String, nil]
|
|
122
|
+
# @param max [String, nil]
|
|
123
|
+
# @return [Hash{Symbol => String, nil}] Hash with :min and :max keys
|
|
124
|
+
def parse_cardinality(min, max)
|
|
125
|
+
{ min: min, max: max }
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -16,13 +16,13 @@ module Lutaml
|
|
|
16
16
|
attr_reader :modifier, :members
|
|
17
17
|
|
|
18
18
|
def modifier=(value)
|
|
19
|
-
@modifier = value.to_s
|
|
19
|
+
@modifier = value.to_s
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def members=(value) # rubocop:disable Metrics/MethodLength
|
|
23
23
|
@members = value.to_a.map do |member|
|
|
24
|
-
type = member.
|
|
25
|
-
attributes = member.
|
|
24
|
+
type = member.keys.first
|
|
25
|
+
attributes = member.values.first
|
|
26
26
|
attributes[:parent] = self
|
|
27
27
|
|
|
28
28
|
case type
|
data/lib/lutaml/uml/operation.rb
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
module Lutaml
|
|
4
4
|
module Uml
|
|
5
5
|
class Operation < TopElement
|
|
6
|
+
attribute :id, :string
|
|
6
7
|
attribute :return_type, :string
|
|
7
8
|
attribute :parameter_type, :string
|
|
8
9
|
|
|
9
10
|
yaml do
|
|
11
|
+
map "id", to: :id
|
|
10
12
|
map "return_type", to: :return_type
|
|
11
13
|
map "parameter_type", to: :parameter_type
|
|
12
14
|
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module UmlRepository
|
|
5
|
+
# O(1) lookup index for UML classes by various identifiers.
|
|
6
|
+
#
|
|
7
|
+
# Replaces O(n) linear scans that were repeated across multiple
|
|
8
|
+
# transformer and serializer classes.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# index = ClassLookupIndex.new(repository.classes_index)
|
|
12
|
+
# index.by_xmi_id("EAID_123...")
|
|
13
|
+
# index.by_object_id(42)
|
|
14
|
+
class ClassLookupIndex
|
|
15
|
+
def initialize(classes)
|
|
16
|
+
@by_xmi_id = {}
|
|
17
|
+
@by_object_id = {}
|
|
18
|
+
|
|
19
|
+
classes.each do |klass|
|
|
20
|
+
@by_xmi_id[klass.xmi_id] = klass if klass.xmi_id
|
|
21
|
+
if klass.respond_to?(:ea_object_id) && klass.ea_object_id
|
|
22
|
+
@by_object_id[klass.ea_object_id] = klass
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @param xmi_id [String]
|
|
28
|
+
# @return [Lutaml::Uml::Class, nil]
|
|
29
|
+
def by_xmi_id(xmi_id)
|
|
30
|
+
@by_xmi_id[xmi_id]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @param object_id [String, Integer]
|
|
34
|
+
# @return [Lutaml::Uml::Class, nil]
|
|
35
|
+
def by_object_id(object_id)
|
|
36
|
+
@by_object_id[object_id]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|