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.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -0
  3. data/.rubocop.yml +10 -0
  4. data/.rubocop_todo.yml +218 -94
  5. data/TODO.cleanups/01-resolve-production-todos.md +65 -0
  6. data/TODO.cleanups/02-reduce-metrics-offenses.md +37 -0
  7. data/TODO.cleanups/03-reduce-rspec-multiple-expectations.md +54 -0
  8. data/TODO.cleanups/04-reduce-rspec-example-length.md +45 -0
  9. data/TODO.cleanups/05-replace-marshal-load.md +37 -0
  10. data/TODO.cleanups/06-replace-eval-in-tests.md +41 -0
  11. data/TODO.cleanups/07-fix-lint-offenses.md +74 -0
  12. data/TODO.cleanups/08-reduce-memoized-helpers-and-nesting.md +43 -0
  13. data/TODO.cleanups/09-reduce-verified-doubles-and-rspec-style.md +57 -0
  14. data/TODO.cleanups/10-split-large-files.md +47 -0
  15. data/bin/console +0 -1
  16. data/exe/lutaml +1 -0
  17. data/lib/lutaml/cli/element_identifier.rb +3 -6
  18. data/lib/lutaml/cli/interactive_shell/bookmark_commands.rb +88 -0
  19. data/lib/lutaml/cli/interactive_shell/command_base.rb +32 -0
  20. data/lib/lutaml/cli/interactive_shell/export_handler.rb +67 -0
  21. data/lib/lutaml/cli/interactive_shell/help_display.rb +114 -0
  22. data/lib/lutaml/cli/interactive_shell/navigation_commands.rb +135 -0
  23. data/lib/lutaml/cli/interactive_shell/query_commands.rb +185 -0
  24. data/lib/lutaml/cli/interactive_shell.rb +116 -802
  25. data/lib/lutaml/cli/uml/build_command.rb +5 -5
  26. data/lib/lutaml/cli/uml/verify_command.rb +0 -1
  27. data/lib/lutaml/converter/xmi_to_uml.rb +3 -153
  28. data/lib/lutaml/converter/xmi_to_uml_generalization.rb +193 -0
  29. data/lib/lutaml/formatter/graphviz.rb +1 -2
  30. data/lib/lutaml/qea/database.rb +1 -47
  31. data/lib/lutaml/qea/factory/association_builder.rb +188 -0
  32. data/lib/lutaml/qea/factory/base_transformer.rb +0 -1
  33. data/lib/lutaml/qea/factory/class_transformer.rb +40 -590
  34. data/lib/lutaml/qea/factory/diagram_transformer.rb +0 -3
  35. data/lib/lutaml/qea/factory/generalization_builder.rb +211 -0
  36. data/lib/lutaml/qea/factory/package_transformer.rb +1 -2
  37. data/lib/lutaml/qea/factory/stereotype_loader.rb +34 -0
  38. data/lib/lutaml/qea/lookup_indexes.rb +54 -0
  39. data/lib/lutaml/qea/models/ea_datatype.rb +0 -2
  40. data/lib/lutaml/qea/validation/validation_engine.rb +0 -2
  41. data/lib/lutaml/uml/has_members.rb +0 -1
  42. data/lib/lutaml/uml/inheritance_walker.rb +92 -0
  43. data/lib/lutaml/uml/model_helpers.rb +129 -0
  44. data/lib/lutaml/uml/node/attribute.rb +3 -1
  45. data/lib/lutaml/uml/node/class_node.rb +3 -3
  46. data/lib/lutaml/uml/operation.rb +2 -0
  47. data/lib/lutaml/uml_repository/class_lookup_index.rb +40 -0
  48. data/lib/lutaml/uml_repository/exporters/markdown/class_page_builder.rb +179 -0
  49. data/lib/lutaml/uml_repository/exporters/markdown/formatting.rb +36 -0
  50. data/lib/lutaml/uml_repository/exporters/markdown/index_page_builder.rb +73 -0
  51. data/lib/lutaml/uml_repository/exporters/markdown/link_resolver.rb +40 -0
  52. data/lib/lutaml/uml_repository/exporters/markdown/package_page_builder.rb +107 -0
  53. data/lib/lutaml/uml_repository/exporters/markdown_exporter.rb +26 -538
  54. data/lib/lutaml/uml_repository/index_builder.rb +3 -271
  55. data/lib/lutaml/uml_repository/index_builders/association_index.rb +141 -0
  56. data/lib/lutaml/uml_repository/index_builders/class_index.rb +94 -0
  57. data/lib/lutaml/uml_repository/index_builders/package_index.rb +57 -0
  58. data/lib/lutaml/uml_repository/package_exporter.rb +10 -20
  59. data/lib/lutaml/uml_repository/package_loader.rb +37 -17
  60. data/lib/lutaml/uml_repository/repository/deprecated.rb +39 -0
  61. data/lib/lutaml/uml_repository/repository/loader.rb +112 -0
  62. data/lib/lutaml/uml_repository/repository.rb +7 -57
  63. data/lib/lutaml/uml_repository/static_site/association_serialization.rb +142 -0
  64. data/lib/lutaml/uml_repository/static_site/configuration.rb +0 -2
  65. data/lib/lutaml/uml_repository/static_site/data_transformer.rb +52 -873
  66. data/lib/lutaml/uml_repository/static_site/generator.rb +29 -8
  67. data/lib/lutaml/uml_repository/static_site/search_index_builder.rb +1 -4
  68. data/lib/lutaml/uml_repository/static_site/serializers/attribute_serializer.rb +78 -0
  69. data/lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb +124 -0
  70. data/lib/lutaml/uml_repository/static_site/serializers/diagram_serializer.rb +60 -0
  71. data/lib/lutaml/uml_repository/static_site/serializers/inheritance_resolver.rb +258 -0
  72. data/lib/lutaml/uml_repository/static_site/serializers/metadata_builder.rb +48 -0
  73. data/lib/lutaml/uml_repository/static_site/serializers/operation_serializer.rb +57 -0
  74. data/lib/lutaml/uml_repository/static_site/serializers/package_serializer.rb +94 -0
  75. data/lib/lutaml/uml_repository/static_site/serializers/package_tree_builder.rb +93 -0
  76. data/lib/lutaml/version.rb +1 -1
  77. data/lib/lutaml/xmi/liquid_drops/association_drop.rb +13 -35
  78. data/lib/lutaml/xmi/liquid_drops/attribute_drop.rb +12 -18
  79. data/lib/lutaml/xmi/liquid_drops/cardinality_drop.rb +14 -6
  80. data/lib/lutaml/xmi/liquid_drops/connector_drop.rb +0 -3
  81. data/lib/lutaml/xmi/liquid_drops/constraint_drop.rb +1 -3
  82. data/lib/lutaml/xmi/liquid_drops/data_type_drop.rb +13 -70
  83. data/lib/lutaml/xmi/liquid_drops/dependency_drop.rb +2 -5
  84. data/lib/lutaml/xmi/liquid_drops/diagram_drop.rb +5 -11
  85. data/lib/lutaml/xmi/liquid_drops/enum_drop.rb +8 -16
  86. data/lib/lutaml/xmi/liquid_drops/enum_owned_literal_drop.rb +3 -9
  87. data/lib/lutaml/xmi/liquid_drops/generalization_attribute_drop.rb +11 -13
  88. data/lib/lutaml/xmi/liquid_drops/generalization_drop.rb +27 -85
  89. data/lib/lutaml/xmi/liquid_drops/klass_drop.rb +39 -91
  90. data/lib/lutaml/xmi/liquid_drops/operation_drop.rb +3 -9
  91. data/lib/lutaml/xmi/liquid_drops/package_drop.rb +16 -44
  92. data/lib/lutaml/xmi/liquid_drops/root_drop.rb +3 -11
  93. data/lib/lutaml/xmi/liquid_drops/source_target_drop.rb +2 -5
  94. data/lib/lutaml/xmi/parsers/xmi_base.rb +2 -749
  95. data/lib/lutaml/xmi/parsers/xmi_class_members.rb +45 -0
  96. data/lib/lutaml/xmi/parsers/xmi_connector.rb +251 -0
  97. data/lib/lutaml/xmi/parsers/xml.rb +7 -120
  98. data/lib/lutaml/xmi/xmi_lookup_service.rb +42 -0
  99. data/lib/lutaml.rb +0 -1
  100. metadata +48 -21
  101. data/lib/lutaml/cli/commands/base_command.rb +0 -118
  102. data/lib/lutaml/command_line.rb +0 -272
  103. data/lib/lutaml/sysml/allocate.rb +0 -9
  104. data/lib/lutaml/sysml/allocated.rb +0 -9
  105. data/lib/lutaml/sysml/binding_connector.rb +0 -9
  106. data/lib/lutaml/sysml/block.rb +0 -32
  107. data/lib/lutaml/sysml/constraint_block.rb +0 -14
  108. data/lib/lutaml/sysml/copy.rb +0 -8
  109. data/lib/lutaml/sysml/derive_requirement.rb +0 -9
  110. data/lib/lutaml/sysml/nested_connector_end.rb +0 -13
  111. data/lib/lutaml/sysml/refine.rb +0 -9
  112. data/lib/lutaml/sysml/requirement.rb +0 -44
  113. data/lib/lutaml/sysml/requirement_related.rb +0 -9
  114. data/lib/lutaml/sysml/satisfy.rb +0 -9
  115. data/lib/lutaml/sysml/test_case.rb +0 -25
  116. data/lib/lutaml/sysml/trace.rb +0 -9
  117. data/lib/lutaml/sysml/verify.rb +0 -8
  118. data/lib/lutaml/sysml/xmi_file.rb +0 -486
  119. data/lib/lutaml/sysml.rb +0 -11
