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
|
@@ -6,95 +6,60 @@ require_relative "operation_transformer"
|
|
|
6
6
|
require_relative "constraint_transformer"
|
|
7
7
|
require_relative "tagged_value_transformer"
|
|
8
8
|
require_relative "object_property_transformer"
|
|
9
|
-
require_relative "
|
|
10
|
-
require_relative "
|
|
9
|
+
require_relative "stereotype_loader"
|
|
10
|
+
require_relative "generalization_builder"
|
|
11
|
+
require_relative "association_builder"
|
|
11
12
|
require "lutaml/uml"
|
|
12
13
|
|
|
13
14
|
module Lutaml
|
|
14
15
|
module Qea
|
|
15
16
|
module Factory
|
|
16
|
-
# Transforms EA objects (Class type) to UML classes
|
|
17
17
|
class ClassTransformer < BaseTransformer
|
|
18
|
-
|
|
19
|
-
# @param ea_object [EaObject] EA object model
|
|
20
|
-
# @return [Lutaml::Uml::Class] UML class
|
|
21
|
-
def transform(ea_object) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
18
|
+
def transform(ea_object)
|
|
22
19
|
return nil if ea_object.nil?
|
|
23
20
|
|
|
24
|
-
# Allow Class, Interface, ProxyConnector, and Text objects
|
|
25
21
|
is_class_type = ea_object.uml_class? || ea_object.interface?
|
|
26
22
|
is_proxy = ea_object.object_type == "ProxyConnector"
|
|
27
23
|
is_text_class = ea_object.object_type == "Text"
|
|
28
24
|
return nil unless is_class_type || is_proxy || is_text_class
|
|
29
25
|
|
|
30
|
-
Lutaml::Uml::Class.new.tap do |klass|
|
|
31
|
-
# Map basic properties
|
|
26
|
+
Lutaml::Uml::Class.new.tap do |klass|
|
|
32
27
|
klass.name = ea_object.name
|
|
33
28
|
klass.xmi_id = normalize_guid_to_xmi_format(ea_object.ea_guid,
|
|
34
29
|
"EAID")
|
|
35
30
|
klass.is_abstract = ea_object.abstract?
|
|
36
|
-
# Text objects exported as Class in XMI
|
|
37
31
|
klass.type = "Class"
|
|
38
32
|
klass.visibility = map_visibility(ea_object.visibility)
|
|
39
33
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if ea_object.stereotype && !ea_object.stereotype.empty?
|
|
43
|
-
stereotypes << ea_object.stereotype
|
|
44
|
-
end
|
|
34
|
+
stereotypes = build_stereotypes(ea_object)
|
|
35
|
+
klass.stereotype = stereotypes unless stereotypes.empty?
|
|
45
36
|
|
|
46
|
-
# Check t_xref for additional stereotypes
|
|
47
|
-
# (only if not already added)
|
|
48
|
-
xref_stereotype = load_stereotype_from_xref(ea_object.ea_guid)
|
|
49
|
-
if xref_stereotype && !stereotypes.include?(xref_stereotype)
|
|
50
|
-
stereotypes << xref_stereotype
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Assign stereotypes (always as array for collection: true)
|
|
54
|
-
unless stereotypes.empty?
|
|
55
|
-
klass.stereotype = stereotypes
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Add "interface" stereotype if it's an interface
|
|
59
|
-
if ea_object.interface? && !klass.stereotype.include?("interface")
|
|
60
|
-
klass.stereotype << "interface"
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Map definition/notes
|
|
64
37
|
klass.definition = normalize_line_endings(ea_object.note) unless
|
|
65
38
|
ea_object.note.nil? || ea_object.note.empty?
|
|
66
39
|
|
|
67
|
-
|
|
40
|
+
gen_builder = GeneralizationBuilder.new(database)
|
|
41
|
+
assoc_builder = AssociationBuilder.new(database)
|
|
42
|
+
|
|
68
43
|
attrs = load_attributes(ea_object.ea_object_id)
|
|
69
|
-
assoc_attrs = convert_to_top_element_attributes(
|
|
70
|
-
load_association_attributes(ea_object.ea_object_id),
|
|
44
|
+
assoc_attrs = gen_builder.convert_to_top_element_attributes(
|
|
45
|
+
assoc_builder.load_association_attributes(ea_object.ea_object_id),
|
|
71
46
|
)
|
|
72
47
|
klass.attributes = attrs + assoc_attrs
|
|
73
48
|
|
|
74
|
-
# Load and transform operations
|
|
75
49
|
klass.operations = load_operations(ea_object.ea_object_id)
|
|
76
|
-
|
|
77
|
-
# Load and transform constraints
|
|
78
50
|
klass.constraints = load_constraints(ea_object.ea_object_id)
|
|
79
|
-
|
|
80
|
-
# Load and transform tagged values
|
|
81
51
|
klass.tagged_values = load_tagged_values(ea_object.ea_guid)
|
|
82
|
-
|
|
83
|
-
# Load and transform object properties (as additional tagged values)
|
|
84
52
|
klass.tagged_values.concat(
|
|
85
53
|
load_object_properties(ea_object.ea_object_id),
|
|
86
54
|
)
|
|
87
55
|
|
|
88
|
-
|
|
89
|
-
klass.generalization = load_generalization(ea_object.ea_object_id)
|
|
90
|
-
|
|
91
|
-
# Load association generalizations
|
|
92
|
-
klass.association_generalization = load_association_generalizations(
|
|
56
|
+
klass.generalization = gen_builder.load_generalization(
|
|
93
57
|
ea_object.ea_object_id,
|
|
94
58
|
)
|
|
59
|
+
klass.association_generalization = gen_builder
|
|
60
|
+
.load_association_generalizations(ea_object.ea_object_id)
|
|
95
61
|
|
|
96
|
-
|
|
97
|
-
klass.associations = load_class_associations(
|
|
62
|
+
klass.associations = assoc_builder.load_class_associations(
|
|
98
63
|
ea_object.ea_object_id, ea_object.ea_guid
|
|
99
64
|
)
|
|
100
65
|
end
|
|
@@ -102,584 +67,69 @@ module Lutaml
|
|
|
102
67
|
|
|
103
68
|
private
|
|
104
69
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
70
|
+
def build_stereotypes(ea_object)
|
|
71
|
+
stereotypes = []
|
|
72
|
+
if ea_object.stereotype && !ea_object.stereotype.empty?
|
|
73
|
+
stereotypes << ea_object.stereotype
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
xref_stereotype = StereotypeLoader.new(database)
|
|
77
|
+
.load_from_xref(ea_object.ea_guid)
|
|
78
|
+
if xref_stereotype && !stereotypes.include?(xref_stereotype)
|
|
79
|
+
stereotypes << xref_stereotype
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
if ea_object.interface? && !stereotypes.include?("interface")
|
|
83
|
+
stereotypes << "interface"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
stereotypes
|
|
87
|
+
end
|
|
88
|
+
|
|
108
89
|
def load_attributes(object_id)
|
|
109
90
|
return [] if object_id.nil?
|
|
110
91
|
|
|
111
92
|
ea_attributes = database.attributes_for_object(object_id)
|
|
112
93
|
.sort_by { |a| a.pos || 0 }
|
|
113
|
-
|
|
114
|
-
attribute_transformer = AttributeTransformer.new(database)
|
|
115
|
-
attribute_transformer.transform_collection(ea_attributes)
|
|
94
|
+
AttributeTransformer.new(database).transform_collection(ea_attributes)
|
|
116
95
|
end
|
|
117
96
|
|
|
118
|
-
# Load and transform operations for a class
|
|
119
|
-
# @param object_id [Integer] Object ID
|
|
120
|
-
# @return [Array<Lutaml::Uml::Operation>] UML operations
|
|
121
97
|
def load_operations(object_id)
|
|
122
98
|
return [] if object_id.nil?
|
|
123
99
|
|
|
124
100
|
ea_operations = database.operations_for_object(object_id)
|
|
125
101
|
.sort_by { |op| op.pos || 0 }
|
|
126
|
-
|
|
127
|
-
operation_transformer = OperationTransformer.new(database)
|
|
128
|
-
operation_transformer.transform_collection(ea_operations)
|
|
102
|
+
OperationTransformer.new(database).transform_collection(ea_operations)
|
|
129
103
|
end
|
|
130
104
|
|
|
131
|
-
# Load and transform constraints for a class
|
|
132
|
-
# @param object_id [Integer] Object ID
|
|
133
|
-
# @return [Array<Lutaml::Uml::Constraint>] UML constraints
|
|
134
105
|
def load_constraints(object_id)
|
|
135
106
|
return [] if object_id.nil?
|
|
136
107
|
return [] unless database.object_constraints
|
|
137
108
|
|
|
138
|
-
# Filter constraints for this object from the in-memory collection
|
|
139
109
|
ea_constraints = database.object_constraints.select do |c|
|
|
140
110
|
c.ea_object_id == object_id
|
|
141
111
|
end
|
|
142
|
-
|
|
143
|
-
# Transform to UML constraints
|
|
144
|
-
constraint_transformer = ConstraintTransformer.new(database)
|
|
145
|
-
constraint_transformer.transform_collection(ea_constraints)
|
|
112
|
+
ConstraintTransformer.new(database).transform_collection(ea_constraints)
|
|
146
113
|
end
|
|
147
114
|
|
|
148
|
-
# Load and transform tagged values for a class
|
|
149
|
-
# @param ea_guid [String] Element GUID
|
|
150
|
-
# @return [Array<Lutaml::Uml::TaggedValue>] UML tagged values
|
|
151
115
|
def load_tagged_values(ea_guid)
|
|
152
116
|
return [] if ea_guid.nil?
|
|
153
117
|
return [] unless database.tagged_values
|
|
154
118
|
|
|
155
|
-
# Filter tagged values for this element from the in-memory collection
|
|
156
119
|
ea_tags = database.tagged_values.select do |tag|
|
|
157
120
|
tag.element_id == ea_guid
|
|
158
121
|
end
|
|
159
|
-
|
|
160
|
-
# Transform to UML tagged values
|
|
161
|
-
tag_transformer = TaggedValueTransformer.new(database)
|
|
162
|
-
tag_transformer.transform_collection(ea_tags)
|
|
122
|
+
TaggedValueTransformer.new(database).transform_collection(ea_tags)
|
|
163
123
|
end
|
|
164
124
|
|
|
165
|
-
# Load and transform object properties for a class
|
|
166
|
-
# @param object_id [Integer] Object ID
|
|
167
|
-
# @return [Array<Lutaml::Uml::TaggedValue>] UML tagged values
|
|
168
125
|
def load_object_properties(object_id)
|
|
169
126
|
return [] if object_id.nil?
|
|
170
127
|
return [] unless database.object_properties
|
|
171
128
|
|
|
172
|
-
# Filter object properties for this object from the in-memory
|
|
173
|
-
# collection
|
|
174
129
|
ea_props = database.object_properties.select do |prop|
|
|
175
130
|
prop.ea_object_id == object_id
|
|
176
131
|
end
|
|
177
|
-
|
|
178
|
-
# Transform to UML tagged values
|
|
179
|
-
prop_transformer = ObjectPropertyTransformer.new(database)
|
|
180
|
-
prop_transformer.transform_collection(ea_props)
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
# Load stereotype from t_xref table
|
|
184
|
-
# @param ea_guid [String] Element GUID
|
|
185
|
-
# @return [String, nil] Stereotype value
|
|
186
|
-
def load_stereotype_from_xref(ea_guid) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
187
|
-
return nil if ea_guid.nil?
|
|
188
|
-
return nil unless database.xrefs
|
|
189
|
-
|
|
190
|
-
# Find stereotype xref from the in-memory collection
|
|
191
|
-
xref = database.xrefs.find do |x|
|
|
192
|
-
x.client == ea_guid && x.name == "Stereotypes" &&
|
|
193
|
-
x.type == "element property"
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
return nil unless xref
|
|
197
|
-
|
|
198
|
-
# Parse stereotype from Description field
|
|
199
|
-
# Format: @STEREO;Name=FeatureType;FQName=...;@ENDSTEREO;
|
|
200
|
-
description = xref.description
|
|
201
|
-
return nil if description.nil? || description.empty?
|
|
202
|
-
|
|
203
|
-
# Extract the Name value from the @STEREO format
|
|
204
|
-
if description =~ /@STEREO;Name=([^;]+);/
|
|
205
|
-
return $1
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
nil
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
# Load generalization for a class
|
|
212
|
-
# @param object_id [Integer] Object ID
|
|
213
|
-
# @param visited [Set] Set of visited object IDs to prevent
|
|
214
|
-
# circular references
|
|
215
|
-
# @param is_leaf [Boolean] Whether this is the leaf class
|
|
216
|
-
# (not a parent in recursion)
|
|
217
|
-
# @return [Lutaml::Uml::Generalization, nil] UML generalization or nil
|
|
218
|
-
def load_generalization(object_id, visited = Set.new, is_leaf = true) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity,Style/OptionalBooleanParameter
|
|
219
|
-
return nil if object_id.nil?
|
|
220
|
-
|
|
221
|
-
# Detect circular reference
|
|
222
|
-
if visited.include?(object_id)
|
|
223
|
-
warn "Circular inheritance detected for object_id #{object_id}, " \
|
|
224
|
-
"stopping recursion"
|
|
225
|
-
return nil
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
# Add current object to visited set
|
|
229
|
-
visited = visited.dup.add(object_id)
|
|
230
|
-
|
|
231
|
-
# 1. Load CURRENT object
|
|
232
|
-
current_obj = find_object_by_id(object_id)
|
|
233
|
-
return nil unless current_obj
|
|
234
|
-
|
|
235
|
-
# 2. Find generalization connector where this class is the subtype
|
|
236
|
-
ea_connector = database.connectors_for_object(object_id)
|
|
237
|
-
.find { |c| c.generalization? && c.start_object_id == object_id }
|
|
238
|
-
|
|
239
|
-
# 3. Create generalization object for current class
|
|
240
|
-
# Even if no parent exists, we need a Generalization
|
|
241
|
-
# representing this class
|
|
242
|
-
gen_transformer = GeneralizationTransformer.new(database)
|
|
243
|
-
generalization = if ea_connector.nil?
|
|
244
|
-
# No parent - create terminal generalization
|
|
245
|
-
gen_transformer.transform(nil, current_obj)
|
|
246
|
-
else
|
|
247
|
-
# Has parent - create generalization with parent connector
|
|
248
|
-
gen_transformer
|
|
249
|
-
.transform(ea_connector, current_obj)
|
|
250
|
-
end
|
|
251
|
-
return nil unless generalization
|
|
252
|
-
|
|
253
|
-
# 4. Load CURRENT object attributes and convert to GeneralAttribute
|
|
254
|
-
current_attrs = load_attributes(object_id)
|
|
255
|
-
current_assoc_attrs = load_association_attributes(object_id)
|
|
256
|
-
general_attrs = convert_to_general_attributes(
|
|
257
|
-
current_attrs + current_assoc_attrs,
|
|
258
|
-
)
|
|
259
|
-
|
|
260
|
-
# Set gen_name and name_ns on general_attributes
|
|
261
|
-
# (matches current class context)
|
|
262
|
-
upper_klass = generalization.general_upper_klass
|
|
263
|
-
gen_name = generalization.general_name
|
|
264
|
-
general_attrs.each do |attr|
|
|
265
|
-
attr.gen_name = gen_name
|
|
266
|
-
# Determine name_ns based on type_ns
|
|
267
|
-
# (same logic as transform_general_attributes)
|
|
268
|
-
name_ns = case attr.type_ns
|
|
269
|
-
when "core", "gml"
|
|
270
|
-
upper_klass
|
|
271
|
-
else
|
|
272
|
-
attr.type_ns
|
|
273
|
-
end
|
|
274
|
-
attr.name_ns = name_ns || upper_klass
|
|
275
|
-
end
|
|
276
|
-
|
|
277
|
-
generalization.general_attributes = general_attrs
|
|
278
|
-
.sort_by { |a| [a.name.to_s, a.id] }
|
|
279
|
-
|
|
280
|
-
# 5. Transform attributes (set name_ns, gen_name) -
|
|
281
|
-
# creates working copies
|
|
282
|
-
generalization.attributes = transform_general_attributes(
|
|
283
|
-
generalization,
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
# 6. Partition into owned_props and assoc_props
|
|
287
|
-
generalization.owned_props = generalization.attributes
|
|
288
|
-
.reject(&:has_association)
|
|
289
|
-
generalization.assoc_props = generalization.attributes
|
|
290
|
-
.select(&:has_association)
|
|
291
|
-
|
|
292
|
-
# 7. Recursively load PARENT generalization with circular
|
|
293
|
-
# reference detection
|
|
294
|
-
# Pass is_leaf=false so parent doesn't populate inherited_props
|
|
295
|
-
parent_object_id = ea_connector&.end_object_id
|
|
296
|
-
if parent_object_id
|
|
297
|
-
parent_gen = load_generalization(parent_object_id, visited, false)
|
|
298
|
-
if parent_gen
|
|
299
|
-
generalization.general = parent_gen
|
|
300
|
-
generalization.has_general = true
|
|
301
|
-
end
|
|
302
|
-
end
|
|
303
|
-
|
|
304
|
-
# 8. Collect inherited properties from ancestor chain
|
|
305
|
-
# Only populate inherited_props at the LEAF level
|
|
306
|
-
# (matches XMI behavior)
|
|
307
|
-
if is_leaf && generalization.has_general
|
|
308
|
-
collect_inherited_properties(generalization)
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
generalization
|
|
312
|
-
end
|
|
313
|
-
|
|
314
|
-
# Convert TopElementAttribute array to GeneralAttribute array
|
|
315
|
-
# @param attributes [Array<Lutaml::Uml::TopElementAttribute>]
|
|
316
|
-
# @return [Array<Lutaml::Uml::GeneralAttribute>]
|
|
317
|
-
def convert_to_general_attributes(attributes) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
318
|
-
attributes.map do |attr|
|
|
319
|
-
Lutaml::Uml::GeneralAttribute.new.tap do |gen_attr|
|
|
320
|
-
gen_attr.id = attr.id
|
|
321
|
-
gen_attr.name = attr.name
|
|
322
|
-
gen_attr.type = attr.type
|
|
323
|
-
gen_attr.xmi_id = attr.xmi_id
|
|
324
|
-
gen_attr.is_derived = !!attr.is_derived
|
|
325
|
-
gen_attr.cardinality = attr.cardinality
|
|
326
|
-
gen_attr.definition = attr.definition&.strip
|
|
327
|
-
gen_attr.association = attr.association
|
|
328
|
-
gen_attr.has_association = !!attr.association
|
|
329
|
-
gen_attr.type_ns = attr.type_ns
|
|
330
|
-
end
|
|
331
|
-
end
|
|
332
|
-
end
|
|
333
|
-
|
|
334
|
-
# Convert GeneralAttribute array to TopElementAttribute array
|
|
335
|
-
# @param attributes [Array<Lutaml::Uml::TopElementAttribute>]
|
|
336
|
-
# @return [Array<Lutaml::Uml::TopElementAttribute>]
|
|
337
|
-
def convert_to_top_element_attributes(attributes) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
338
|
-
attributes.map do |attr|
|
|
339
|
-
Lutaml::Uml::TopElementAttribute.new.tap do |top_attr|
|
|
340
|
-
top_attr.id = attr.id
|
|
341
|
-
top_attr.name = attr.name
|
|
342
|
-
top_attr.type = attr.type
|
|
343
|
-
top_attr.xmi_id = attr.xmi_id
|
|
344
|
-
top_attr.cardinality = attr.cardinality
|
|
345
|
-
top_attr.definition = attr.definition&.strip
|
|
346
|
-
top_attr.association = attr.association
|
|
347
|
-
top_attr.type_ns = attr.type_ns
|
|
348
|
-
top_attr.is_derived = !!attr.is_derived
|
|
349
|
-
end
|
|
350
|
-
end
|
|
351
|
-
end
|
|
352
|
-
|
|
353
|
-
# Transform GeneralAttributes with context (name_ns, gen_name)
|
|
354
|
-
# Similar to XMI's create_uml_attributes
|
|
355
|
-
# @param generalization [Lutaml::Uml::Generalization]
|
|
356
|
-
# @return [Array<Lutaml::Uml::GeneralAttribute>]
|
|
357
|
-
def transform_general_attributes(generalization) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
358
|
-
upper_klass = generalization.general_upper_klass
|
|
359
|
-
gen_name = generalization.general_name
|
|
360
|
-
gen_attrs = generalization.general_attributes
|
|
361
|
-
|
|
362
|
-
gen_attrs.map do |attr|
|
|
363
|
-
# Clone to avoid mutation
|
|
364
|
-
transformed = attr.dup
|
|
365
|
-
|
|
366
|
-
# Set name_ns based on type_ns
|
|
367
|
-
name_ns = case attr.type_ns
|
|
368
|
-
when "core", "gml"
|
|
369
|
-
upper_klass
|
|
370
|
-
else
|
|
371
|
-
attr.type_ns
|
|
372
|
-
end
|
|
373
|
-
name_ns = upper_klass if name_ns.nil?
|
|
374
|
-
|
|
375
|
-
transformed.name_ns = name_ns
|
|
376
|
-
transformed.gen_name = gen_name
|
|
377
|
-
transformed.name = "" if transformed.name.nil?
|
|
378
|
-
|
|
379
|
-
transformed
|
|
380
|
-
end
|
|
381
|
-
end
|
|
382
|
-
|
|
383
|
-
# Collect inherited properties from ancestor chain
|
|
384
|
-
# Similar to XMI's loop_general_item
|
|
385
|
-
# @param generalization [Lutaml::Uml::Generalization]
|
|
386
|
-
# @return [void] (modifies generalization in place)
|
|
387
|
-
def collect_inherited_properties(generalization) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
388
|
-
inherited_props = []
|
|
389
|
-
inherited_assoc_props = []
|
|
390
|
-
level = 0
|
|
391
|
-
|
|
392
|
-
# Walk the general chain
|
|
393
|
-
current_gen = generalization.general
|
|
394
|
-
while current_gen
|
|
395
|
-
# Set metadata on BOTH general_attributes and attributes
|
|
396
|
-
# (matches XMI behavior)
|
|
397
|
-
# upper_klass and level are set during the inheritance walk
|
|
398
|
-
[current_gen.general_attributes,
|
|
399
|
-
current_gen.attributes].each do |attr_list|
|
|
400
|
-
attr_list&.each do |attr|
|
|
401
|
-
attr.upper_klass = current_gen.general_upper_klass
|
|
402
|
-
attr.level = level
|
|
403
|
-
end
|
|
404
|
-
end
|
|
405
|
-
|
|
406
|
-
# Process each attribute in reverse order
|
|
407
|
-
# (to show super class first after reversal)
|
|
408
|
-
current_gen.attributes.reverse_each do |attr|
|
|
409
|
-
# Clone attribute for inherited collection
|
|
410
|
-
inherited_attr = attr.dup
|
|
411
|
-
inherited_attr.upper_klass = current_gen.general_upper_klass
|
|
412
|
-
inherited_attr.gen_name = current_gen.general_name
|
|
413
|
-
inherited_attr.level = level
|
|
414
|
-
|
|
415
|
-
# Partition by association
|
|
416
|
-
if attr.has_association
|
|
417
|
-
inherited_assoc_props << inherited_attr
|
|
418
|
-
else
|
|
419
|
-
inherited_props << inherited_attr
|
|
420
|
-
end
|
|
421
|
-
end
|
|
422
|
-
|
|
423
|
-
# Move to next level
|
|
424
|
-
level += 1
|
|
425
|
-
current_gen = current_gen.general
|
|
426
|
-
end
|
|
427
|
-
|
|
428
|
-
# Reverse to show super class first
|
|
429
|
-
generalization.inherited_props = inherited_props.reverse
|
|
430
|
-
generalization.inherited_assoc_props = inherited_assoc_props.reverse
|
|
431
|
-
end
|
|
432
|
-
|
|
433
|
-
# Load association generalizations for a class
|
|
434
|
-
# @param object_id [Integer] Object ID
|
|
435
|
-
# @return [Array<Lutaml::Uml::AssociationGeneralization>]
|
|
436
|
-
# UML association generalizations
|
|
437
|
-
def load_association_generalizations(object_id) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
438
|
-
return [] if object_id.nil?
|
|
439
|
-
|
|
440
|
-
# Find ALL generalization connectors for this class
|
|
441
|
-
gen_connectors = database.connectors_for_object(object_id)
|
|
442
|
-
.select { |c| c.generalization? && c.start_object_id == object_id }
|
|
443
|
-
|
|
444
|
-
gen_connectors.filter_map do |ea_connector|
|
|
445
|
-
guid = ea_connector.ea_guid
|
|
446
|
-
parent_object_id = ea_connector.end_object_id
|
|
447
|
-
|
|
448
|
-
# Find parent object to get its GUID
|
|
449
|
-
parent_obj = find_object_by_id(parent_object_id)
|
|
450
|
-
next unless parent_obj
|
|
451
|
-
|
|
452
|
-
Lutaml::Uml::AssociationGeneralization.new.tap do |ag|
|
|
453
|
-
ag.id = normalize_guid_to_xmi_format(guid, "EAID")
|
|
454
|
-
ag.type = "uml:Generalization"
|
|
455
|
-
ag.general = normalize_guid_to_xmi_format(parent_obj.ea_guid,
|
|
456
|
-
"EAID")
|
|
457
|
-
end
|
|
458
|
-
end
|
|
459
|
-
end
|
|
460
|
-
|
|
461
|
-
# Load associations for a class
|
|
462
|
-
# Creates Association objects from navigable association ends
|
|
463
|
-
# (ownedAttributes with association markers)
|
|
464
|
-
# This matches XMI behavior where current class is always the owner
|
|
465
|
-
# @param object_id [Integer] Object ID
|
|
466
|
-
# @param object_guid [String] Object GUID
|
|
467
|
-
# @return [Array<Lutaml::Uml::Association>] UML associations
|
|
468
|
-
def load_class_associations(object_id, object_guid) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
469
|
-
return [] if object_id.nil?
|
|
470
|
-
|
|
471
|
-
associations = []
|
|
472
|
-
normalized_owner_xmi_id = normalize_guid_to_xmi_format(object_guid,
|
|
473
|
-
"EAID")
|
|
474
|
-
|
|
475
|
-
assoc_types = ["Association", "Aggregation", "Composition"].freeze
|
|
476
|
-
assoc_connectors = database.connectors_for_object(object_id)
|
|
477
|
-
.select { |c| assoc_types.include?(c.connector_type) }
|
|
478
|
-
|
|
479
|
-
assoc_connectors.each do |ea_connector| # rubocop:disable Metrics/BlockLength
|
|
480
|
-
# Determine which end this class is on
|
|
481
|
-
is_start = ea_connector.start_object_id == object_id
|
|
482
|
-
|
|
483
|
-
# Get role name (this is the ownedAttribute name)
|
|
484
|
-
owner_end_attribute_name = if is_start
|
|
485
|
-
ea_connector.destrole
|
|
486
|
-
else
|
|
487
|
-
ea_connector.sourcerole
|
|
488
|
-
end
|
|
489
|
-
|
|
490
|
-
# Only create association if there's a navigable role name
|
|
491
|
-
# (matches XMI ownedAttribute[@association])
|
|
492
|
-
if owner_end_attribute_name.nil? || owner_end_attribute_name.empty?
|
|
493
|
-
next
|
|
494
|
-
end
|
|
495
|
-
|
|
496
|
-
# Get member end (the other class)
|
|
497
|
-
member_obj = if is_start
|
|
498
|
-
find_object_by_id(ea_connector.end_object_id)
|
|
499
|
-
else
|
|
500
|
-
find_object_by_id(ea_connector.start_object_id)
|
|
501
|
-
end
|
|
502
|
-
next unless member_obj
|
|
503
|
-
|
|
504
|
-
# Get member end attribute name
|
|
505
|
-
# (role at the opposite end, or class name if no role)
|
|
506
|
-
member_end_attribute_name = if is_start
|
|
507
|
-
ea_connector.sourcerole
|
|
508
|
-
else
|
|
509
|
-
ea_connector.destrole
|
|
510
|
-
end
|
|
511
|
-
if member_end_attribute_name.nil? ||
|
|
512
|
-
member_end_attribute_name.empty?
|
|
513
|
-
member_end_attribute_name = member_obj.name
|
|
514
|
-
end
|
|
515
|
-
|
|
516
|
-
# Get cardinality for this end
|
|
517
|
-
member_cardinality_str = if is_start
|
|
518
|
-
ea_connector.destcard
|
|
519
|
-
else
|
|
520
|
-
ea_connector.sourcecard
|
|
521
|
-
end
|
|
522
|
-
|
|
523
|
-
# Create association from this class's perspective
|
|
524
|
-
associations << Lutaml::Uml::Association.new.tap do |assoc| # rubocop:disable Metrics/BlockLength
|
|
525
|
-
assoc.xmi_id = normalize_guid_to_xmi_format(ea_connector.ea_guid,
|
|
526
|
-
"EAID")
|
|
527
|
-
unless ea_connector.name.nil? || ea_connector.name.empty?
|
|
528
|
-
assoc.name = ea_connector.name
|
|
529
|
-
end
|
|
530
|
-
|
|
531
|
-
# Owner is always the current class (matches XMI)
|
|
532
|
-
assoc.owner_end = find_object_by_id(object_id)&.name
|
|
533
|
-
assoc.owner_end_xmi_id = normalized_owner_xmi_id
|
|
534
|
-
assoc.owner_end_attribute_name = owner_end_attribute_name
|
|
535
|
-
|
|
536
|
-
# Member is the other end
|
|
537
|
-
assoc.member_end = member_obj.name
|
|
538
|
-
assoc.member_end_xmi_id = normalize_guid_to_xmi_format(
|
|
539
|
-
member_obj.ea_guid, "EAID"
|
|
540
|
-
)
|
|
541
|
-
assoc.member_end_attribute_name = member_end_attribute_name
|
|
542
|
-
|
|
543
|
-
# Set member_end_type based on connector type
|
|
544
|
-
ea_type = ea_connector.connector_type
|
|
545
|
-
assoc.member_end_type = ea_type&.downcase
|
|
546
|
-
|
|
547
|
-
# Set cardinality
|
|
548
|
-
if member_cardinality_str && !member_cardinality_str.empty?
|
|
549
|
-
parsed = parse_cardinality(member_cardinality_str)
|
|
550
|
-
if parsed[:min] || parsed[:max]
|
|
551
|
-
assoc.member_end_cardinality = Lutaml::Uml::Cardinality.new
|
|
552
|
-
.tap do |card|
|
|
553
|
-
card.min = parsed[:min]
|
|
554
|
-
card.max = parsed[:max]
|
|
555
|
-
end
|
|
556
|
-
end
|
|
557
|
-
end
|
|
558
|
-
|
|
559
|
-
# Note: XMI does not include connector notes
|
|
560
|
-
# in Association definition
|
|
561
|
-
end
|
|
562
|
-
end
|
|
563
|
-
|
|
564
|
-
associations.compact
|
|
565
|
-
end
|
|
566
|
-
|
|
567
|
-
# Load association-based attributes
|
|
568
|
-
# (navigable association ends with role names)
|
|
569
|
-
# @param object_id [Integer] Object ID
|
|
570
|
-
# @return [Array<Lutaml::Uml::TopElementAttribute>]
|
|
571
|
-
# Association-based attributes
|
|
572
|
-
def load_association_attributes(object_id) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
573
|
-
return [] if object_id.nil?
|
|
574
|
-
|
|
575
|
-
attributes = []
|
|
576
|
-
|
|
577
|
-
assoc_types = ["Association", "Aggregation", "Composition"].freeze
|
|
578
|
-
assoc_connectors = database.connectors_for_object(object_id)
|
|
579
|
-
.select { |c| assoc_types.include?(c.connector_type) }
|
|
580
|
-
obj = find_object_by_id(object_id)
|
|
581
|
-
obj_pkg_name = find_package_name(obj&.package_id)
|
|
582
|
-
|
|
583
|
-
assoc_connectors.each do |ea_connector| # rubocop:disable Metrics/BlockLength
|
|
584
|
-
# Check if this object is the source (owner) or target (member)
|
|
585
|
-
if ea_connector.start_object_id == object_id
|
|
586
|
-
# This class is the source - check for dest role
|
|
587
|
-
next if ea_connector.destrole.nil? || ea_connector.destrole.empty?
|
|
588
|
-
|
|
589
|
-
target_obj = find_object_by_id(ea_connector.end_object_id)
|
|
590
|
-
next unless target_obj
|
|
591
|
-
|
|
592
|
-
target_obj_pkg_name = find_package_name(target_obj.package_id)
|
|
593
|
-
|
|
594
|
-
attributes << create_association_attribute(
|
|
595
|
-
name: ea_connector.destrole,
|
|
596
|
-
type: target_obj.name,
|
|
597
|
-
type_xmi_id: target_obj.ea_guid,
|
|
598
|
-
association_xmi_id: ea_connector.ea_guid,
|
|
599
|
-
cardinality: ea_connector.destcard,
|
|
600
|
-
definition: ea_connector.notes,
|
|
601
|
-
gen_name: obj.name,
|
|
602
|
-
name_ns: obj_pkg_name,
|
|
603
|
-
type_ns: target_obj_pkg_name,
|
|
604
|
-
is_src: false,
|
|
605
|
-
)
|
|
606
|
-
elsif ea_connector.end_object_id == object_id
|
|
607
|
-
# This class is the target - check for source role
|
|
608
|
-
if ea_connector.sourcerole.nil? || ea_connector.sourcerole.empty?
|
|
609
|
-
next
|
|
610
|
-
end
|
|
611
|
-
|
|
612
|
-
source_obj = find_object_by_id(ea_connector.start_object_id)
|
|
613
|
-
next unless source_obj
|
|
614
|
-
|
|
615
|
-
source_obj_pkg_name = find_package_name(source_obj.package_id)
|
|
616
|
-
|
|
617
|
-
attributes << create_association_attribute(
|
|
618
|
-
name: ea_connector.sourcerole,
|
|
619
|
-
type: source_obj.name,
|
|
620
|
-
type_xmi_id: source_obj.ea_guid,
|
|
621
|
-
association_xmi_id: ea_connector.ea_guid,
|
|
622
|
-
cardinality: ea_connector.sourcecard,
|
|
623
|
-
definition: ea_connector.notes,
|
|
624
|
-
gen_name: obj.name,
|
|
625
|
-
name_ns: obj_pkg_name,
|
|
626
|
-
type_ns: source_obj_pkg_name,
|
|
627
|
-
)
|
|
628
|
-
end
|
|
629
|
-
end
|
|
630
|
-
|
|
631
|
-
attributes.compact
|
|
632
|
-
end
|
|
633
|
-
|
|
634
|
-
# Create an attribute from association end
|
|
635
|
-
# @param name [String] Attribute name (role name)
|
|
636
|
-
# @param type [String] Attribute type (class name)
|
|
637
|
-
# @param type_xmi_id [String] Type XMI ID
|
|
638
|
-
# @param association_xmi_id [String] Association XMI ID
|
|
639
|
-
# @param cardinality [String] Cardinality string
|
|
640
|
-
# @param gen_name [String] Name of the generalization object
|
|
641
|
-
# @return [Lutaml::Uml::GeneralAttribute] Created attribute
|
|
642
|
-
def create_association_attribute( # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/ParameterLists
|
|
643
|
-
name:, type:, type_xmi_id:,
|
|
644
|
-
association_xmi_id:, cardinality:, definition:,
|
|
645
|
-
gen_name:, name_ns:, type_ns:, is_src: true
|
|
646
|
-
)
|
|
647
|
-
Lutaml::Uml::GeneralAttribute.new.tap do |attr|
|
|
648
|
-
attr.name = name
|
|
649
|
-
attr.type = type
|
|
650
|
-
attr.gen_name = gen_name
|
|
651
|
-
attr.definition = definition
|
|
652
|
-
attr.xmi_id = normalize_guid_to_xmi_format(type_xmi_id, "EAID")
|
|
653
|
-
attr.association = normalize_guid_to_xmi_format(
|
|
654
|
-
association_xmi_id, "EAID"
|
|
655
|
-
)
|
|
656
|
-
attr.has_association = true
|
|
657
|
-
attr.id = normalize_guid_to_xmi_src_dst_format(
|
|
658
|
-
association_xmi_id, "EAID", is_src
|
|
659
|
-
)
|
|
660
|
-
attr.name_ns = name_ns
|
|
661
|
-
attr.type_ns = type_ns
|
|
662
|
-
|
|
663
|
-
# Map cardinality if present
|
|
664
|
-
if cardinality && !cardinality.empty?
|
|
665
|
-
parsed = parse_cardinality(cardinality)
|
|
666
|
-
if parsed[:min] || parsed[:max]
|
|
667
|
-
attr.cardinality = Lutaml::Uml::Cardinality.new.tap do |card|
|
|
668
|
-
card.min = parsed[:min]
|
|
669
|
-
card.max = parsed[:max]
|
|
670
|
-
end
|
|
671
|
-
end
|
|
672
|
-
end
|
|
673
|
-
end
|
|
674
|
-
end
|
|
675
|
-
|
|
676
|
-
# Find package name by ID
|
|
677
|
-
# @param package_id [Integer] Package ID
|
|
678
|
-
# @return [String, Nil] Package name or nil
|
|
679
|
-
def find_package_name(package_id)
|
|
680
|
-
return nil if package_id.nil?
|
|
681
|
-
|
|
682
|
-
database.find_package(package_id)&.name
|
|
132
|
+
ObjectPropertyTransformer.new(database).transform_collection(ea_props)
|
|
683
133
|
end
|
|
684
134
|
end
|
|
685
135
|
end
|