lutaml 0.10.8 → 0.10.10

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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +42 -8
  3. data/lib/lutaml/cli/interactive_shell/command_base.rb +2 -2
  4. data/lib/lutaml/cli/interactive_shell/query_commands.rb +1 -1
  5. data/lib/lutaml/cli/interactive_shell.rb +21 -18
  6. data/lib/lutaml/cli/resource_registry.rb +1 -1
  7. data/lib/lutaml/cli/uml/inspect_command.rb +1 -1
  8. data/lib/lutaml/converter/dsl_to_uml.rb +8 -8
  9. data/lib/lutaml/converter/xmi_to_uml.rb +2 -2
  10. data/lib/lutaml/ea/diagram/style_parser.rb +15 -15
  11. data/lib/lutaml/ea/diagram/style_resolver.rb +4 -3
  12. data/lib/lutaml/formatter/graphviz.rb +1 -1
  13. data/lib/lutaml/model_transformations/parsers/qea_parser.rb +2 -4
  14. data/lib/lutaml/model_transformations/parsers/xmi_parser.rb +1 -6
  15. data/lib/lutaml/qea/factory/document_builder.rb +3 -3
  16. data/lib/lutaml/qea/lookup_indexes.rb +2 -2
  17. data/lib/lutaml/qea/models/base_model.rb +1 -1
  18. data/lib/lutaml/qea/repositories/base_repository.rb +7 -7
  19. data/lib/lutaml/qea/validation/base_validator.rb +5 -3
  20. data/lib/lutaml/qea/verification/element_comparator.rb +18 -47
  21. data/lib/lutaml/uml/association_generalization.rb +1 -0
  22. data/lib/lutaml/uml/class.rb +0 -1
  23. data/lib/lutaml/uml/classifier.rb +3 -0
  24. data/lib/lutaml/uml/data_type.rb +0 -1
  25. data/lib/lutaml/uml/has_attributes.rb +1 -1
  26. data/lib/lutaml/uml/has_members.rb +1 -1
  27. data/lib/lutaml/uml/inheritance_walker.rb +4 -4
  28. data/lib/lutaml/uml/model_helpers.rb +5 -62
  29. data/lib/lutaml/uml/operation.rb +6 -0
  30. data/lib/lutaml/uml/operation_parameter.rb +17 -0
  31. data/lib/lutaml/uml/qualified_name.rb +4 -5
  32. data/lib/lutaml/uml/top_element_attribute.rb +4 -0
  33. data/lib/lutaml/uml/validation/document_structure_validator.rb +4 -4
  34. data/lib/lutaml/uml_repository/class_lookup_index.rb +1 -1
  35. data/lib/lutaml/uml_repository/exporters/json_exporter.rb +1 -1
  36. data/lib/lutaml/uml_repository/exporters/markdown/class_page_builder.rb +2 -2
  37. data/lib/lutaml/uml_repository/index_builder.rb +15 -15
  38. data/lib/lutaml/uml_repository/package_exporter.rb +3 -5
  39. data/lib/lutaml/uml_repository/package_loader.rb +3 -3
  40. data/lib/lutaml/uml_repository/queries/association_query.rb +2 -2
  41. data/lib/lutaml/uml_repository/queries/inheritance_query.rb +9 -13
  42. data/lib/lutaml/uml_repository/queries/search_query.rb +7 -7
  43. data/lib/lutaml/uml_repository/query_dsl/conditions/hash_condition.rb +2 -2
  44. data/lib/lutaml/uml_repository/query_dsl/order.rb +2 -2
  45. data/lib/lutaml/uml_repository/query_dsl/query_builder.rb +2 -2
  46. data/lib/lutaml/uml_repository/repository.rb +2 -2
  47. data/lib/lutaml/uml_repository/repository_enhanced.rb +1 -1
  48. data/lib/lutaml/uml_repository/static_site/association_serialization.rb +38 -86
  49. data/lib/lutaml/uml_repository/static_site/data_transformer.rb +13 -39
  50. data/lib/lutaml/uml_repository/static_site/models/spa_association.rb +32 -0
  51. data/lib/lutaml/uml_repository/static_site/models/spa_association_end.rb +28 -0
  52. data/lib/lutaml/uml_repository/static_site/models/spa_attribute.rb +42 -0
  53. data/lib/lutaml/uml_repository/static_site/models/spa_base.rb +12 -0
  54. data/lib/lutaml/uml_repository/static_site/models/spa_cardinality.rb +21 -0
  55. data/lib/lutaml/uml_repository/static_site/models/spa_class.rb +64 -0
  56. data/lib/lutaml/uml_repository/static_site/models/spa_diagram.rb +33 -0
  57. data/lib/lutaml/uml_repository/static_site/models/spa_document.rb +42 -0
  58. data/lib/lutaml/uml_repository/static_site/models/spa_inherited_association.rb +27 -0
  59. data/lib/lutaml/uml_repository/static_site/models/spa_inherited_attribute.rb +28 -0
  60. data/lib/lutaml/uml_repository/static_site/models/spa_literal.rb +21 -0
  61. data/lib/lutaml/uml_repository/static_site/models/spa_metadata.rb +26 -0
  62. data/lib/lutaml/uml_repository/static_site/models/spa_operation.rb +37 -0
  63. data/lib/lutaml/uml_repository/static_site/models/spa_package.rb +37 -0
  64. data/lib/lutaml/uml_repository/static_site/models/spa_package_tree_node.rb +35 -0
  65. data/lib/lutaml/uml_repository/static_site/models/spa_parameter.rb +23 -0
  66. data/lib/lutaml/uml_repository/static_site/models/spa_search_entry.rb +37 -0
  67. data/lib/lutaml/uml_repository/static_site/models/spa_statistics.rb +27 -0
  68. data/lib/lutaml/uml_repository/static_site/models/spa_tree_class_ref.rb +23 -0
  69. data/lib/lutaml/uml_repository/static_site/search_index_builder.rb +46 -131
  70. data/lib/lutaml/uml_repository/static_site/serializers/attribute_serializer.rb +11 -11
  71. data/lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb +17 -16
  72. data/lib/lutaml/uml_repository/static_site/serializers/diagram_serializer.rb +8 -12
  73. data/lib/lutaml/uml_repository/static_site/serializers/inheritance_resolver.rb +44 -51
  74. data/lib/lutaml/uml_repository/static_site/serializers/metadata_builder.rb +8 -5
  75. data/lib/lutaml/uml_repository/static_site/serializers/operation_serializer.rb +14 -11
  76. data/lib/lutaml/uml_repository/static_site/serializers/package_serializer.rb +15 -17
  77. data/lib/lutaml/uml_repository/static_site/serializers/package_tree_builder.rb +19 -31
  78. data/lib/lutaml/uml_repository/statistics_calculator.rb +2 -4
  79. data/lib/lutaml/version.rb +1 -1
  80. data/lib/lutaml/xmi/liquid_drops/generalization_attribute_drop.rb +2 -2
  81. data/lib/lutaml/xmi/liquid_drops/package_drop.rb +11 -1
  82. data/lib/lutaml/xmi/parsers/xmi_base.rb +4 -4
  83. data/lib/lutaml/xmi/parsers/xmi_connector.rb +7 -7
  84. metadata +22 -2
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "top_element"
4
4
  require_relative "association_generalization"
