lutaml 0.10.2 → 0.10.3
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/.rubocop_todo.yml +177 -27
- data/lib/lutaml/converter/xmi_to_uml.rb +12 -3
- data/lib/lutaml/qea/database.rb +157 -4
- data/lib/lutaml/qea/factory/association_transformer.rb +1 -5
- data/lib/lutaml/qea/factory/attribute_transformer.rb +4 -10
- data/lib/lutaml/qea/factory/base_transformer.rb +1 -5
- data/lib/lutaml/qea/factory/class_transformer.rb +26 -62
- data/lib/lutaml/qea/factory/data_type_transformer.rb +7 -21
- data/lib/lutaml/qea/factory/diagram_transformer.rb +6 -20
- data/lib/lutaml/qea/factory/enum_transformer.rb +3 -5
- data/lib/lutaml/qea/factory/generalization_transformer.rb +2 -7
- data/lib/lutaml/qea/factory/instance_transformer.rb +2 -10
- data/lib/lutaml/qea/factory/operation_transformer.rb +2 -8
- data/lib/lutaml/qea/factory/package_transformer.rb +4 -13
- data/lib/lutaml/uml_repository/index_builder.rb +19 -17
- data/lib/lutaml/uml_repository/queries/class_query.rb +44 -10
- data/lib/lutaml/uml_repository/repository.rb +12 -8
- data/lib/lutaml/uml_repository/statistics_calculator.rb +28 -16
- data/lib/lutaml/version.rb +1 -1
- data/lib/lutaml/xmi/parsers/xmi_base.rb +28 -5
- metadata +2 -2
|
@@ -108,15 +108,9 @@ module Lutaml
|
|
|
108
108
|
def load_attributes(object_id)
|
|
109
109
|
return [] if object_id.nil?
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
rows = database.connection.execute(query, object_id)
|
|
111
|
+
ea_attributes = database.attributes_for_object(object_id)
|
|
112
|
+
.sort_by { |a| a.pos || 0 }
|
|
114
113
|
|
|
115
|
-
ea_attributes = rows.map do |row|
|
|
116
|
-
Models::EaAttribute.from_db_row(row)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Transform to UML attributes
|
|
120
114
|
attribute_transformer = AttributeTransformer.new(database)
|
|
121
115
|
attribute_transformer.transform_collection(ea_attributes)
|
|
122
116
|
end
|
|
@@ -127,15 +121,9 @@ module Lutaml
|
|
|
127
121
|
def load_operations(object_id)
|
|
128
122
|
return [] if object_id.nil?
|
|
129
123
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
rows = database.connection.execute(query, object_id)
|
|
133
|
-
|
|
134
|
-
ea_operations = rows.map do |row|
|
|
135
|
-
Models::EaOperation.from_db_row(row)
|
|
136
|
-
end
|
|
124
|
+
ea_operations = database.operations_for_object(object_id)
|
|
125
|
+
.sort_by { |op| op.pos || 0 }
|
|
137
126
|
|
|
138
|
-
# Transform to UML operations
|
|
139
127
|
operation_transformer = OperationTransformer.new(database)
|
|
140
128
|
operation_transformer.transform_collection(ea_operations)
|
|
141
129
|
end
|
|
@@ -245,24 +233,21 @@ module Lutaml
|
|
|
245
233
|
return nil unless current_obj
|
|
246
234
|
|
|
247
235
|
# 2. Find generalization connector where this class is the subtype
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
rows = database.connection.execute(query, object_id)
|
|
236
|
+
ea_connector = database.connectors_for_object(object_id)
|
|
237
|
+
.find { |c| c.generalization? && c.start_object_id == object_id }
|
|
251
238
|
|
|
252
239
|
# 3. Create generalization object for current class
|
|
253
240
|
# Even if no parent exists, we need a Generalization
|
|
254
241
|
# representing this class
|
|
255
|
-
|
|
242
|
+
gen_transformer = GeneralizationTransformer.new(database)
|
|
243
|
+
generalization = if ea_connector.nil?
|
|
256
244
|
# No parent - create terminal generalization
|
|
257
|
-
gen_transformer
|
|
258
|
-
generalization = gen_transformer.transform(nil, current_obj)
|
|
245
|
+
gen_transformer.transform(nil, current_obj)
|
|
259
246
|
else
|
|
260
247
|
# Has parent - create generalization with parent connector
|
|
261
|
-
|
|
262
|
-
gen_transformer = GeneralizationTransformer.new(database)
|
|
263
|
-
generalization = gen_transformer
|
|
248
|
+
gen_transformer
|
|
264
249
|
.transform(ea_connector, current_obj)
|
|
265
|
-
|
|
250
|
+
end
|
|
266
251
|
return nil unless generalization
|
|
267
252
|
|
|
268
253
|
# 4. Load CURRENT object attributes and convert to GeneralAttribute
|
|
@@ -452,19 +437,13 @@ module Lutaml
|
|
|
452
437
|
def load_association_generalizations(object_id) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
453
438
|
return [] if object_id.nil?
|
|
454
439
|
|
|
455
|
-
#
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
"AND Connector_Type = 'Generalization'"
|
|
459
|
-
rows = database.connection.execute(query, object_id)
|
|
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 }
|
|
460
443
|
|
|
461
|
-
|
|
462
|
-
guid =
|
|
463
|
-
parent_object_id =
|
|
464
|
-
row["End_Object_ID"] || row[:End_Object_ID]
|
|
465
|
-
else
|
|
466
|
-
row[1]
|
|
467
|
-
end
|
|
444
|
+
gen_connectors.filter_map do |ea_connector|
|
|
445
|
+
guid = ea_connector.ea_guid
|
|
446
|
+
parent_object_id = ea_connector.end_object_id
|
|
468
447
|
|
|
469
448
|
# Find parent object to get its GUID
|
|
470
449
|
parent_obj = find_object_by_id(parent_object_id)
|
|
@@ -493,16 +472,11 @@ module Lutaml
|
|
|
493
472
|
normalized_owner_xmi_id = normalize_guid_to_xmi_format(object_guid,
|
|
494
473
|
"EAID")
|
|
495
474
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
"AND Connector_Type IN " \
|
|
500
|
-
"('Association', 'Aggregation', 'Composition')"
|
|
501
|
-
rows = database.connection.execute(query, [object_id, object_id])
|
|
502
|
-
|
|
503
|
-
rows.each do |row| # rubocop:disable Metrics/BlockLength
|
|
504
|
-
ea_connector = Models::EaConnector.from_db_row(row)
|
|
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) }
|
|
505
478
|
|
|
479
|
+
assoc_connectors.each do |ea_connector| # rubocop:disable Metrics/BlockLength
|
|
506
480
|
# Determine which end this class is on
|
|
507
481
|
is_start = ea_connector.start_object_id == object_id
|
|
508
482
|
|
|
@@ -600,19 +574,13 @@ module Lutaml
|
|
|
600
574
|
|
|
601
575
|
attributes = []
|
|
602
576
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
"WHERE (Start_Object_ID = ? OR End_Object_ID = ?) " \
|
|
607
|
-
"AND Connector_Type IN " \
|
|
608
|
-
"('Association', 'Aggregation', 'Composition')"
|
|
609
|
-
rows = database.connection.execute(query, [object_id, object_id])
|
|
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) }
|
|
610
580
|
obj = find_object_by_id(object_id)
|
|
611
581
|
obj_pkg_name = find_package_name(obj&.package_id)
|
|
612
582
|
|
|
613
|
-
|
|
614
|
-
ea_connector = Models::EaConnector.from_db_row(row)
|
|
615
|
-
|
|
583
|
+
assoc_connectors.each do |ea_connector| # rubocop:disable Metrics/BlockLength
|
|
616
584
|
# Check if this object is the source (owner) or target (member)
|
|
617
585
|
if ea_connector.start_object_id == object_id
|
|
618
586
|
# This class is the source - check for dest role
|
|
@@ -711,11 +679,7 @@ module Lutaml
|
|
|
711
679
|
def find_package_name(package_id)
|
|
712
680
|
return nil if package_id.nil?
|
|
713
681
|
|
|
714
|
-
|
|
715
|
-
rows = database.connection.execute(query, [package_id])
|
|
716
|
-
return nil if rows.empty?
|
|
717
|
-
|
|
718
|
-
rows.first["Name"]
|
|
682
|
+
database.find_package(package_id)&.name
|
|
719
683
|
end
|
|
720
684
|
end
|
|
721
685
|
end
|
|
@@ -64,12 +64,8 @@ module Lutaml
|
|
|
64
64
|
def load_attributes(object_id)
|
|
65
65
|
return [] if object_id.nil?
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
ea_attributes = rows.map do |row|
|
|
71
|
-
Models::EaAttribute.from_db_row(row)
|
|
72
|
-
end
|
|
67
|
+
ea_attributes = database.attributes_for_object(object_id)
|
|
68
|
+
.sort_by { |a| a.pos || 0 }
|
|
73
69
|
|
|
74
70
|
attribute_transformer = AttributeTransformer.new(database)
|
|
75
71
|
attribute_transformer.transform_collection(ea_attributes)
|
|
@@ -81,12 +77,8 @@ module Lutaml
|
|
|
81
77
|
def load_operations(object_id)
|
|
82
78
|
return [] if object_id.nil?
|
|
83
79
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
ea_operations = rows.map do |row|
|
|
88
|
-
Models::EaOperation.from_db_row(row)
|
|
89
|
-
end
|
|
80
|
+
ea_operations = database.operations_for_object(object_id)
|
|
81
|
+
.sort_by { |op| op.pos || 0 }
|
|
90
82
|
|
|
91
83
|
operation_transformer = OperationTransformer.new(database)
|
|
92
84
|
operation_transformer.transform_collection(ea_operations)
|
|
@@ -129,19 +121,13 @@ module Lutaml
|
|
|
129
121
|
def load_associations(object_id, object_guid) # rubocop:disable Metrics/MethodLength
|
|
130
122
|
return [] if object_id.nil?
|
|
131
123
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
WHERE Connector_Type = 'Association'
|
|
135
|
-
AND (Start_Object_ID = ? OR End_Object_ID = ?)
|
|
136
|
-
SQL
|
|
137
|
-
|
|
138
|
-
rows = database.connection.execute(query, [object_id, object_id])
|
|
124
|
+
assoc_connectors = database.connectors_for_object(object_id)
|
|
125
|
+
.select { |c| c.connector_type == "Association" }
|
|
139
126
|
|
|
140
127
|
assoc_transformer = AssociationTransformer.new(database)
|
|
141
128
|
normalized_xmi_id = normalize_guid_to_xmi_format(object_guid, "EAID")
|
|
142
129
|
|
|
143
|
-
|
|
144
|
-
ea_connector = Models::EaConnector.from_db_row(row)
|
|
130
|
+
assoc_connectors.filter_map do |ea_connector|
|
|
145
131
|
assoc = assoc_transformer.transform(ea_connector)
|
|
146
132
|
|
|
147
133
|
next unless assoc && assoc.owner_end_xmi_id == normalized_xmi_id
|
|
@@ -72,11 +72,7 @@ module Lutaml
|
|
|
72
72
|
def find_package(package_id)
|
|
73
73
|
return nil if package_id.nil?
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
rows = database.connection.execute(query, package_id)
|
|
77
|
-
return nil if rows.empty?
|
|
78
|
-
|
|
79
|
-
Models::EaPackage.from_db_row(rows.first)
|
|
75
|
+
database.find_package(package_id)
|
|
80
76
|
end
|
|
81
77
|
|
|
82
78
|
# Load diagram objects for a diagram
|
|
@@ -85,11 +81,8 @@ module Lutaml
|
|
|
85
81
|
def load_diagram_objects(diagram_id)
|
|
86
82
|
return [] if diagram_id.nil?
|
|
87
83
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
rows.filter_map do |row|
|
|
92
|
-
ea_obj = Models::EaDiagramObject.from_db_row(row)
|
|
84
|
+
ea_objects = database.diagram_objects_for(diagram_id)
|
|
85
|
+
ea_objects.filter_map do |ea_obj|
|
|
93
86
|
transform_diagram_object(ea_obj)
|
|
94
87
|
end
|
|
95
88
|
end
|
|
@@ -127,11 +120,8 @@ module Lutaml
|
|
|
127
120
|
def load_diagram_links(diagram_id)
|
|
128
121
|
return [] if diagram_id.nil?
|
|
129
122
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
rows.filter_map do |row|
|
|
134
|
-
ea_link = Models::EaDiagramLink.from_db_row(row)
|
|
123
|
+
ea_links = database.diagram_links_for(diagram_id)
|
|
124
|
+
ea_links.filter_map do |ea_link|
|
|
135
125
|
transform_diagram_link(ea_link)
|
|
136
126
|
end
|
|
137
127
|
end
|
|
@@ -167,11 +157,7 @@ module Lutaml
|
|
|
167
157
|
def find_connector_by_id(connector_id)
|
|
168
158
|
return nil if connector_id.nil?
|
|
169
159
|
|
|
170
|
-
|
|
171
|
-
rows = database.connection.execute(query, connector_id)
|
|
172
|
-
return nil if rows.empty?
|
|
173
|
-
|
|
174
|
-
Models::EaConnector.from_db_row(rows.first)
|
|
160
|
+
database.find_connector(connector_id)
|
|
175
161
|
end
|
|
176
162
|
end
|
|
177
163
|
end
|
|
@@ -50,12 +50,10 @@ module Lutaml
|
|
|
50
50
|
def load_enum_values(object_id) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
51
51
|
return [] if object_id.nil?
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
rows.filter_map do |row|
|
|
57
|
-
ea_attr = Models::EaAttribute.from_db_row(row)
|
|
53
|
+
ea_attrs = database.attributes_for_object(object_id)
|
|
54
|
+
.sort_by { |a| a.pos || 0 }
|
|
58
55
|
|
|
56
|
+
ea_attrs.filter_map do |ea_attr|
|
|
59
57
|
Lutaml::Uml::Value.new.tap do |value|
|
|
60
58
|
value.name = ea_attr.name
|
|
61
59
|
value.id = normalize_guid_to_xmi_format(ea_attr.ea_guid, "EAID")
|
|
@@ -72,11 +72,7 @@ module Lutaml
|
|
|
72
72
|
def find_object(object_id)
|
|
73
73
|
return nil if object_id.nil?
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
rows = database.connection.execute(query, object_id)
|
|
77
|
-
return nil if rows.empty?
|
|
78
|
-
|
|
79
|
-
Models::EaObject.from_db_row(rows.first)
|
|
75
|
+
database.find_object(object_id)
|
|
80
76
|
end
|
|
81
77
|
|
|
82
78
|
# Find package by ID
|
|
@@ -84,9 +80,8 @@ module Lutaml
|
|
|
84
80
|
# @return [EaPackage, nil] EA package or nil if not found
|
|
85
81
|
def find_package(package_id)
|
|
86
82
|
return nil if package_id.nil?
|
|
87
|
-
return nil unless database.packages
|
|
88
83
|
|
|
89
|
-
database.
|
|
84
|
+
database.find_package(package_id)
|
|
90
85
|
end
|
|
91
86
|
|
|
92
87
|
# Extract package prefix from package
|
|
@@ -54,11 +54,7 @@ module Lutaml
|
|
|
54
54
|
def find_classifier(classifier_id)
|
|
55
55
|
return nil if classifier_id.nil? || classifier_id.zero?
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
rows = database.connection.execute(query, classifier_id)
|
|
59
|
-
return nil if rows.empty?
|
|
60
|
-
|
|
61
|
-
Models::EaObject.from_db_row(rows.first)
|
|
57
|
+
database.find_object(classifier_id)
|
|
62
58
|
end
|
|
63
59
|
|
|
64
60
|
# Find classifier object by GUID
|
|
@@ -67,11 +63,7 @@ module Lutaml
|
|
|
67
63
|
def find_classifier_by_guid(classifier_guid)
|
|
68
64
|
return nil if classifier_guid.nil? || classifier_guid.empty?
|
|
69
65
|
|
|
70
|
-
|
|
71
|
-
rows = database.connection.execute(query, classifier_guid)
|
|
72
|
-
return nil if rows.empty?
|
|
73
|
-
|
|
74
|
-
Models::EaObject.from_db_row(rows.first)
|
|
66
|
+
database.find_object_by_guid(classifier_guid)
|
|
75
67
|
end
|
|
76
68
|
|
|
77
69
|
# Load and transform tagged values for an instance
|
|
@@ -60,14 +60,8 @@ module Lutaml
|
|
|
60
60
|
def load_parameters(operation_id)
|
|
61
61
|
return [] if operation_id.nil?
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"ORDER BY Pos"
|
|
66
|
-
rows = database.connection.execute(query, operation_id)
|
|
67
|
-
|
|
68
|
-
rows.map do |row|
|
|
69
|
-
Models::EaOperationParam.from_db_row(row)
|
|
70
|
-
end
|
|
63
|
+
database.operation_params_for(operation_id)
|
|
64
|
+
.sort_by { |p| p.pos || 0 }
|
|
71
65
|
end
|
|
72
66
|
end
|
|
73
67
|
end
|
|
@@ -70,11 +70,8 @@ module Lutaml
|
|
|
70
70
|
def load_child_packages(parent_id)
|
|
71
71
|
return [] if parent_id.nil?
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
rows = database.connection.execute(query, parent_id)
|
|
76
|
-
|
|
77
|
-
rows.map { |row| Models::EaPackage.from_db_row(row) }
|
|
73
|
+
database.child_packages_for(parent_id)
|
|
74
|
+
.sort_by { |p| p.tpos || 0 }
|
|
78
75
|
end
|
|
79
76
|
|
|
80
77
|
# Load package contents (objects and diagrams)
|
|
@@ -94,10 +91,7 @@ module Lutaml
|
|
|
94
91
|
# @param pkg [Lutaml::Uml::Package] UML package
|
|
95
92
|
# @param package_id [Integer] Package ID
|
|
96
93
|
def load_package_objects(pkg, package_id) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
97
|
-
|
|
98
|
-
rows = database.connection.execute(query, package_id)
|
|
99
|
-
|
|
100
|
-
ea_objects = rows.map { |row| Models::EaObject.from_db_row(row) }
|
|
94
|
+
ea_objects = database.objects_in_package(package_id)
|
|
101
95
|
|
|
102
96
|
class_transformer = ClassTransformer.new(database)
|
|
103
97
|
enum_transformer = EnumTransformer.new(database)
|
|
@@ -155,10 +149,7 @@ module Lutaml
|
|
|
155
149
|
def load_package_diagrams(pkg, package_id)
|
|
156
150
|
diagram_transformer = DiagramTransformer.new(database)
|
|
157
151
|
|
|
158
|
-
|
|
159
|
-
rows = database.connection.execute(query, package_id)
|
|
160
|
-
|
|
161
|
-
ea_diagrams = rows.map { |row| Models::EaDiagram.from_db_row(row) }
|
|
152
|
+
ea_diagrams = database.diagrams_in_package(package_id)
|
|
162
153
|
pkg.diagrams = diagram_transformer.transform_collection(ea_diagrams)
|
|
163
154
|
end
|
|
164
155
|
|
|
@@ -42,18 +42,8 @@ module Lutaml
|
|
|
42
42
|
# - :class_to_qname - Maps class XMI IDs to qualified names
|
|
43
43
|
# - :classes - Maps class XMI IDs to Class objects
|
|
44
44
|
# - :associations - Maps association XMI IDs to Association objects
|
|
45
|
-
def self.build_all(document)
|
|
46
|
-
|
|
47
|
-
package_paths: build_package_paths(document),
|
|
48
|
-
qualified_names: build_qualified_names(document),
|
|
49
|
-
stereotypes: build_stereotypes(document),
|
|
50
|
-
inheritance_graph: build_inheritance_graph(document, nil),
|
|
51
|
-
diagram_index: build_diagram_index(document, nil),
|
|
52
|
-
package_to_path: build_package_to_path(document),
|
|
53
|
-
class_to_qname: build_class_to_qname(document),
|
|
54
|
-
classes: build_classes(document),
|
|
55
|
-
associations: build_associations(document),
|
|
56
|
-
}.freeze
|
|
45
|
+
def self.build_all(document)
|
|
46
|
+
new(document).build_all
|
|
57
47
|
end
|
|
58
48
|
|
|
59
49
|
# Build package paths index
|
|
@@ -160,6 +150,8 @@ module Lutaml
|
|
|
160
150
|
@class_to_qname = {}
|
|
161
151
|
@classes = {}
|
|
162
152
|
@associations = {}
|
|
153
|
+
@simple_name_to_qnames = {}
|
|
154
|
+
@package_to_classes = {}
|
|
163
155
|
end
|
|
164
156
|
|
|
165
157
|
# Build all indexes and return them as a frozen hash
|
|
@@ -183,6 +175,7 @@ module Lutaml
|
|
|
183
175
|
class_to_qname: @class_to_qname.freeze,
|
|
184
176
|
classes: @classes.freeze,
|
|
185
177
|
associations: @associations.freeze,
|
|
178
|
+
package_to_classes: plain_hash(@package_to_classes).freeze,
|
|
186
179
|
}.freeze
|
|
187
180
|
end
|
|
188
181
|
|
|
@@ -358,6 +351,9 @@ module Lutaml
|
|
|
358
351
|
qualified_name
|
|
359
352
|
end
|
|
360
353
|
@classes[classifier.xmi_id] = classifier if classifier.xmi_id
|
|
354
|
+
@simple_name_to_qnames[classifier.name] ||= []
|
|
355
|
+
@simple_name_to_qnames[classifier.name] << qualified_name
|
|
356
|
+
(@package_to_classes[package_path] ||= []) << classifier
|
|
361
357
|
end
|
|
362
358
|
end
|
|
363
359
|
|
|
@@ -466,12 +462,18 @@ module Lutaml
|
|
|
466
462
|
local_qname = "#{current_package_path}::#{name}"
|
|
467
463
|
return local_qname if @qualified_names.key?(local_qname)
|
|
468
464
|
|
|
469
|
-
#
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
465
|
+
# O(1) lookup using reverse index instead of O(n) scan
|
|
466
|
+
candidates = @simple_name_to_qnames[name]
|
|
467
|
+
candidates&.first
|
|
468
|
+
end
|
|
473
469
|
|
|
474
|
-
|
|
470
|
+
private
|
|
471
|
+
|
|
472
|
+
# Convert a hash with default proc to a plain hash (Marshal-safe)
|
|
473
|
+
# @param hash [Hash] Hash possibly with default proc
|
|
474
|
+
# @return [Hash] Plain hash without default proc
|
|
475
|
+
def plain_hash(hash_with_default)
|
|
476
|
+
hash_with_default.each_with_object({}) { |(k, v), h| h[k] = v }
|
|
475
477
|
end
|
|
476
478
|
end
|
|
477
479
|
end
|
|
@@ -69,38 +69,72 @@ module Lutaml
|
|
|
69
69
|
def in_package(package_path_string, recursive: false) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
70
70
|
return [] if package_path_string.nil? || package_path_string.empty?
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
pkg_to_classes = indexes[:package_to_classes]
|
|
73
|
+
if pkg_to_classes
|
|
74
|
+
in_package_indexed(package_path_string, pkg_to_classes,
|
|
75
|
+
recursive: recursive)
|
|
76
|
+
else
|
|
77
|
+
in_package_scan(package_path_string, recursive: recursive)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
# O(1) indexed lookup for in_package
|
|
84
|
+
def in_package_indexed(package_path_string, pkg_to_classes, recursive:) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
85
|
+
is_absolute = package_path_string.start_with?("::")
|
|
86
|
+
search_segs = package_path_string.split("::").reject(&:empty?)
|
|
87
|
+
|
|
73
88
|
results = []
|
|
89
|
+
pkg_to_classes.each do |path, classes|
|
|
90
|
+
path_segs = path.split("::")
|
|
91
|
+
matched = if is_absolute
|
|
92
|
+
if recursive
|
|
93
|
+
path == package_path_string ||
|
|
94
|
+
path.start_with?("#{package_path_string}::")
|
|
95
|
+
else
|
|
96
|
+
path == package_path_string
|
|
97
|
+
end
|
|
98
|
+
elsif recursive
|
|
99
|
+
# Relative: match when path ends with search segments
|
|
100
|
+
(0..(path_segs.size - search_segs.size)).any? do |i|
|
|
101
|
+
path_segs[i, search_segs.size] == search_segs
|
|
102
|
+
end
|
|
103
|
+
else
|
|
104
|
+
path_segs.size >= search_segs.size &&
|
|
105
|
+
path_segs[-search_segs.size..] == search_segs
|
|
106
|
+
end
|
|
74
107
|
|
|
75
|
-
|
|
108
|
+
results.concat(classes) if matched
|
|
109
|
+
end
|
|
110
|
+
results
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Fallback: original O(n) scan
|
|
114
|
+
def in_package_scan(package_path_string, recursive:)
|
|
115
|
+
package_path = Lutaml::Uml::PackagePath.new(package_path_string)
|
|
116
|
+
results = []
|
|
76
117
|
is_absolute = package_path.absolute?
|
|
77
118
|
|
|
78
|
-
indexes[:qualified_names].each do |qname_string, klass|
|
|
119
|
+
indexes[:qualified_names].each do |qname_string, klass|
|
|
79
120
|
qname = Lutaml::Uml::QualifiedName.new(qname_string)
|
|
80
121
|
|
|
81
122
|
matched = if is_absolute
|
|
82
|
-
# Absolute path - exact match
|
|
83
123
|
if recursive
|
|
84
124
|
qname.package_path.starts_with?(package_path)
|
|
85
125
|
else
|
|
86
126
|
qname.package_path == package_path
|
|
87
127
|
end
|
|
88
128
|
else
|
|
89
|
-
# Relative path - match if the class's package path ends with
|
|
90
|
-
# the given path
|
|
91
129
|
class_pkg_segs = qname.package_path.segments
|
|
92
130
|
search_segs = package_path.segments
|
|
93
131
|
|
|
94
132
|
if recursive
|
|
95
|
-
# For recursive, check if any suffix of the class path
|
|
96
|
-
# starts with search_segs
|
|
97
133
|
(0..(class_pkg_segs.size - search_segs.size))
|
|
98
134
|
.any? do |i|
|
|
99
135
|
class_pkg_segs[i, search_segs.size] == search_segs
|
|
100
136
|
end
|
|
101
137
|
else
|
|
102
|
-
# For non-recursive, check if class path ends with
|
|
103
|
-
# search_segs
|
|
104
138
|
class_pkg_segs.size >= search_segs.size &&
|
|
105
139
|
class_pkg_segs[-search_segs.size..] == search_segs
|
|
106
140
|
end
|
|
@@ -646,25 +646,29 @@ module Lutaml
|
|
|
646
646
|
# Get all associations as an array
|
|
647
647
|
# Collects from both document-level (XMI) and class-level (QEA/EA)
|
|
648
648
|
# @return [Array<Lutaml::Uml::Association>] All associations
|
|
649
|
-
def associations_index # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics
|
|
649
|
+
def associations_index # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics:MethodLength,Metrics:PerceivedComplexity
|
|
650
650
|
# Use cached index if available (built by IndexBuilder)
|
|
651
651
|
return @indexes[:associations].values if @indexes[:associations]
|
|
652
652
|
|
|
653
653
|
# Fallback for edge cases: collect from document and classes
|
|
654
|
+
seen = Set.new
|
|
654
655
|
associations = []
|
|
655
656
|
|
|
656
|
-
|
|
657
|
-
|
|
657
|
+
(@document.associations || []).each do |assoc|
|
|
658
|
+
if assoc.xmi_id && !seen.include?(assoc.xmi_id)
|
|
659
|
+
seen << assoc.xmi_id
|
|
660
|
+
associations << assoc
|
|
661
|
+
end
|
|
662
|
+
end
|
|
658
663
|
|
|
659
|
-
# Class-level associations (QEA/EA format)
|
|
660
664
|
classes_index.each do |klass|
|
|
661
665
|
next unless klass.respond_to?(:associations) && klass.associations
|
|
662
666
|
|
|
663
667
|
klass.associations.each do |assoc|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
+
if assoc.xmi_id && !seen.include?(assoc.xmi_id)
|
|
669
|
+
seen << assoc.xmi_id
|
|
670
|
+
associations << assoc
|
|
671
|
+
end
|
|
668
672
|
end
|
|
669
673
|
end
|
|
670
674
|
|
|
@@ -262,12 +262,14 @@ module Lutaml
|
|
|
262
262
|
def max_inheritance_depth
|
|
263
263
|
return 0 if @indexes[:inheritance_graph].empty?
|
|
264
264
|
|
|
265
|
-
|
|
265
|
+
@inheritance_depth_cache ||= {}
|
|
266
|
+
max_depth = 0
|
|
267
|
+
|
|
266
268
|
@indexes[:qualified_names].each_key do |qname|
|
|
267
|
-
depth =
|
|
268
|
-
|
|
269
|
+
depth = memoized_inheritance_depth(qname)
|
|
270
|
+
max_depth = depth if depth > max_depth
|
|
269
271
|
end
|
|
270
|
-
|
|
272
|
+
max_depth
|
|
271
273
|
end
|
|
272
274
|
|
|
273
275
|
# Calculate inheritance depth for a class
|
|
@@ -275,23 +277,33 @@ module Lutaml
|
|
|
275
277
|
# @param qname [String] Qualified name of the class
|
|
276
278
|
# @param visited [Set] Set of visited classes (to detect cycles)
|
|
277
279
|
# @return [Integer] Depth of inheritance chain
|
|
278
|
-
def calculate_inheritance_depth(qname, visited = Set.new)
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
visited.add(qname)
|
|
280
|
+
def calculate_inheritance_depth(qname, visited = Set.new)
|
|
281
|
+
memoized_inheritance_depth(qname, visited)
|
|
282
|
+
end
|
|
282
283
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
@
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
284
|
+
# Build reverse index: child_qname => parent_qname
|
|
285
|
+
def child_to_parent_index
|
|
286
|
+
@child_to_parent_index ||= begin
|
|
287
|
+
idx = {}
|
|
288
|
+
@indexes[:inheritance_graph].each do |parent, children|
|
|
289
|
+
children.each { |child| idx[child] = parent }
|
|
289
290
|
end
|
|
291
|
+
idx
|
|
290
292
|
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Memoized inheritance depth calculation using reverse index
|
|
296
|
+
def memoized_inheritance_depth(qname, visited = Set.new)
|
|
297
|
+
return 0 if visited.include?(qname)
|
|
298
|
+
return @inheritance_depth_cache[qname] if @inheritance_depth_cache.key?(qname)
|
|
291
299
|
|
|
292
|
-
|
|
300
|
+
parent = child_to_parent_index[qname]
|
|
301
|
+
return 0 unless parent
|
|
293
302
|
|
|
294
|
-
|
|
303
|
+
visited.add(qname)
|
|
304
|
+
depth = 1 + memoized_inheritance_depth(parent, visited)
|
|
305
|
+
@inheritance_depth_cache[qname] = depth
|
|
306
|
+
depth
|
|
295
307
|
end
|
|
296
308
|
|
|
297
309
|
# Get count of abstract classes
|
data/lib/lutaml/version.rb
CHANGED