lutaml 0.10.2 → 0.10.4

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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/.rubocop_todo.yml +40 -35
  4. data/CHANGELOG.md +108 -0
  5. data/lib/lutaml/cli/uml_commands.rb +2 -2
  6. data/lib/lutaml/command_line.rb +1 -1
  7. data/lib/lutaml/converter/xmi_to_uml.rb +12 -3
  8. data/lib/lutaml/model_transformations/parsers/base_parser.rb +1 -1
  9. data/lib/lutaml/model_transformations/transformation_engine.rb +1 -1
  10. data/lib/lutaml/qea/database.rb +157 -4
  11. data/lib/lutaml/qea/factory/association_transformer.rb +1 -5
  12. data/lib/lutaml/qea/factory/attribute_transformer.rb +4 -10
  13. data/lib/lutaml/qea/factory/base_transformer.rb +1 -5
  14. data/lib/lutaml/qea/factory/class_transformer.rb +26 -62
  15. data/lib/lutaml/qea/factory/data_type_transformer.rb +7 -21
  16. data/lib/lutaml/qea/factory/diagram_transformer.rb +6 -20
  17. data/lib/lutaml/qea/factory/document_builder.rb +1 -1
  18. data/lib/lutaml/qea/factory/enum_transformer.rb +3 -5
  19. data/lib/lutaml/qea/factory/generalization_transformer.rb +2 -7
  20. data/lib/lutaml/qea/factory/instance_transformer.rb +2 -10
  21. data/lib/lutaml/qea/factory/operation_transformer.rb +2 -8
  22. data/lib/lutaml/qea/factory/package_transformer.rb +4 -13
  23. data/lib/lutaml/qea/repositories/base_repository.rb +6 -6
  24. data/lib/lutaml/qea/validation/base_validator.rb +2 -3
  25. data/lib/lutaml/qea/validation/validation_message.rb +2 -2
  26. data/lib/lutaml/qea/validation/validation_result.rb +2 -2
  27. data/lib/lutaml/sysml.rb +1 -1
  28. data/lib/lutaml/uml/has_members.rb +1 -1
  29. data/lib/lutaml/uml/parsers/dsl.rb +1 -1
  30. data/lib/lutaml/uml.rb +1 -1
  31. data/lib/lutaml/uml_repository/index_builder.rb +19 -17
  32. data/lib/lutaml/uml_repository/queries/class_query.rb +44 -10
  33. data/lib/lutaml/uml_repository/queries/inheritance_query.rb +0 -1
  34. data/lib/lutaml/uml_repository/repository.rb +14 -10
  35. data/lib/lutaml/uml_repository/statistics_calculator.rb +28 -16
  36. data/lib/lutaml/version.rb +1 -1
  37. data/lib/lutaml/xmi/parsers/xmi_base.rb +28 -5
  38. data/lib/lutaml.rb +3 -1
  39. data/lutaml.gemspec +2 -2
  40. metadata +5 -4
@@ -108,15 +108,9 @@ module Lutaml
108
108
  def load_attributes(object_id)
109
109
  return [] if object_id.nil?
110
110
 
111
- # Query t_attribute table
112
- query = "SELECT * FROM t_attribute WHERE Object_ID = ? ORDER BY Pos"
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
- # Query t_operation table
131
- query = "SELECT * FROM t_operation WHERE Object_ID = ? ORDER BY Pos"
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
- query = "SELECT * FROM t_connector WHERE Start_Object_ID = ? " \
249
- "AND Connector_Type = 'Generalization' LIMIT 1"
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
- if rows.empty?
242
+ gen_transformer = GeneralizationTransformer.new(database)
243
+ generalization = if ea_connector.nil?
256
244
  # No parent - create terminal generalization
257
- gen_transformer = GeneralizationTransformer.new(database)
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
- ea_connector = Models::EaConnector.from_db_row(rows.first)
262
- gen_transformer = GeneralizationTransformer.new(database)
263
- generalization = gen_transformer
248
+ gen_transformer
264
249
  .transform(ea_connector, current_obj)
265
- end
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
- # Query for ALL generalization connectors for this class
456
- query = "SELECT ea_guid, End_Object_ID FROM t_connector " \
457
- "WHERE Start_Object_ID = ? " \
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
- rows.filter_map do |row|
462
- guid = row.is_a?(Hash) ? (row["ea_guid"] || row[:ea_guid]) : row[0]
463
- parent_object_id = if row.is_a?(Hash)
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
- # Find all association-type connectors where this class participates
497
- query = "SELECT * FROM t_connector " \
498
- "WHERE (Start_Object_ID = ? OR End_Object_ID = ?) " \
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
- # Find all association-type connectors
604
- # (Association, Aggregation, Composition)
605
- query = "SELECT * FROM t_connector " \
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
- rows.each do |row| # rubocop:disable Metrics/BlockLength
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
- query = "SELECT NAME FROM t_package WHERE Package_ID = ?"
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
- query = "SELECT * FROM t_attribute WHERE Object_ID = ? ORDER BY Pos"
68
- rows = database.connection.execute(query, object_id)
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
- query = "SELECT * FROM t_operation WHERE Object_ID = ? ORDER BY Pos"
85
- rows = database.connection.execute(query, object_id)
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
- query = <<-SQL
133
- SELECT * FROM t_connector
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
- rows.filter_map do |row|
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
- query = "SELECT * FROM t_package WHERE Package_ID = ?"
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
- query = "SELECT * FROM t_diagramobjects WHERE Diagram_ID = ?"
89
- rows = database.connection.execute(query, diagram_id)
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
- query = "SELECT * FROM t_diagramlinks WHERE DiagramID = ?"
131
- rows = database.connection.execute(query, diagram_id)
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
- query = "SELECT * FROM t_connector WHERE Connector_ID = ?"
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
@@ -268,7 +268,7 @@ module Lutaml
268
268
  end