5
+ require_relative "operation"
5
6
 
6
7
  module Lutaml
7
8
  module Uml
@@ -9,6 +10,8 @@ module Lutaml
9
10
  attribute :association_generalization,
10
11
  ::Lutaml::Uml::AssociationGeneralization,
11
12
  collection: true, default: -> { [] }
13
+ attribute :operations, Operation, collection: true, default: -> { [] }
14
+ attribute :is_abstract, :boolean, default: false
12
15
 
13
16
  yaml do
14
17
  map "generalization", to: :association_generalization
@@ -11,7 +11,6 @@ module Lutaml
11
11
  class DataType < Classifier
12
12
  attribute :nested_classifier, :string, collection: true,
13
13
  default: -> { [] }
14
- attribute :is_abstract, :boolean, default: false
15
14
  attribute :type, :string
16
15
  attribute :attributes, TopElementAttribute, collection: true
17
16
  attribute :modifier, :string
@@ -5,7 +5,7 @@ module Lutaml
5
5
  module HasAttributes
6
6
  def update_attributes(attributes = {})
7
7
  attributes.to_h.each do |name, value|
8
- value = value.str if value.respond_to?(:str)
8
+ value = value.str if value.is_a?(Parslet::Slice)
9
9
  public_send(:"#{name}=", value)
10
10
  end
11
11
  end
@@ -21,7 +21,7 @@ module Lutaml
21
21
  private
22
22
 
23
23
  def association_type(type)