@@ -2,6 +2,9 @@
2
2
 
3
3
  require_relative "../uml/package_path"
4
4
  require_relative "../uml/qualified_name"
5
+ require_relative "index_builders/package_index"
6
+ require_relative "index_builders/class_index"
7
+ require_relative "index_builders/association_index"
5
8
 
6
9
  module Lutaml
7
10
  module UmlRepository
@@ -179,113 +182,6 @@ module Lutaml
179
182
  }.freeze
180
183
  end
181
184
 
182
- # Build the package path index
183
- #
184
- # Creates a hash mapping package paths to Package objects:
185
- # "ModelRoot" => Package{},
186
- # "ModelRoot::i-UR" => Package{},
187
- # "ModelRoot::i-UR::urf" => Package{}
188
- # @api public
189
- def build_package_path_index
190
- # Add root package if it exists
191
- @package_paths[ROOT_PACKAGE_NAME] = @document if @document
192
-
193
- # Traverse all packages recursively
194
- traverse_packages(@document.packages,
195
- parent_path: ROOT_PACKAGE_NAME) do |package, path|
196
- @package_paths[path] = package
197
- @package_to_path[package.xmi_id] = path if package.xmi_id
198
- end
199
- end
200
-
201
- # Build the qualified name index
202
- #
203
- # Creates a hash mapping qualified names to Class/DataType/Enum objects:
204
- # "ModelRoot::i-UR::urf::Building" => Class{}
205
- # @api public
206
- def build_qualified_name_index # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
207
- # Index top-level classes, data_types, and enums from document
208
- if @document.classes
209
- index_classifiers(@document.classes,
210
- ROOT_PACKAGE_NAME)
211
- end
212
- if @document.data_types
213
- index_classifiers(@document.data_types,
214
- ROOT_PACKAGE_NAME)
215
- end
216
- index_classifiers(@document.enums, ROOT_PACKAGE_NAME) if @document.enums
217
-
218
- # Traverse packages and index their classifiers
219
- traverse_packages(@document.packages,
220
- parent_path: ROOT_PACKAGE_NAME) do |package, path|
221
- index_classifiers(package.classes, path) if package.classes
222
- index_classifiers(package.data_types, path) if package.data_types
223
- index_classifiers(package.enums, path) if package.enums
224
- end
225
- end
226
-
227
- def build_association_index # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
228
- # Collect document-level associations (XMI format)
229
- @document.associations&.each do |assoc|
230
- next unless assoc.xmi_id
231
-
232
- @associations[assoc.xmi_id] = assoc
233
- end
234
-
235
- # Collect class-level associations (QEA/EA format)
236
- # Note: This requires qualified_names index to be built first
237
- @qualified_names.each_value do |klass|
238
- next unless klass.respond_to?(:associations) && klass.associations
239
-
240
- klass.associations.each do |assoc|
241
- next unless assoc.xmi_id
242
-
243
- # Avoid duplicates - only add if not already present
244
- @associations[assoc.xmi_id] ||= assoc
245
- end
246
- end
247
- end
248
-
249
- # Build the stereotype index
250
- #
251
- # Creates a hash grouping classes by their stereotype:
252
- # "featureType" => [Class{}, Class{}],
253
- # "dataType" => [Class{}]
254
- # @api public
255
- def build_stereotype_index # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity
256
- # Process top-level classes
257
- index_by_stereotype(@document.classes) if @document.classes
258
- index_by_stereotype(@document.data_types) if @document.data_types
259
- index_by_stereotype(@document.enums) if @document.enums
260
-
261
- # Process classes in packages
262
- traverse_packages(@document.packages) do |package, _path|
263
- index_by_stereotype(package.classes) if package.classes
264
- index_by_stereotype(package.data_types) if package.data_types
265
- index_by_stereotype(package.enums) if package.enums
266
- end
267
- end
268
-
269
- # Build the inheritance graph index
270
- #
271
- # Creates a hash mapping parent qualified names to arrays of
272
- # child qualified names:
273
- # "ModelRoot::Parent" => ["ModelRoot::Child1", "ModelRoot::Child2"]
274
- # @api public
275
- def build_inheritance_graph_index
276
- # Process top-level classes
277
- if @document.classes
278
- process_generalizations(@document.classes,
279
- ROOT_PACKAGE_NAME)
280
- end
281
-
282
- # Process classes in packages
283
- traverse_packages(@document.packages,
284
- parent_path: ROOT_PACKAGE_NAME) do |package, path|
285
- process_generalizations(package.classes, path) if package.classes
286
- end
287
- end
288
-
289
185
  # Build the diagram index