269
269
 
270
270
  # Custom validation error
271
- class ValidationError < StandardError; end
271
+ class ValidationError < Lutaml::Error; end
272
272
  end
273
273
  end
274
274
  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
- query = "SELECT * FROM t_attribute WHERE Object_ID = ? ORDER BY Pos"
54
- rows = database.connection.execute(query, object_id)
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
- query = "SELECT * FROM t_object WHERE Object_ID = ?"
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.packages.find { |pkg| pkg.package_id == package_id }
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
- query = "SELECT * FROM t_object WHERE Object_ID = ?"
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
- query = "SELECT * FROM t_object WHERE ea_guid = ?"
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
- # Query t_operationparams table
64
- query = "SELECT * FROM t_operationparams WHERE OperationID = ? " \
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
- query = "SELECT * FROM t_package WHERE Parent_ID = ? " \
74
- "ORDER BY TPos"
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
- query = "SELECT * FROM t_object WHERE Package_ID = ?"
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
- query = "SELECT * FROM t_diagram WHERE Package_ID = ?"
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
 
@@ -118,8 +118,8 @@ module Lutaml
118
118
  # @example
119
119
  # repository.find_first(name: "Test")
120
120
  # repository.find_first { |r| r.name.start_with?("Test") }
121
- def find_first(conditions = nil, &block)
122
- where(conditions, &block).first
121
+ def find_first(conditions = nil, &)
122
+ where(conditions, &).first
123
123
  end
124
124
 
125
125
  # Check if any records match conditions
@@ -127,8 +127,8 @@ module Lutaml
127
127
  # @param conditions [Hash, nil] Optional conditions
128
128
  # @yield [record] Optional block for custom filtering
129
129
  # @return [Boolean] true if any records match
130
- def any?(conditions = nil, &block)
131
- !where(conditions, &block).empty?
130
+ def any?(conditions = nil, &)
131
+ !where(conditions, &).empty?
132
132
  end
133
133
 
134
134
  # Check if no records match conditions
@@ -136,8 +136,8 @@ module Lutaml
136
136
  # @param conditions [Hash, nil] Optional conditions
137
137
  # @yield [record] Optional block for custom filtering
138
138
  # @return [Boolean] true if no records match
139
- def none?(conditions = nil, &block)
140
- where(conditions, &block).empty?
139
+ def none?(conditions = nil, &)
140
+ where(conditions, &).empty?
141
141
  end
142
142
 
143
143
  # Select specific attributes from records
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
3
  require_relative "validation_result"
5
4
 
6
5
  module Lutaml
@@ -233,8 +232,8 @@ module Lutaml
233
232
  # @param entities [Array] Collection of entities to validate
234
233
  # @yield [entity] Validation block for each entity
235
234
  # @return [ValidationResult]
236
- def validate_each(entities, &block)
237
- entities.each(&block)
235
+ def validate_each(entities, &)
236
+ entities.each(&)
238
237
  @result
239
238
  end
240
239
 
@@ -134,9 +134,9 @@ module Lutaml
134
134
  # Returns a JSON representation of the message
135
135
  #
136
136
  # @return [String]
137
- def to_json(*args)
137
+ def to_json(*)
138
138
  require "json"
139
- to_h.to_json(*args)
139
+ to_h.to_json(*)
140
140
  end
141
141
  end
142
142
  end
@@ -202,9 +202,9 @@ module Lutaml
202
202
  # Returns a JSON representation
203
203
  #
204
204
  # @return [String]
205
- def to_json(*args)
205
+ def to_json(*)
206
206
  require "json"
207
- to_h.to_json(*args)
207
+ to_h.to_json(*)
208
208
  end
209
209
  end
210
210
  end
data/lib/lutaml/sysml.rb CHANGED
@@ -5,7 +5,7 @@ require "nokogiri"
5
5
 
6
6
  module Lutaml
7
7
  module Sysml
8
- class Error < StandardError; end
8
+ class Error < Lutaml::Error; end
9
9
  # Your code goes here...
10
10
  end
11
11
  end
@@ -3,7 +3,7 @@
3
3
  module Lutaml
4
4
  module Uml
5
5
  module HasMembers
6
- class UnknownMemberTypeError < StandardError; end
6
+ class UnknownMemberTypeError < Lutaml::Error; end
7
7
 
8
8
  # TODO: move to Parslet::Transform
9
9
  def members=(value) # rubocop:disable Metrics/AbcSize
@@ -10,7 +10,7 @@ require "lutaml/converter/dsl_to_uml"
10
10
  module Lutaml
11
11
  module Uml
12
12
  module Parsers
13
- class ParsingError < StandardError; end
13
+ class ParsingError < Lutaml::Error; end
14
14
 
15
15
  # Class for parsing LutaML dsl into Lutaml::Uml::Document
16
16
  class Dsl < Parslet::Parser
data/lib/lutaml/uml.rb CHANGED
@@ -4,7 +4,7 @@ require "lutaml/model"
4
4
 
5
5
  module Lutaml
6
6
  module Uml
7
- class Error < StandardError; end
7
+ class Error < Lutaml::Error; end
8
8
 
9
9
  # Base modules and mixins
10
10
  autoload :HasAttributes, "lutaml/uml/has_attributes"
@@ -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) # rubocop:disable Metrics/MethodLength
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
- # Try to find in all qualified names (simple name match)
470
- @qualified_names.each_key do |qname|
471
- return qname if qname.end_with?("::#{name}")
472
- end
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
- nil
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