24
- return type if respond_to?(:"#{type}=")
24
+ return type if self.class.attributes.key?(type.to_sym)
25
25
 
26
26
  raise(UnknownMemberTypeError, "Unknown member type: #{type}")
27
27
  end
@@ -31,7 +31,7 @@ module Lutaml
31
31
  # puts "#{' ' * (level - 1)}Parent #{level}: #{parent.name}"
32
32
  # end
33
33
  def walk(klass)
34
- return [] unless klass.respond_to?(:generalization) && klass.generalization
34
+ return [] unless klass.is_a?(Lutaml::Uml::Class) && klass.generalization
35
35
 
36
36
  ancestors = []
37
37
  @visited.clear
@@ -69,12 +69,12 @@ module Lutaml
69
69
  # Uses a trail set for cycle detection (avoids Set mutation issues across calls).
70
70
  def collect_ancestors(klass, result, trail = [])
71
71
  return [] if trail.include?(klass.xmi_id) # cycle guard
72
- return [] unless klass.respond_to?(:generalization) && klass.generalization
72
+ return [] unless klass.is_a?(Lutaml::Uml::Class) && klass.generalization
73
73
 
74
74
  trail = trail.dup
75
75
  trail << klass.xmi_id
76
76
 
77
- general_id = klass.generalization.respond_to?(:general_id) ? klass.generalization.general_id : nil
77
+ general_id = klass.generalization.general_id
78
78
  parent = general_id ? find_class_by_id(general_id) : nil
79
79
 
80
80
  return [] unless parent
@@ -85,7 +85,7 @@ module Lutaml
85
85
 
86
86
  def find_class_by_id(xmi_id)
87
87
  @repository.indexes&.dig(:qualified_names, xmi_id) ||
88
- @repository.send(:classes_index)&.find { |c| c.xmi_id == xmi_id }
88
+ @repository.classes_index&.find { |c| c.xmi_id == xmi_id }
89
89
  end
90
90
  end
91
91
  end
@@ -2,20 +2,7 @@
2
2
 
3
3
  module Lutaml
4
4
  module Uml
5
- # Shared helper methods for UML model objects.
6
- # These methods are used across transformers, serializers, and presenters
7
- # to avoid duplication of common model traversal and formatting logic.
8
5
  module ModelHelpers
9
- # Normalize a stereotype value to a consistent Array format.
10
- #
11
- # @param stereotype [String, Array, nil, Symbol] The stereotype value
12
- # @return [Array<String>] Array of stereotype strings
13
- #
14
- # @example
15
- # normalize_stereotypes(nil) # => []
16
- # normalize_stereotypes("enumeration") # => ["enumeration"]
17
- # normalize_stereotypes(["a", "b"]) # => ["a", "b"]
18
- # normalize_stereotypes(:enumeration) # => ["enumeration"]
19
6
  def normalize_stereotypes(stereotype)
20
7
  return [] if stereotype.nil?
21
8
 
@@ -27,51 +14,28 @@ module Lutaml
27
14
  end
28
15
  end
29
16
 
30
- # Build a fully qualified name by walking the namespace chain of a UML element.
31
- #
32
- # @param uml_element [Lutaml::Uml::TopElement, Lutaml::Uml::Package] Any element with a namespace
33
- # @return [String] Fully qualified name joined by '::'
34
- #
35
- # @example
36
- # qualified_name_for(class_in_package) # => "ModelName::PackageName::ClassName"
37
17
  def qualified_name_for(uml_element)
38
- return uml_element.name unless uml_element.respond_to?(:namespace)
39
-
40
18
  parts = []
41
19
  current = uml_element
42
20
 
43
21
  while current
44
22
  parts.unshift(current.name) if current.name
45
- current = if current.respond_to?(:namespace) && current.namespace
46
- current.namespace
47
- else
48
- break
49
- end
23
+ current = current.namespace
50
24
  end
51
25
 
52
26
  parts.join("::")
53
27
  end
54
28
 
55
- # Build a package-only namespace path (no class names).
56
- #
57
- # @param uml_element [Lutaml::Uml::TopElement, Lutaml::Uml::Package]
58
- # @return [String] Package path joined by '::'
59
- #
60
- # @example
61
- # package_path_for(class_in_nested_package) # => "ModelName::ParentPackage::ChildPackage"
62
29
  def package_path_for(uml_element)
63
- return uml_element.name unless uml_element.respond_to?(:namespace)
64
-
65
30
  parts = []