290
186
  #
291
187
  # Creates a hash mapping package IDs/paths to arrays of Diagram objects:
@@ -303,170 +199,6 @@ module Lutaml
303
199
  end
304
200
  end
305
201
 
306
- # Traverse packages recursively, yielding each package with its path
307
- #
308
- # @param packages [Array<Lutaml::Uml::Package>] Packages to traverse
309
- # @param parent_path [String, nil] Parent package path
310
- # @yield [package, path] Yields each package with its full path
311
- def traverse_packages(packages, parent_path: nil, &block)
312
- return unless packages
313
-
314
- packages.each do |package|
315
- path = build_package_path(package.name, parent_path)
316
- yield package, path if block
317
-
318
- # Recursively traverse nested packages
319
- if package.packages
320
- traverse_packages(package.packages, parent_path: path,
321
- &block)
322
- end
323
- end
324
- end
325
-
326
- # Build a package path from a package name and parent path
327
- #
328
- # @param name [String] Package name
329
- # @param parent_path [String, nil] Parent package path
330
- # @return [String] Full package path
331
- def build_package_path(name, parent_path)
332
- return name unless parent_path
333
-
334
- "#{parent_path}::#{name}"
335
- end
336
-
337
- # Index classifiers (classes, data types, enums) by their qualified names
338
- #
339
- # @param classifiers [Array] Array of classifier objects
340
- # @param package_path [String] Package path for these classifiers
341
- def index_classifiers(classifiers, package_path) # rubocop:disable Metrics/MethodLength
342
- return unless classifiers
343
-
344
- classifiers.each do |classifier|
345
- next unless classifier.name
346
-
347
- qualified_name = "#{package_path}::#{classifier.name}"
348
- @qualified_names[qualified_name] = classifier
349
- if classifier.xmi_id
350
- @class_to_qname[classifier.xmi_id] =
351
- qualified_name
352
- end
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
357
- end
358
- end
359
-
360
- # Index classifiers by their stereotypes
361
- #
362
- # @param classifiers [Array] Array of classifier objects
363
- def index_by_stereotype(classifiers) # rubocop:disable Metrics/CyclomaticComplexity
364
- return unless classifiers
365
-
366
- classifiers.each do |classifier|
367
- next unless classifier.stereotype && !classifier.stereotype.empty?
368
-
369
- # Handle both String and Array stereotypes
370
- stereotypes = Array(classifier.stereotype)
371
- stereotypes.each do |stereotype|
372
- @stereotypes[stereotype] ||= []
373
- @stereotypes[stereotype] << classifier
374
- end
375
- end
376
- end
377
-
378
- # Process generalization relationships to build inheritance graph
379
- #
380
- # @param classes [Array<Lutaml::Uml::Class>] Classes to process
381
- # @param package_path [String] Package path for these classes
382
- def process_generalizations(classes, package_path) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
383
- return unless classes
384
-
385
- classes.each do |klass|
386
- next unless klass.name
387
-
388
- child_qname = "#{package_path}::#{klass.name}"
389
-
390
- # Handle generalization attribute
391
- if klass.generalization
392
- parent_name = extract_parent_name(klass.generalization)
393
- if parent_name
394
- parent_qname = resolve_qualified_name(parent_name, package_path)
395
- if parent_qname && child_qname != parent_qname
396
- @inheritance_graph[parent_qname] ||= []
397
- @inheritance_graph[parent_qname] << child_qname
398
- end
399
- end
400
- end
401
-
402
- # Handle inheritance associations
403
- next unless klass.associations
404
-
405
- klass.associations.each do |assoc|
406
- next unless assoc.respond_to?(:member_end_type)
407
- next unless assoc.member_end_type == "inheritance"
408
-
409
- parent_name = assoc.member_end
410
- next unless parent_name
411
-
412
- parent_name = parent_name.name if parent_name.respond_to?(:name)
413
- next unless parent_name.is_a?(String) && !parent_name.empty?
414
-
415
- parent_qname = resolve_qualified_name(parent_name, package_path)
416
- next unless parent_qname
417
- next if child_qname == parent_qname
418
-
419
- @inheritance_graph[parent_qname] ||= []
420
- @inheritance_graph[parent_qname] << child_qname
421
- end
422
- end
423
- end
424
-
425
- # Extract parent name from generalization object
426
- #
427
- # @param generalization [Lutaml::Uml::Generalization]
428
- # Generalization object
429
- # @return [String, nil] Parent class name
430
- def extract_parent_name(generalization) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
431
- return nil unless generalization
432
-
433
- # Check for general attribute (could be a string or object)
434
- if generalization.respond_to?(:general)
435
- parent = generalization.general
436
- return parent.name if parent.respond_to?(:name)
437
- return parent.to_s if parent
438
- end
439
-
440
- # Check for name attribute directly
441
- if generalization.respond_to?(:name) && generalization.name
442
- return generalization.name
443
- end
444
-
445
- nil
446
- end
447
-
448
- # Resolve a class name to its qualified name
449
- #
450
- # This is a simplified resolution that checks:
451
- # 1. Same package
452
- # 2. Already qualified name in index
453
- #
454
- # @param name [String] Class name to resolve
455
- # @param current_package_path [String] Current package context
456
- # @return [String, nil] Resolved qualified name
457
- def resolve_qualified_name(name, current_package_path)
458
- # If name contains "::", it might already be qualified
459
- return name if @qualified_names.key?(name)
460
-
461
- # Try in current package
462
- local_qname = "#{current_package_path}::#{name}"
463
- return local_qname if @qualified_names.key?(local_qname)
464
-
465
- # O(1) lookup using reverse index instead of O(n) scan
466
- candidates = @simple_name_to_qnames[name]
467
- candidates&.first
468
- end
469
-
470
202
  private