66
31
  current = uml_element
67
32
 
68
33
  while current
69
34
  if current.is_a?(Lutaml::Uml::Package)
70
35
  parts.unshift(current.name) if current.name
71
- current = current.namespace if current.respond_to?(:namespace)
36
+ current = current.namespace
72
37
  elsif current.is_a?(Lutaml::Uml::TopElement)
73
- # Stop at the first TopElement (class, enum, etc.) — namespace above is package
74
- current = current.namespace if current.respond_to?(:namespace)
38
+ current = current.namespace
75
39
  else
76
40
  break
77
41
  end
@@ -80,14 +44,6 @@ module Lutaml
80
44
  parts.join("::")
81
45
  end
82
46
 
83
- # Extract the leaf class name from a fully-qualified class name or class object.
84
- #
85
- # @param uml_class [String, Class] A class name string or a Lutaml::Uml::* class instance
86
- # @return [String] The leaf class name
87
- #
88
- # @example
89
- # class_type_for("Lutaml::Uml::Class") # => "Class"
90
- # class_type_for(some_enum_object) # => "Enumeration"
91
47
  def class_type_for(uml_class)
92
48
  case uml_class
93
49
  when String then uml_class.split("::").last
@@ -96,19 +52,11 @@ module Lutaml
96
52
  end
97
53
  end
98
54
 
99
- # Format a cardinality object as a "min..max" string.
100
- #
101
- # @param cardinality [Lutaml::Uml::Cardinality, Hash, nil]
102
- # @return [String, nil] Formatted cardinality string or nil
103
- #
104
- # @example
105
- # format_cardinality(Lutaml::Uml::Cardinality.new(min: 0, max: 5)) # => "0..5"
106
- # format_cardinality({min: 1, max: nil}) # => "1..*"
107
55
  def format_cardinality(cardinality)
108
56
  return nil unless cardinality
109
57
 
110
- min = cardinality.respond_to?(:min) ? cardinality.min : cardinality[:min]
111
- max = cardinality.respond_to?(:max) ? cardinality.max : cardinality[:max]
58
+ min = cardinality.min
59
+ max = cardinality.max
112
60
  return nil if min.nil? && max.nil?
113
61
 
114
62
  min_str = min.nil? ? "0" : min.to_s
@@ -116,11 +64,6 @@ module Lutaml
116
64
  "#{min_str}..#{max_str}"
117
65
  end
118
66
 
119
- # Parse a cardinality string or hash into a {min:, max:} hash.
120
- #
121
- # @param min [String, nil]
122
- # @param max [String, nil]
123
- # @return [Hash{Symbol => String, nil}] Hash with :min and :max keys
124
67
  def parse_cardinality(min, max)
125
68
  { min: min, max: max }
126
69
  end
@@ -1,11 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "operation_parameter"
4
+
3
5
  module Lutaml
4
6
  module Uml
5
7
  class Operation < TopElement
6
8
  attribute :id, :string
7
9
  attribute :return_type, :string
8
10
  attribute :parameter_type, :string
11
+ attribute :is_static, :boolean, default: false
12
+ attribute :is_abstract, :boolean, default: false
13
+ attribute :owned_parameter, OperationParameter, collection: true,
14
+ default: -> { [] }
9
15
 
10
16
  yaml do
11
17
  map "id", to: :id
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Uml
5
+ class OperationParameter < Lutaml::Model::Serializable
6
+ attribute :name, :string
7
+ attribute :type, :string
8
+ attribute :direction, :string, default: "in"
9
+
10
+ yaml do
11
+ map "name", to: :name
12
+ map "type", to: :type
13
+ map "direction", to: :direction
14
+ end
15
+ end
16
+ end
17
+ end
@@ -65,8 +65,7 @@ module Lutaml
65
65
  return "" if @class_name.nil? || @class_name.empty?
66
66
 
67
67
  # Check if package path is empty
68
- if @package_path.nil? ||
69
- (@package_path.respond_to?(:empty?) && @package_path.empty?)
68
+ if @package_path.nil? || @package_path.empty?
70
69
  return @class_name
71
70
  end
72
71
 
@@ -105,7 +104,7 @@ module Lutaml
105
104
  # qname.matches_glob?("Package1::*::ClassName") # => true
106
105
  def matches_glob?(pattern)
107
106
  # Create a full path for matching (package + class)