471
203
 
472
204
  # Convert a hash with default proc to a plain hash (Marshal-safe)
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module UmlRepository
5
+ class IndexBuilder
6
+ def build_association_index # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
7
+ # Collect document-level associations (XMI format)
8
+ @document.associations&.each do |assoc|
9
+ next unless assoc.xmi_id
10
+
11
+ @associations[assoc.xmi_id] = assoc
12
+ end
13
+
14
+ # Collect class-level associations (QEA/EA format)
15
+ # Note: This requires qualified_names index to be built first
16
+ @qualified_names.each_value do |klass|
17
+ next unless klass.respond_to?(:associations) && klass.associations
18
+
19
+ klass.associations.each do |assoc|
20
+ next unless assoc.xmi_id
21
+
22
+ # Avoid duplicates - only add if not already present
23
+ @associations[assoc.xmi_id] ||= assoc
24
+ end
25
+ end
26
+ end
27
+
28
+ # Build the inheritance graph index
29
+ #
30
+ # Creates a hash mapping parent qualified names to arrays of
31
+ # child qualified names:
32
+ # "ModelRoot::Parent" => ["ModelRoot::Child1", "ModelRoot::Child2"]
33
+ # @api public
34
+ def build_inheritance_graph_index
35
+ # Process top-level classes
36
+ if @document.classes
37
+ process_generalizations(@document.classes,
38
+ ROOT_PACKAGE_NAME)
39
+ end
40
+
41
+ # Process classes in packages
42
+ traverse_packages(@document.packages,
43
+ parent_path: ROOT_PACKAGE_NAME) do |package, path|
44
+ process_generalizations(package.classes, path) if package.classes
45
+ end
46
+ end
47
+
48
+ # Process generalization relationships to build inheritance graph
49
+ #
50
+ # @param classes [Array<Lutaml::Uml::Class>] Classes to process
51
+ # @param package_path [String] Package path for these classes
52
+ def process_generalizations(classes, package_path) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
53
+ return unless classes
54
+
55
+ classes.each do |klass|
56
+ next unless klass.name
57
+
58
+ child_qname = "#{package_path}::#{klass.name}"
59
+
60
+ # Handle generalization attribute
61
+ if klass.generalization
62
+ parent_name = extract_parent_name(klass.generalization)
63
+ if parent_name
64
+ parent_qname = resolve_qualified_name(parent_name, package_path)
65
+ if parent_qname && child_qname != parent_qname
66
+ @inheritance_graph[parent_qname] ||= []
67
+ @inheritance_graph[parent_qname] << child_qname
68
+ end
69
+ end
70
+ end
71
+
72
+ # Handle inheritance associations
73
+ next unless klass.associations
74
+
75
+ klass.associations.each do |assoc|
76
+ next unless assoc.respond_to?(:member_end_type)
77
+ next unless assoc.member_end_type == "inheritance"
78
+
79
+ parent_name = assoc.member_end
80
+ next unless parent_name
81
+
82
+ parent_name = parent_name.name if parent_name.respond_to?(:name)
83
+ next unless parent_name.is_a?(String) && !parent_name.empty?
84
+
85
+ parent_qname = resolve_qualified_name(parent_name, package_path)
86
+ next unless parent_qname
87
+ next if child_qname == parent_qname
88
+
89
+ @inheritance_graph[parent_qname] ||= []
90
+ @inheritance_graph[parent_qname] << child_qname
91
+ end
92
+ end
93
+ end
94
+
95
+ # Extract parent name from generalization object
96
+ #
97
+ # @param generalization [Lutaml::Uml::Generalization]
98
+ # Generalization object
99
+ # @return [String, nil] Parent class name
100
+ def extract_parent_name(generalization) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
101
+ return nil unless generalization
102
+
103
+ # Check for general attribute (could be a string or object)
104
+ if generalization.respond_to?(:general)
105
+ parent = generalization.general
106
+ return parent.name if parent.respond_to?(:name)
107
+ return parent.to_s if parent
108
+ end
109
+
110
+ # Check for name attribute directly
111
+ if generalization.respond_to?(:name) && generalization.name
112
+ return generalization.name
113
+ end
114
+
115
+ nil
116
+ end
117
+
118
+ # Resolve a class name to its qualified name
119
+ #
120
+ # This is a simplified resolution that checks:
121
+ # 1. Same package
122
+ # 2. Already qualified name in index
123
+ #
124
+ # @param name [String] Class name to resolve
125
+ # @param current_package_path [String] Current package context
126
+ # @return [String, nil] Resolved qualified name
127
+ def resolve_qualified_name(name, current_package_path)
128
+ # If name contains "::", it might already be qualified
129
+ return name if @qualified_names.key?(name)
130
+
131
+ # Try in current package
132
+ local_qname = "#{current_package_path}::#{name}"
133
+ return local_qname if @qualified_names.key?(local_qname)
134
+
135
+ # O(1) lookup using reverse index instead of O(n) scan
136
+ candidates = @simple_name_to_qnames[name]
137
+ candidates&.first
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module UmlRepository
5
+ class IndexBuilder
6
+ # Build the qualified name index
7
+ #
8
+ # Creates a hash mapping qualified names to Class/DataType/Enum objects:
9
+ # "ModelRoot::i-UR::urf::Building" => Class{}
10
+ # @api public
11
+ def build_qualified_name_index # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
12
+ # Index top-level classes, data_types, and enums from document
13
+ if @document.classes
14
+ index_classifiers(@document.classes,
15
+ ROOT_PACKAGE_NAME)
16
+ end
17
+ if @document.data_types
18
+ index_classifiers(@document.data_types,
19
+ ROOT_PACKAGE_NAME)
20
+ end
21
+ index_classifiers(@document.enums, ROOT_PACKAGE_NAME) if @document.enums
22
+
23
+ # Traverse packages and index their classifiers
24
+ traverse_packages(@document.packages,
25
+ parent_path: ROOT_PACKAGE_NAME) do |package, path|
26
+ index_classifiers(package.classes, path) if package.classes
27
+ index_classifiers(package.data_types, path) if package.data_types
28
+ index_classifiers(package.enums, path) if package.enums
29
+ end
30
+ end
31
+
32
+ # Build the stereotype index
33
+ #
34
+ # Creates a hash grouping classes by their stereotype:
35
+ # "featureType" => [Class{}, Class{}],
36
+ # "dataType" => [Class{}]
37
+ # @api public
38
+ def build_stereotype_index # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity
39
+ # Process top-level classes
40
+ index_by_stereotype(@document.classes) if @document.classes
41
+ index_by_stereotype(@document.data_types) if @document.data_types
42
+ index_by_stereotype(@document.enums) if @document.enums
43
+
44
+ # Process classes in packages
45
+ traverse_packages(@document.packages) do |package, _path|
46
+ index_by_stereotype(package.classes) if package.classes
47
+ index_by_stereotype(package.data_types) if package.data_types
48
+ index_by_stereotype(package.enums) if package.enums
49
+ end
50
+ end
51
+
52
+ # Index classifiers (classes, data types, enums) by their qualified names
53
+ #
54
+ # @param classifiers [Array] Array of classifier objects
55
+ # @param package_path [String] Package path for these classifiers
56
+ def index_classifiers(classifiers, package_path) # rubocop:disable Metrics/MethodLength
57
+ return unless classifiers
58
+
59
+ classifiers.each do |classifier|
60
+ next unless classifier.name
61
+
62
+ qualified_name = "#{package_path}::#{classifier.name}"
63
+ @qualified_names[qualified_name] = classifier
64
+ if classifier.xmi_id
65
+ @class_to_qname[classifier.xmi_id] =
66
+ qualified_name
67
+ end
68
+ @classes[classifier.xmi_id] = classifier if classifier.xmi_id
69
+ @simple_name_to_qnames[classifier.name] ||= []
70
+ @simple_name_to_qnames[classifier.name] << qualified_name
71
+ (@package_to_classes[package_path] ||= []) << classifier
72
+ end
73
+ end
74
+
75
+ # Index classifiers by their stereotypes
76
+ #
77
+ # @param classifiers [Array] Array of classifier objects
78
+ def index_by_stereotype(classifiers) # rubocop:disable Metrics/CyclomaticComplexity
79
+ return unless classifiers
80
+
81
+ classifiers.each do |classifier|
82
+ next unless classifier.stereotype && !classifier.stereotype.empty?
83
+
84
+ # Handle both String and Array stereotypes
85
+ stereotypes = Array(classifier.stereotype)
86
+ stereotypes.each do |stereotype|
87
+ @stereotypes[stereotype] ||= []
88
+ @stereotypes[stereotype] << classifier
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module UmlRepository
5
+ class IndexBuilder
6
+ # Build the package path index
7
+ #
8
+ # Creates a hash mapping package paths to Package objects:
9
+ # "ModelRoot" => Package{},
10
+ # "ModelRoot::i-UR" => Package{},
11
+ # "ModelRoot::i-UR::urf" => Package{}
12
+ # @api public
13
+ def build_package_path_index
14
+ # Add root package if it exists
15
+ @package_paths[ROOT_PACKAGE_NAME] = @document if @document
16
+
17
+ # Traverse all packages recursively
18
+ traverse_packages(@document.packages,
19
+ parent_path: ROOT_PACKAGE_NAME) do |package, path|
20
+ @package_paths[path] = package
21
+ @package_to_path[package.xmi_id] = path if package.xmi_id
22
+ end
23
+ end
24
+
25
+ # Traverse packages recursively, yielding each package with its path
26
+ #
27
+ # @param packages [Array<Lutaml::Uml::Package>] Packages to traverse
28
+ # @param parent_path [String, nil] Parent package path
29
+ # @yield [package, path] Yields each package with its full path
30
+ def traverse_packages(packages, parent_path: nil, &block)
31
+ return unless packages
32
+
33
+ packages.each do |package|
34
+ path = build_package_path(package.name, parent_path)
35
+ yield package, path if block
36
+
37
+ # Recursively traverse nested packages
38
+ if package.packages
39
+ traverse_packages(package.packages, parent_path: path,
40
+ &block)
41
+ end
42
+ end
43
+ end
44
+
45
+ # Build a package path from a package name and parent path
46
+ #
47
+ # @param name [String] Package name
48
+ # @param parent_path [String, nil] Parent package path
49
+ # @return [String] Full package path
50
+ def build_package_path(name, parent_path)
51
+ return name unless parent_path
52
+
53
+ "#{parent_path}::#{name}"
54
+ end
55
+ end
56
+ end
57
+ end
@@ -53,8 +53,8 @@ module Lutaml
53
53
  # @param options [Hash] Export options