108
- full_segments = if @package_path.respond_to?(:segments) &&
107
+ full_segments = if @package_path.is_a?(PackagePath) &&
109
108
  !@package_path.segments.empty?
110
109
  @package_path.segments + [@class_name]
111
110
  else
@@ -125,7 +124,7 @@ module Lutaml
125
124
  # new_path = PackagePath.new("Package2")
126
125
  # qname.with_package(new_path) # => QualifiedName("Package2::ClassName")
127
126
  def with_package(new_package_path)
128
- if new_package_path.respond_to?(:empty?) && new_package_path.empty?
127
+ if new_package_path.empty?
129
128
  self.class.new(@class_name)
130
129
  else
131
130
  self.class.new("#{new_package_path}#{PackagePath::SEPARATOR}#{@class_name}")
@@ -148,7 +147,7 @@ module Lutaml
148
147
  return self if relative_path == @package_path
149
148
 
150
149
  # Otherwise create new qualified name with relative path
151
- if relative_path.respond_to?(:empty?) && relative_path.empty?
150
+ if relative_path.empty?
152
151
  self.class.new(@class_name)
153
152
  else
154
153
  self.class.new("#{relative_path}#{PackagePath::SEPARATOR}#{@class_name}")
@@ -13,6 +13,10 @@ module Lutaml
13
13
  attribute :cardinality, Cardinality
14
14
  attribute :keyword, :string
15
15
  attribute :is_derived, :boolean, default: false
16
+ attribute :is_static, :boolean, default: false
17
+ attribute :is_read_only, :boolean, default: false
18
+ attribute :default, :string
19
+ attribute :stereotype, :string, collection: true, default: -> { [] }
16
20
 
17
21
  attribute :definition, :string
18
22
  attribute :association, :string
@@ -75,7 +75,7 @@ module Lutaml
75
75
  # @param path [String] Package path for error reporting
76
76
  # @return [void]
77
77
  def validate_collection(package, attribute, path) # rubocop:disable Metrics/MethodLength
78
- value = package.send(attribute)
78
+ value = package.public_send(attribute)
79
79
  unless value.nil? || value.is_a?(Array)