54
54
  # @option options [PackageMetadata, Hash] :metadata Package metadata
55
55
  # (can be PackageMetadata object or Hash)
56
- # @option options [Symbol] :serialization_format (:marshal) Format for
57
- # Document serialization (:marshal or :yaml)
56
+ # @option options [Symbol] :serialization_format (:yaml) Format for
57
+ # Document serialization (:yaml)
58
58
  # @option options [Boolean] :include_xmi (false) Include source XMI
59
59
  # in package
60
60
  # @option options [Integer] :compression_level (6) ZIP compression level
@@ -106,7 +106,7 @@ module Lutaml
106
106
  # @return [Hash] Default options
107
107
  def default_options
108
108
  {
109
- serialization_format: :marshal,
109
+ serialization_format: :yaml,
110
110
  include_xmi: false,
111
111
  compression_level: 6,
112
112
  name: "UML Model",
@@ -154,10 +154,9 @@ module Lutaml
154
154
  # @raise [ArgumentError] If options are invalid
155
155
  def validate_options!
156
156
  format = @options[:serialization_format]
157
- unless %i[marshal yaml].include?(format)
157
+ unless format == :yaml
158
158
  raise ArgumentError,
159
- "Invalid serialization format: #{format}. " \
160
- "Must be :marshal or :yaml"
159
+ "Invalid serialization format: #{format}. Must be :yaml"
161
160
  end
162
161
  end
163
162
 
@@ -191,18 +190,9 @@ module Lutaml
191
190
  #
192
191
  # @param zip [Zip::File] The ZIP archive
193
192
  # @return [void]
194
- def write_document(zip) # rubocop:disable Metrics/MethodLength
195
- format = @options[:serialization_format]
196
-
197
- case format
198
- when :yaml
199
- zip.get_output_stream("repository.yaml") do |io|
200
- io.write(@repository.document.to_yaml)
201
- end
202
- when :marshal
203
- zip.get_output_stream("repository.marshal") do |io|
204
- io.write(Marshal.dump(@repository.document))
205
- end
193
+ def write_document(zip)
194
+ zip.get_output_stream("repository.yaml") do |io|
195
+ io.write(@repository.document.to_yaml)
206
196
  end
207
197
  end
208
198
 
@@ -211,8 +201,8 @@ module Lutaml
211
201
  # @param zip [Zip::File] The ZIP archive
212
202
  # @return [void]
213
203
  def write_indexes(zip)
214
- zip.get_output_stream("indexes/all.marshal") do |io|
215
- io.write(Marshal.dump(@repository.indexes))
204
+ zip.get_output_stream("indexes/all.yaml") do |io|
205
+ io.write(YAML.dump(@repository.indexes))
216
206
  end
217
207
  end
218
208