80
80
  result.add_error(
81
81
  category: :invalid_structure,
@@ -175,7 +175,7 @@ module Lutaml
175
175
  name_counts = Hash.new(0)
176
176
 
177
177
  collection.each do |entity|
178
- next unless entity.respond_to?(:name) && entity.name
178
+ next unless entity.name
179
179
 
180
180
  name_counts[entity.name] += 1
181
181
  end
@@ -212,10 +212,10 @@ module Lutaml
212
212
 
213
213
  # Check attribute type references
214
214
  all_classes.each do |cls, path|
215
- next unless cls.respond_to?(:attributes)
215
+ next unless cls.is_a?(Lutaml::Uml::Class) || cls.is_a?(Lutaml::Uml::DataType)
216
216
 
217
217
  (cls.attributes || []).each do |attr|
218
- next unless attr.respond_to?(:type) && attr.type
218
+ next unless attr.type
219
219
  next if primitive_type?(attr.type)
220
220
  next if valid_types.include?(attr.type)
221
221
 
@@ -18,7 +18,7 @@ module Lutaml
18
18
 
19
19
  classes.each do |klass|
20
20
  @by_xmi_id[klass.xmi_id] = klass if klass.xmi_id
21
- if klass.respond_to?(:ea_object_id) && klass.ea_object_id
21
+ if klass.class.attributes.key?(:ea_object_id) && klass.ea_object_id
22
22
  @by_object_id[klass.ea_object_id] = klass
23
23
  end
24
24
  end
@@ -228,7 +228,7 @@ module Lutaml
228
228
  # @param klass [Object] The class object
229
229
  # @return [Array<Hash>] Array of operation hashes
230
230
  def serialize_operations(klass)
231
- return [] unless klass.respond_to?(:operations) && klass.operations
231
+ return [] unless klass.is_a?(Lutaml::Uml::Classifier) && klass.operations
232
232
 
233
233
  klass.operations.map do |op|
234
234
  {
@@ -55,7 +55,7 @@ module Lutaml
55
55
  end
56
56
 
57
57
  def build_definition_section(klass)
58
- return "" unless klass.respond_to?(:definition) && klass.definition
58
+ return "" unless klass.definition
59
59
 
60
60
  "## Description\n\n#{klass.definition}\n\n"
61
61
  end
@@ -105,7 +105,7 @@ module Lutaml
105
105
  end
106
106
 
107
107
  def build_operations_section(klass)
108
- return "" unless klass.respond_to?(:operations) && klass.operations&.any?
108
+ return "" unless klass.operations&.any?
109
109
 
110
110
  content = "## Operations\n\n"
111
111
  content += "| Name | Return Type | Visibility |\n"
@@ -56,13 +56,13 @@ module Lutaml
56
56
  def self.build_package_paths(document)
57
57
  builder = new(document)
58
58
  builder.build_package_path_index
59
- builder.instance_variable_get(:@package_paths).freeze
59
+ builder.package_paths.freeze
60
60
  end
61
61
 
62
62
  def self.build_package_to_path(document)
63
63
  builder = new(document)
64
64
  builder.build_package_path_index
65
- builder.instance_variable_get(:@package_to_path).freeze
65
+ builder.package_to_path.freeze
66
66
  end
67
67
 
68
68
  # Build qualified names index
@@ -72,19 +72,19 @@ module Lutaml
72
72
  def self.build_qualified_names(document)
73
73
  builder = new(document)
74
74
  builder.build_qualified_name_index
75
- builder.instance_variable_get(:@qualified_names).freeze
75
+ builder.qualified_names.freeze
76
76
  end
77
77
 
78
78
  def self.build_class_to_qname(document)
79
79
  builder = new(document)
80
80
  builder.build_qualified_name_index
81
- builder.instance_variable_get(:@class_to_qname).freeze
81
+ builder.class_to_qname.freeze
82
82
  end
83
83
 
84
84
  def self.build_classes(document)
85
85
  builder = new(document)
86
86
  builder.build_qualified_name_index
87
- builder.instance_variable_get(:@classes).freeze
87
+ builder.classes.freeze
88
88
  end
89
89
 
90
90
  def self.build_associations(document)
@@ -93,7 +93,7 @@ module Lutaml
93
93
  # class-level associations
94
94
  builder.build_qualified_name_index
95
95
  builder.build_association_index
96
- builder.instance_variable_get(:@associations).freeze
96
+ builder.associations.freeze
97
97
  end
98
98
 
99
99
  # Build stereotypes index
@@ -103,7 +103,7 @@ module Lutaml
103
103
  def self.build_stereotypes(document)
104
104
  builder = new(document)
105
105
  builder.build_stereotype_index
106
- builder.instance_variable_get(:@stereotypes).freeze
106
+ builder.stereotypes.freeze
107
107
  end
108
108
 
109
109
  # Build inheritance graph index
@@ -113,15 +113,13 @@ module Lutaml
113
113
  # @return [Hash] Frozen hash mapping parent qnames to child qnames
114
114
  def self.build_inheritance_graph(document, indexes)
115
115
  builder = new(document)
116
- # If qualified_names index is provided, use it
117
116
  if indexes && indexes[:qualified_names]
118
- builder.instance_variable_set(:@qualified_names,
119
- indexes[:qualified_names])
117
+ builder.qualified_names = indexes[:qualified_names]
120
118
  else
121
119
  builder.build_qualified_name_index
122
120
  end
123
121
  builder.build_inheritance_graph_index
124
- builder.instance_variable_get(:@inheritance_graph).freeze
122
+ builder.inheritance_graph.freeze
125
123
  end
126
124
 
127
125
  # Build diagram index
@@ -131,15 +129,13 @@ module Lutaml
131
129
  # @return [Hash] Frozen hash mapping package IDs to Diagram objects
132
130
  def self.build_diagram_index(document, indexes)
133
131
  builder = new(document)
134
- # If package_paths index is provided, use it
135
132
  if indexes && indexes[:package_paths]
136
- builder.instance_variable_set(:@package_paths,
137
- indexes[:package_paths])
133
+ builder.package_paths = indexes[:package_paths]
138
134
  else
139
135
  builder.build_package_path_index
140
136
  end
141
137
  builder.build_diagram_index
142
- builder.instance_variable_get(:@diagram_index).freeze
138
+ builder.diagram_index.freeze
143
139
  end
144
140
 
145
141
  def initialize(document)
@@ -157,6 +153,10 @@ module Lutaml
157
153
  @package_to_classes = {}
158
154
  end
159
155
 
156
+ attr_accessor :package_paths, :qualified_names
157
+ attr_reader :package_to_path, :class_to_qname, :classes, :associations,
158
+ :stereotypes, :inheritance_graph, :diagram_index
159
+
160
160
  # Build all indexes and return them as a frozen hash
161
161
  #
162
162
  # @return [Hash] Frozen hash containing all indexes
@@ -277,15 +277,13 @@ module Lutaml
277
277
  # @param klass [Object] The class object
278
278
  # @return [Array<Hash>] List of attributes
279
279
  def build_attributes_list(klass)
280
- return [] unless klass.respond_to?(:attributes) && klass.attributes
280
+ return [] unless klass.is_a?(Lutaml::Uml::Classifier) && klass.attributes
281
281
 
282
282
  klass.attributes.map do |attr|
283
283
  {
284
284
  "name" => attr.name,
285
285
  "type" => attr.type,
286
- "visibility" => if attr.respond_to?(:visibility)
287
- attr.visibility
288
- end,
286
+ "visibility" => attr.visibility,
289
287
  }.compact
290
288
  end
291
289
  end
@@ -316,7 +314,7 @@ module Lutaml
316
314
  def count_all_attributes
317
315
  total = 0
318
316
  @repository.indexes[:qualified_names].each_value do |klass|
319
- if klass.respond_to?(:attributes) && klass.attributes
317
+ if klass.is_a?(Lutaml::Uml::Classifier) && klass.attributes
320
318
  total += klass.attributes.size
321
319
  end
322
320
  end
@@ -147,11 +147,11 @@ module Lutaml
147
147
  # @raise [RuntimeError] If document is missing or format is unknown
148
148
  def self.load_document(zip, metadata) # rubocop:disable Metrics/MethodLength
149
149
  # Handle both PackageMetadata object and Hash (backward compatibility)
150
- format = if metadata.respond_to?(:serialization_format)
151
- metadata.serialization_format
152
- else
150
+ format = if metadata.is_a?(Hash)
153
151
  metadata["serialization_format"] ||
154
152
  metadata[:serialization_format]
153
+ else
154
+ metadata.serialization_format
155
155
  end
156
156
 
157
157
  case format.to_s
@@ -57,13 +57,13 @@ module Lutaml
57
57
  results = []
58
58
 
59
59
  # Get owned associations from the class itself
60
- if klass.respond_to?(:associations) && klass.associations
60
+ if (klass.is_a?(Lutaml::Uml::Class) || klass.is_a?(Lutaml::Uml::DataType)) && klass.associations
61
61
  results.concat(klass.associations)
62
62
  end
63
63
 
64
64
  # Get associations from document level unless owned_only
65
65
  if !owned_only &&
66
- document.respond_to?(:associations) && document.associations
66
+ document.is_a?(Lutaml::Uml::Document) && document.associations
67
67
  document_associations = document.associations.select do |assoc|
68
68
  match_association?(assoc, class_name, direction)
69
69
  end
@@ -36,7 +36,7 @@ module Lutaml
36
36
  def supertype(class_or_qname) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
37
37
  klass = resolve_class(class_or_qname)
38
38
  return nil unless klass
39
- return nil unless klass.respond_to?(:generalization)
39
+ return nil unless klass.is_a?(Lutaml::Uml::Class)
40
40
  return nil unless klass.generalization
41
41
 
42
42
  parent_name = extract_parent_name(klass.generalization)
@@ -230,7 +230,7 @@ module Lutaml
230
230
 
231
231
  # Try as xmi_id - search in qualified_names
232
232
  indexes[:qualified_names].each_value do |entity|
233
- next unless entity.respond_to?(:xmi_id)
233
+ next unless entity.xmi_id
234
234
 
235
235
  return entity if entity.xmi_id == class_or_id
236
236
  end
@@ -280,22 +280,18 @@ module Lutaml
280
280
  # @param generalization [Lutaml::Uml::Generalization]
281
281
  # Generalization object
282
282
  # @return [String, nil] Parent class name
283
- def extract_parent_name(generalization) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
283
+ def extract_parent_name(generalization)
284
284
  return nil unless generalization
285
+ return nil unless generalization.is_a?(Lutaml::Uml::Generalization)
285
286
 
286
- # Check for general attribute (could be a string or object)
287
- if generalization.respond_to?(:general)
288
- parent = generalization.general
289
- return parent.name if parent.respond_to?(:name)
290
- return parent.to_s if parent
291
- end
287
+ parent = generalization.general
288
+ if parent
289
+ return parent.name if parent.is_a?(Lutaml::Uml::Generalization) && parent.name
292
290
 
293
- # Check for name attribute directly
294
- if generalization.respond_to?(:name) && generalization.name
295
- return generalization.name
291
+ return parent.to_s
296
292
  end
297
293
 
298
- nil
294
+ generalization.name if generalization.name
299
295
  end
300
296
 
301
297
  # Resolve a class name to its qualified name
@@ -129,8 +129,8 @@ module Lutaml
129
129
 
130
130
  # Check fields for match
131
131
  fields.each do |field|
132
- if entity.respond_to?(field) &&
133
- entity.send(field)&.match?(pattern)
132
+ if entity.class.attributes.key?(field) &&
133
+ entity.public_send(field)&.match?(pattern)
134
134
 
135
135
  match_field = field
136
136
  qualified_name = qname
@@ -157,7 +157,7 @@ module Lutaml
157
157
  matched_entities = indexes[:stereotypes]
158
158
  .filter_map do |_stereotype, entities|
159
159
  entities.select do |entity|
160
- entity.respond_to?(:stereotype) &&
160
+ entity.is_a?(Lutaml::Uml::Classifier) &&
161
161
  Array(entity.stereotype).any? { |s| s&.match?(pattern) }
162
162
  end.uniq
163
163
  end.uniq.flatten
@@ -218,7 +218,7 @@ module Lutaml
218
218
  )
219
219
 
220
220
  indexes[:qualified_names].filter_map do |class_qname, entity| # rubocop:disable Metrics/BlockLength
221
- next unless entity.respond_to?(:attributes) && entity.attributes
221
+ next unless entity.is_a?(Lutaml::Uml::Classifier) && entity.attributes
222
222
 
223
223
  match_field = nil
224
224
  match_attr = nil
@@ -227,8 +227,8 @@ module Lutaml
227
227
  entity.attributes.each do |attr|
228
228
  # Check attribute for match
229
229
  fields.each do |field|
230
- if attr.respond_to?(field) &&
231
- attr.send(field)&.match?(pattern)
230
+ if attr.class.attributes.key?(field) &&
231
+ attr.public_send(field)&.match?(pattern)
232
232
 
233
233
  match_attr = attr
234
234
  match_field = field
@@ -294,7 +294,7 @@ module Lutaml
294
294
  match_field = nil
295
295
 
296
296
  fields.each do |field|
297
- if assoc.respond_to?(field) && assoc.send(field)&.match?(pattern)
297
+ if assoc.class.attributes.key?(field) && assoc.public_send(field)&.match?(pattern)
298
298
  match_field = field
299
299
  end
300
300
  end
@@ -51,9 +51,9 @@ module Lutaml
51
51
  # @param value [Object] The expected value
52
52
  # @return [Boolean] true if condition matches
53
53
  def matches_condition?(obj, key, value)
54
- return false unless obj.respond_to?(key)
54
+ return false unless obj.class.attributes.key?(key.to_sym)
55
55
 
56
- actual_value = obj.send(key)
56
+ actual_value = obj.public_send(key)
57
57
  compare_values(actual_value, value)
58
58
  end
59
59
 
@@ -64,9 +64,9 @@ module Lutaml
64
64
  # @param obj [Object] The object to extract value from
65
65
  # @return [Object] The value to use for sorting
66
66
  def extract_sort_value(obj)
67
- return "" unless obj.respond_to?(@field)
67
+ return "" unless obj.class.attributes.key?(@field.to_sym)
68
68
 
69
- value = obj.send(@field)
69
+ value = obj.public_send(@field)
70
70
  normalize_value(value)
71
71
  end
72
72
 
@@ -236,7 +236,7 @@ module Lutaml
236
236
  #
237
237
  # @return [Array<Lutaml::Uml::Class>] Array of class objects
238
238
  def fetch_classes
239
- indexes = @repository.instance_variable_get(:@indexes)
239
+ indexes = @repository.indexes
240
240
  qnames_index = indexes[:qualified_names] || {}
241
241
 
242
242
  qnames_index.values.select do |obj|
@@ -250,7 +250,7 @@ module Lutaml
250
250
  #
251
251
  # @return [Array<Lutaml::Uml::Package>] Array of package objects
252
252
  def fetch_packages
253
- indexes = @repository.instance_variable_get(:@indexes)
253
+ indexes = @repository.indexes
254
254
  package_paths_index = indexes[:package_paths] || {}
255
255
  package_paths_index.values
256
256
  end