ea 0.1.0 → 0.1.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 (227) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +125 -0
  3. data/Rakefile +12 -4
  4. data/TODO.next/00-publish-blocking-bugs.md +74 -0
  5. data/TODO.next/01-standalone-ea-gem-identity.md +76 -0
  6. data/TODO.next/02-optional-lutaml-uml-dependency.md +47 -0
  7. data/TODO.next/03-slim-lutaml-uml.md +79 -0
  8. data/TODO.next/04-loader-registry-for-uml-repository.md +49 -0
  9. data/TODO.next/05-extract-shared-transformer-methods.md +14 -0
  10. data/TODO.next/06-deduplicate-stereotype-loading.md +17 -0
  11. data/TODO.next/07-transformer-registry-in-factory.md +20 -0
  12. data/TODO.next/08-connector-type-registry.md +27 -0
  13. data/TODO.next/09-element-renderer-registry.md +29 -0
  14. data/TODO.next/10-connector-renderer-lsp.md +18 -0
  15. data/TODO.next/11-consolidate-style-knowledge.md +33 -0
  16. data/TODO.next/12-data-driven-from-db-row.md +24 -0
  17. data/TODO.next/13-extract-duplicated-methods.md +17 -0
  18. data/TODO.next/14-remove-dead-code.md +10 -0
  19. data/TODO.next/15-narrow-exception-handling.md +39 -0
  20. data/TODO.next/16-repository-indexes.md +28 -0
  21. data/TODO.next/17-fix-spec-quality-and-coverage.md +32 -0
  22. data/TODO.next/18-xmi-tool-specific-parser-architecture.md +172 -0
  23. data/TODO.next/19-fix-ea-gemspec-dependency-declarations.md +56 -0
  24. data/TODO.next/20-ci-requires-unreleased-lutaml-uml.md +63 -0
  25. data/TODO.next/21-qeatoxmi-via-xmi-gem.md +340 -0
  26. data/TODO.next/22-strip-respond-to-from-qeatoxmi-specs.md +32 -0
  27. data/TODO.next/23-cleanup-idallocator.md +41 -0
  28. data/TODO.next/24-tighten-parity-specs.md +42 -0
  29. data/TODO.next/25-sparx-eaid-format-for-synthesized-ids.md +62 -0
  30. data/TODO.next/26-fix-uppervalue-lowervalue-count-gap.md +51 -0
  31. data/TODO.next/27-extract-cardinality-module.md +68 -0
  32. data/TODO.next/28-extract-xml-sanitizer.md +51 -0
  33. data/TODO.next/29-ocp-registry-for-classifier-builders.md +58 -0
  34. data/TODO.next/30-struct-return-for-association-end.md +37 -0
  35. data/TODO.next/31-idallocator-specs.md +27 -0
  36. data/TODO.next/32-phase2-gap-sentinel-specs.md +53 -0
  37. data/TODO.next/33-normalize-lower-cleanup.md +30 -0
  38. data/TODO.next/34-document-member-end-order-rt-prefix.md +29 -0
  39. data/TODO.next/35-walk-runstate-for-instance-slots.md +76 -0
  40. data/TODO.next/36-wire-interface-realization.md +50 -0
  41. data/TODO.next/37-visibility-returns-real-booleans.md +36 -0
  42. data/config/diagram_styles.yml +200 -0
  43. data/config/model_transformations.yml +266 -0
  44. data/config/qea_schema.yml +1024 -0
  45. data/docs/ea_to_uml_type_mapping.md +89 -0
  46. data/docs/xmi_qea_conversion_capabilities.md +99 -0
  47. data/examples/lur/20251010_current_plateau_v5.1.lur +0 -0
  48. data/examples/lur/basic.lur +0 -0
  49. data/examples/lur/test-output.lur +0 -0
  50. data/examples/lur/test.lur +0 -0
  51. data/examples/lur_basic_usage.rb +221 -0
  52. data/examples/lur_cli_workflow.rb +263 -0
  53. data/examples/lur_statistics.rb +326 -0
  54. data/examples/qea/20251010_current_plateau_v5.1.qea +0 -0
  55. data/examples/qea/ArcGISWorkspace_template.qea +0 -0
  56. data/examples/qea/README_qea_parser.adoc +230 -0
  57. data/examples/qea/UmlModel_template.qea +0 -0
  58. data/examples/qea/basic.qea +0 -0
  59. data/examples/qea/simple.qea +0 -0
  60. data/examples/qea/simple_example.qea +0 -0
  61. data/examples/qea/test.qea +0 -0
  62. data/examples/qea_standalone_query.rb +73 -0
  63. data/examples/qea_to_repository.rb +51 -0
  64. data/examples/smoke_test_real_qea.rb +81 -0
  65. data/exe/ea +7 -0
  66. data/lib/ea/cli/app.rb +72 -0
  67. data/lib/ea/cli/command/base.rb +80 -0
  68. data/lib/ea/cli/command/convert.rb +62 -0
  69. data/lib/ea/cli/command/diagrams.rb +81 -0
  70. data/lib/ea/cli/command/list.rb +61 -0
  71. data/lib/ea/cli/command/parse.rb +29 -0
  72. data/lib/ea/cli/command/stats.rb +20 -0
  73. data/lib/ea/cli/command/validate.rb +41 -0
  74. data/lib/ea/cli/command.rb +15 -0
  75. data/lib/ea/cli/error.rb +34 -0
  76. data/lib/ea/cli/output/formatter.rb +34 -0
  77. data/lib/ea/cli/output/json_formatter.rb +20 -0
  78. data/lib/ea/cli/output/table_formatter.rb +42 -0
  79. data/lib/ea/cli/output/yaml_formatter.rb +20 -0
  80. data/lib/ea/cli/output.rb +56 -0
  81. data/lib/ea/cli.rb +17 -0
  82. data/lib/ea/diagram/configuration.rb +379 -0
  83. data/lib/ea/diagram/element_renderers/base_renderer.rb +77 -0
  84. data/lib/ea/diagram/element_renderers/class_renderer.rb +323 -0
  85. data/lib/ea/diagram/element_renderers/connector_renderer.rb +41 -0
  86. data/lib/ea/diagram/element_renderers/package_renderer.rb +61 -0
  87. data/lib/ea/diagram/element_renderers.rb +43 -0
  88. data/lib/ea/diagram/extractor.rb +560 -0
  89. data/lib/ea/diagram/layout_engine.rb +170 -0
  90. data/lib/ea/diagram/path_builder.rb +202 -0
  91. data/lib/ea/diagram/style_parser.rb +42 -0
  92. data/lib/ea/diagram/style_resolver.rb +276 -0
  93. data/lib/ea/diagram/svg_renderer.rb +274 -0
  94. data/lib/ea/diagram/util.rb +73 -0
  95. data/lib/ea/diagram.rb +47 -0
  96. data/lib/ea/qea/benchmark.rb +210 -0
  97. data/lib/ea/qea/database.rb +308 -0
  98. data/lib/ea/qea/factory/association_builder.rb +203 -0
  99. data/lib/ea/qea/factory/association_transformer.rb +91 -0
  100. data/lib/ea/qea/factory/attribute_tag_transformer.rb +57 -0
  101. data/lib/ea/qea/factory/attribute_transformer.rb +93 -0
  102. data/lib/ea/qea/factory/base_transformer.rb +177 -0
  103. data/lib/ea/qea/factory/class_transformer.rb +116 -0
  104. data/lib/ea/qea/factory/constraint_transformer.rb +75 -0
  105. data/lib/ea/qea/factory/data_type_transformer.rb +77 -0
  106. data/lib/ea/qea/factory/diagram_transformer.rb +157 -0
  107. data/lib/ea/qea/factory/document_builder.rb +283 -0
  108. data/lib/ea/qea/factory/ea_to_uml_factory.rb +229 -0
  109. data/lib/ea/qea/factory/enum_transformer.rb +74 -0
  110. data/lib/ea/qea/factory/generalization_builder.rb +227 -0
  111. data/lib/ea/qea/factory/generalization_transformer.rb +98 -0
  112. data/lib/ea/qea/factory/instance_transformer.rb +68 -0
  113. data/lib/ea/qea/factory/object_property_transformer.rb +58 -0
  114. data/lib/ea/qea/factory/operation_transformer.rb +66 -0
  115. data/lib/ea/qea/factory/package_transformer.rb +145 -0
  116. data/lib/ea/qea/factory/reference_resolver.rb +99 -0
  117. data/lib/ea/qea/factory/stereotype_loader.rb +39 -0
  118. data/lib/ea/qea/factory/tagged_value_transformer.rb +38 -0
  119. data/lib/ea/qea/factory/transformer_registry.rb +80 -0
  120. data/lib/ea/qea/factory.rb +37 -0
  121. data/lib/ea/qea/file_detector.rb +178 -0
  122. data/lib/ea/qea/infrastructure/database_connection.rb +100 -0
  123. data/lib/ea/qea/infrastructure/schema_reader.rb +136 -0
  124. data/lib/ea/qea/infrastructure/table_reader.rb +224 -0
  125. data/lib/ea/qea/infrastructure.rb +12 -0
  126. data/lib/ea/qea/models/base_model.rb +59 -0
  127. data/lib/ea/qea/models/ea_attribute.rb +109 -0
  128. data/lib/ea/qea/models/ea_attribute_tag.rb +100 -0
  129. data/lib/ea/qea/models/ea_complexity_type.rb +79 -0
  130. data/lib/ea/qea/models/ea_connector.rb +160 -0
  131. data/lib/ea/qea/models/ea_connector_type.rb +60 -0
  132. data/lib/ea/qea/models/ea_constraint_type.rb +63 -0
  133. data/lib/ea/qea/models/ea_datatype.rb +104 -0
  134. data/lib/ea/qea/models/ea_diagram.rb +115 -0
  135. data/lib/ea/qea/models/ea_diagram_link.rb +78 -0
  136. data/lib/ea/qea/models/ea_diagram_object.rb +73 -0
  137. data/lib/ea/qea/models/ea_diagram_type.rb +56 -0
  138. data/lib/ea/qea/models/ea_document.rb +63 -0
  139. data/lib/ea/qea/models/ea_object.rb +223 -0
  140. data/lib/ea/qea/models/ea_object_constraint.rb +53 -0
  141. data/lib/ea/qea/models/ea_object_property.rb +87 -0
  142. data/lib/ea/qea/models/ea_object_type.rb +73 -0
  143. data/lib/ea/qea/models/ea_operation.rb +127 -0
  144. data/lib/ea/qea/models/ea_operation_param.rb +76 -0
  145. data/lib/ea/qea/models/ea_package.rb +78 -0
  146. data/lib/ea/qea/models/ea_script.rb +62 -0
  147. data/lib/ea/qea/models/ea_status_type.rb +66 -0
  148. data/lib/ea/qea/models/ea_stereotype.rb +57 -0
  149. data/lib/ea/qea/models/ea_tagged_value.rb +99 -0
  150. data/lib/ea/qea/models/ea_xref.rb +165 -0
  151. data/lib/ea/qea/models.rb +35 -0
  152. data/lib/ea/qea/repositories/base_repository.rb +225 -0
  153. data/lib/ea/qea/repositories/object_repository.rb +219 -0
  154. data/lib/ea/qea/repositories.rb +10 -0
  155. data/lib/ea/qea/services/configuration.rb +211 -0
  156. data/lib/ea/qea/services/database_loader.rb +191 -0
  157. data/lib/ea/qea/services.rb +10 -0
  158. data/lib/ea/qea/validation/association_validator.rb +73 -0
  159. data/lib/ea/qea/validation/attribute_validator.rb +91 -0
  160. data/lib/ea/qea/validation/base_validator.rb +331 -0
  161. data/lib/ea/qea/validation/class_validator.rb +121 -0
  162. data/lib/ea/qea/validation/database/circular_reference_validator.rb +109 -0
  163. data/lib/ea/qea/validation/database/orphan_validator.rb +153 -0
  164. data/lib/ea/qea/validation/database/referential_integrity_validator.rb +128 -0
  165. data/lib/ea/qea/validation/database.rb +16 -0
  166. data/lib/ea/qea/validation/diagram_validator.rb +112 -0
  167. data/lib/ea/qea/validation/formatters/json_formatter.rb +137 -0
  168. data/lib/ea/qea/validation/formatters/text_formatter.rb +235 -0
  169. data/lib/ea/qea/validation/formatters.rb +12 -0
  170. data/lib/ea/qea/validation/operation_validator.rb +71 -0
  171. data/lib/ea/qea/validation/package_validator.rb +111 -0
  172. data/lib/ea/qea/validation/validation_engine.rb +387 -0
  173. data/lib/ea/qea/validation/validation_message.rb +144 -0
  174. data/lib/ea/qea/validation/validation_result.rb +210 -0
  175. data/lib/ea/qea/validation/validator_registry.rb +134 -0
  176. data/lib/ea/qea/validation.rb +28 -0
  177. data/lib/ea/qea/verification/comparison_result.rb +264 -0
  178. data/lib/ea/qea/verification/document_normalizer.rb +169 -0
  179. data/lib/ea/qea/verification/document_verifier.rb +322 -0
  180. data/lib/ea/qea/verification/element_comparator.rb +277 -0
  181. data/lib/ea/qea/verification/structure_matcher.rb +287 -0
  182. data/lib/ea/qea/verification.rb +14 -0
  183. data/lib/ea/qea.rb +185 -0
  184. data/lib/ea/transformations/configuration.rb +333 -0
  185. data/lib/ea/transformations/format_registry.rb +366 -0
  186. data/lib/ea/transformations/parsers/base_parser.rb +482 -0
  187. data/lib/ea/transformations/parsers/qea_parser.rb +401 -0
  188. data/lib/ea/transformations/parsers/xmi_parser.rb +243 -0
  189. data/lib/ea/transformations/transformation_engine.rb +390 -0
  190. data/lib/ea/transformations.rb +85 -0
  191. data/lib/ea/transformers/qea_to_xmi/association_end.rb +19 -0
  192. data/lib/ea/transformers/qea_to_xmi/cardinality.rb +96 -0
  193. data/lib/ea/transformers/qea_to_xmi/context.rb +106 -0
  194. data/lib/ea/transformers/qea_to_xmi/guid_format.rb +56 -0
  195. data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +92 -0
  196. data/lib/ea/transformers/qea_to_xmi/run_state.rb +107 -0
  197. data/lib/ea/transformers/qea_to_xmi/transformer.rb +607 -0
  198. data/lib/ea/transformers/qea_to_xmi/visibility.rb +73 -0
  199. data/lib/ea/transformers/qea_to_xmi.rb +29 -0
  200. data/lib/ea/transformers/uml_to_xmi/id_generator.rb +54 -0
  201. data/lib/ea/transformers/uml_to_xmi/transformer.rb +152 -0
  202. data/lib/ea/transformers/uml_to_xmi/writer.rb +96 -0
  203. data/lib/ea/transformers/uml_to_xmi.rb +16 -0
  204. data/lib/ea/transformers.rb +34 -0
  205. data/lib/ea/version.rb +1 -1
  206. data/lib/ea/xmi/liquid_drops/association_drop.rb +56 -0
  207. data/lib/ea/xmi/liquid_drops/attribute_drop.rb +72 -0
  208. data/lib/ea/xmi/liquid_drops/cardinality_drop.rb +35 -0
  209. data/lib/ea/xmi/liquid_drops/connector_drop.rb +54 -0
  210. data/lib/ea/xmi/liquid_drops/constraint_drop.rb +29 -0
  211. data/lib/ea/xmi/liquid_drops/data_type_drop.rb +63 -0
  212. data/lib/ea/xmi/liquid_drops/dependency_drop.rb +36 -0
  213. data/lib/ea/xmi/liquid_drops/diagram_drop.rb +34 -0
  214. data/lib/ea/xmi/liquid_drops/enum_drop.rb +49 -0
  215. data/lib/ea/xmi/liquid_drops/enum_owned_literal_drop.rb +25 -0
  216. data/lib/ea/xmi/liquid_drops/generalization_attribute_drop.rb +87 -0
  217. data/lib/ea/xmi/liquid_drops/generalization_drop.rb +127 -0
  218. data/lib/ea/xmi/liquid_drops/klass_drop.rb +191 -0
  219. data/lib/ea/xmi/liquid_drops/operation_drop.rb +29 -0
  220. data/lib/ea/xmi/liquid_drops/package_drop.rb +108 -0
  221. data/lib/ea/xmi/liquid_drops/root_drop.rb +34 -0
  222. data/lib/ea/xmi/liquid_drops/source_target_drop.rb +43 -0
  223. data/lib/ea/xmi/lookup_service.rb +89 -0
  224. data/lib/ea/xmi/parser.rb +919 -0
  225. data/lib/ea/xmi.rb +35 -0
  226. data/lib/ea.rb +10 -1
  227. metadata +382 -9
@@ -0,0 +1,277 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Verification
6
+ # Compares matched element pairs to find differences
7
+ class ElementComparator
8
+ # Compare package properties
9
+ #
10
+ # @param xmi_pkg [Lutaml::Uml::Package] XMI package
11
+ # @param qea_pkg [Lutaml::Uml::Package] QEA package
12
+ # @return [Hash] Comparison result with :equal and :differences
13
+ def compare_packages(xmi_pkg, qea_pkg) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
14
+ differences = []
15
+
16
+ # Compare basic properties
17
+ unless names_equal?(xmi_pkg.name, qea_pkg.name)
18
+ differences << "Name: '#{xmi_pkg.name}' vs '#{qea_pkg.name}'"
19
+ end
20
+
21
+ # Compare collection counts
22
+ compare_collection_count(
23
+ xmi_pkg.classes, qea_pkg.classes, "classes", differences
24
+ )
25
+ compare_collection_count(
26
+ xmi_pkg.enums, qea_pkg.enums, "enums", differences
27
+ )
28
+ compare_collection_count(
29
+ xmi_pkg.data_types, qea_pkg.data_types, "data_types", differences
30
+ )
31
+ compare_collection_count(
32
+ xmi_pkg.packages, qea_pkg.packages, "packages", differences
33
+ )
34
+
35
+ {
36
+ equal: differences.empty?,
37
+ differences: differences,
38
+ }
39
+ end
40
+
41
+ # Compare class properties
42
+ #
43
+ # @param xmi_class [Lutaml::Uml::UmlClass] XMI class
44
+ # @param qea_class [Lutaml::Uml::UmlClass] QEA class
45
+ # @return [Hash] Comparison result with :equal and :differences
46
+ def compare_classes(xmi_class, qea_class) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
47
+ differences = []
48
+
49
+ # Compare name
50
+ unless names_equal?(xmi_class.name, qea_class.name)
51
+ differences << "Name: '#{xmi_class.name}' vs '#{qea_class.name}'"
52
+ end
53
+
54
+ # Compare is_abstract
55
+ if xmi_class.is_abstract != qea_class.is_abstract
56
+ differences << "is_abstract: #{xmi_class.is_abstract} " \
57
+ "vs #{qea_class.is_abstract}"
58
+ end
59
+
60
+ # Compare type
61
+ if normalize_value(xmi_class.type) != normalize_value(qea_class.type)
62
+ differences << "type: '#{xmi_class.type}' vs '#{qea_class.type}'"
63
+ end
64
+
65
+ # Compare modifier
66
+ if normalize_value(xmi_class.modifier) !=
67
+ normalize_value(qea_class.modifier)
68
+ differences << "modifier: '#{xmi_class.modifier}' " \
69
+ "vs '#{qea_class.modifier}'"
70
+ end
71
+
72
+ # Compare collection counts
73
+ compare_collection_count(
74
+ xmi_class.attributes, qea_class.attributes,
75
+ "attributes", differences
76
+ )
77
+ compare_collection_count(
78
+ xmi_class.operations, qea_class.operations,
79
+ "operations", differences
80
+ )
81
+
82
+ {
83
+ equal: differences.empty?,
84
+ differences: differences,
85
+ }
86
+ end
87
+
88
+ # Compare attribute properties
89
+ #
90
+ # @param xmi_attr [Lutaml::Uml::TopElementAttribute] XMI attribute
91
+ # @param qea_attr [Lutaml::Uml::TopElementAttribute] QEA attribute
92
+ # @return [Hash] Comparison result with :equal and :differences
93
+ def compare_attributes(xmi_attr, qea_attr) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
94
+ differences = []
95
+
96
+ # Compare name
97
+ unless names_equal?(xmi_attr.name, qea_attr.name)
98
+ differences << "Name: '#{xmi_attr.name}' vs '#{qea_attr.name}'"
99
+ end
100
+
101
+ # Compare type
102
+ if normalize_value(xmi_attr.type) !=
103
+ normalize_value(qea_attr.type)
104
+ differences << "type: '#{xmi_attr.type}' vs '#{qea_attr.type}'"
105
+ end
106
+
107
+ # Compare visibility
108
+ if normalize_value(xmi_attr.visibility) !=
109
+ normalize_value(qea_attr.visibility)
110
+ differences << "visibility: '#{xmi_attr.visibility}' " \
111
+ "vs '#{qea_attr.visibility}'"
112
+ end
113
+
114
+ # Compare cardinality if present
115
+ xmi_card = xmi_attr.cardinality
116
+ qea_card = qea_attr.cardinality
117
+ if xmi_card || qea_card
118
+ if xmi_card && qea_card
119
+ unless cardinalities_equal?(xmi_card, qea_card)
120
+ differences << "cardinality: #{format_cardinality(xmi_card)} " \
121
+ "vs #{format_cardinality(qea_card)}"
122
+ end
123
+ elsif xmi_card || qea_card
124
+ differences << "cardinality: #{format_cardinality(xmi_card)} " \
125
+ "vs #{format_cardinality(qea_card)}"
126
+ end
127
+ end
128
+
129
+ {
130
+ equal: differences.empty?,
131
+ differences: differences,
132
+ }
133
+ end
134
+
135
+ # Compare operation properties
136
+ #
137
+ # @param xmi_op [Lutaml::Uml::Operation] XMI operation
138
+ # @param qea_op [Lutaml::Uml::Operation] QEA operation
139
+ # @return [Hash] Comparison result with :equal and :differences
140
+ def compare_operations(xmi_op, qea_op) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
141
+ differences = []
142
+
143
+ # Compare name
144
+ unless names_equal?(xmi_op.name, qea_op.name)
145
+ differences << "Name: '#{xmi_op.name}' vs '#{qea_op.name}'"
146
+ end
147
+
148
+ # Compare return type
149
+ if normalize_value(xmi_op.return_type) !=
150
+ normalize_value(qea_op.return_type)
151
+ differences << "return_type: '#{xmi_op.return_type}' " \
152
+ "vs '#{qea_op.return_type}'"
153
+ end
154
+
155
+ # Compare visibility
156
+ if normalize_value(xmi_op.visibility) !=
157
+ normalize_value(qea_op.visibility)
158
+ differences << "visibility: '#{xmi_op.visibility}' " \
159
+ "vs '#{qea_op.visibility}'"
160
+ end
161
+
162
+ # Compare parameter counts
163
+ compare_collection_count(
164
+ xmi_op.parameters, qea_op.parameters, "parameters", differences
165
+ )
166
+
167
+ {
168
+ equal: differences.empty?,
169
+ differences: differences,
170
+ }
171
+ end
172
+
173
+ # Compare association properties
174
+ #
175
+ # @param xmi_assoc [Lutaml::Uml::Association] XMI association
176
+ # @param qea_assoc [Lutaml::Uml::Association] QEA association
177
+ # @return [Hash] Comparison result with :equal and :differences
178
+ def compare_associations(xmi_assoc, qea_assoc) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
179
+ differences = []
180
+
181
+ # Compare owner end
182
+ unless normalize_value(xmi_assoc.owner_end) ==
183
+ normalize_value(qea_assoc.owner_end)
184
+ differences << "owner_end: '#{xmi_assoc.owner_end}' " \
185
+ "vs '#{qea_assoc.owner_end}'"
186
+ end
187
+
188
+ # Compare member end
189
+ unless normalize_value(xmi_assoc.member_end) ==
190
+ normalize_value(qea_assoc.member_end)
191
+ differences << "member_end: '#{xmi_assoc.member_end}' " \
192
+ "vs '#{qea_assoc.member_end}'"
193
+ end
194
+
195
+ # Compare owner end cardinality
196
+ if xmi_assoc.owner_end_cardinality &&
197
+ qea_assoc.owner_end_cardinality &&
198
+ !cardinalities_equal?(
199
+ xmi_assoc.owner_end_cardinality,
200
+ qea_assoc.owner_end_cardinality,
201
+ )
202
+ differences << "owner_end_cardinality: " \
203
+ "#{format_cardinality(xmi_assoc
204
+ .owner_end_cardinality)} " \
205
+ "vs #{format_cardinality(qea_assoc
206
+ .owner_end_cardinality)}"
207
+ end
208
+
209
+ # Compare member end cardinality
210
+ if xmi_assoc.member_end_cardinality &&
211
+ qea_assoc.member_end_cardinality &&
212
+ !cardinalities_equal?(
213
+ xmi_assoc.member_end_cardinality,
214
+ qea_assoc.member_end_cardinality,
215
+ )
216
+ differences << "member_end_cardinality: " \
217
+ "#{format_cardinality(xmi_assoc
218
+ .member_end_cardinality)} " \
219
+ "vs #{format_cardinality(qea_assoc
220
+ .member_end_cardinality)}"
221
+ end
222
+
223
+ {
224
+ equal: differences.empty?,
225
+ differences: differences,
226
+ }
227
+ end
228
+
229
+ private
230
+
231
+ # Compare names with normalization
232
+ def names_equal?(name1, name2)
233
+ normalize_value(name1) == normalize_value(name2)
234
+ end
235
+
236
+ # Normalize value for comparison
237
+ def normalize_value(value)
238
+ return nil if value.nil?
239
+ return value unless value.is_a?(String)
240
+
241
+ value.strip
242
+ end
243
+
244
+ # Compare collection counts
245
+ def compare_collection_count(xmi_coll, qea_coll, name, differences) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
246
+ xmi_count = xmi_coll&.size || 0
247
+ qea_count = qea_coll&.size || 0
248
+
249
+ return if xmi_count == qea_count
250
+
251
+ suffix = if qea_count < xmi_count
252
+ "QEA has fewer"
253
+ else
254
+ "QEA has more (acceptable)"
255
+ end
256
+ differences << "#{name}: #{xmi_count} (XMI) vs " \
257
+ "#{qea_count} (QEA) - #{suffix}"
258
+ end
259
+
260
+ # Check if cardinalities are equal
261
+ def cardinalities_equal?(card1, card2)
262
+ return true if card1.nil? && card2.nil?
263
+ return false if card1.nil? || card2.nil?
264
+
265
+ card1.min == card2.min && card1.max == card2.max
266
+ end
267
+
268
+ # Format cardinality for display
269
+ def format_cardinality(card)
270
+ return "nil" if card.nil?
271
+
272
+ "\#{card.min}..\#{card.max}"
273
+ end
274
+ end
275
+ end
276
+ end
277
+ end
@@ -0,0 +1,287 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Verification
6
+ # Matches corresponding elements between XMI and QEA documents
7
+ # by qualified name/path
8
+ class StructureMatcher
9
+ # Match packages between documents
10
+ #
11
+ # @param xmi_doc [Lutaml::Uml::Document] XMI document
12
+ # @param qea_doc [Lutaml::Uml::Document] QEA document
13
+ # @return [Hash] Hash with :matches, :xmi_only, :qea_only
14
+ def match_packages(xmi_doc, qea_doc)
15
+ xmi_packages = build_package_index(xmi_doc.packages)
16
+ qea_packages = build_package_index(qea_doc.packages)
17
+
18
+ match_elements(xmi_packages, qea_packages)
19
+ end
20
+
21
+ # Match classes between documents
22
+ #
23
+ # @param xmi_doc [Lutaml::Uml::Document] XMI document
24
+ # @param qea_doc [Lutaml::Uml::Document] QEA document
25
+ # @return [Hash] Hash with :matches, :xmi_only, :qea_only
26
+ def match_classes(xmi_doc, qea_doc)
27
+ xmi_classes = build_class_index(xmi_doc)
28
+ qea_classes = build_class_index(qea_doc)
29
+
30
+ match_elements(xmi_classes, qea_classes)
31
+ end
32
+
33
+ # Match attributes between classes
34
+ #
35
+ # @param xmi_class [Lutaml::Uml::UmlClass] XMI class
36
+ # @param qea_class [Lutaml::Uml::UmlClass] QEA class
37
+ # @return [Hash] Hash with :matches, :xmi_only, :qea_only
38
+ def match_attributes(xmi_class, qea_class)
39
+ xmi_attrs = index_by_name(xmi_class.attributes || [])
40
+ qea_attrs = index_by_name(qea_class.attributes || [])
41
+
42
+ match_elements(xmi_attrs, qea_attrs)
43
+ end
44
+
45
+ # Match operations between classes
46
+ #
47
+ # @param xmi_class [Lutaml::Uml::UmlClass] XMI class
48
+ # @param qea_class [Lutaml::Uml::UmlClass] QEA class
49
+ # @return [Hash] Hash with :matches, :xmi_only, :qea_only
50
+ def match_operations(xmi_class, qea_class)
51
+ xmi_ops = index_by_signature(xmi_class.operations || [])
52
+ qea_ops = index_by_signature(qea_class.operations || [])
53
+
54
+ match_elements(xmi_ops, qea_ops)
55
+ end
56
+
57
+ # Build qualified name index for document
58
+ #
59
+ # @param document [Lutaml::Uml::Document] The document
60
+ # @return [Hash] Hash mapping qualified names to elements
61
+ def build_qualified_names(document)
62
+ {
63
+ packages: build_package_index(document.packages),
64
+ classes: build_class_index(document),
65
+ enums: build_enum_index(document),
66
+ data_types: build_data_type_index(document),
67
+ }
68
+ end
69
+
70
+ private
71
+
72
+ # Build package index with qualified paths
73
+ def build_package_index(packages, parent_path = "") # rubocop:disable Metrics/MethodLength
74
+ index = {}
75
+ return index unless packages
76
+
77
+ packages.each do |package|
78
+ next unless package.name
79
+
80
+ qualified_path = build_path(parent_path, package.name)
81
+ index[qualified_path] = package
82
+
83
+ # Recursively index nested packages
84
+ nested = build_package_index(package.packages, qualified_path)
85
+ index.merge!(nested)
86
+ end
87
+
88
+ index
89
+ end
90
+
91
+ # Build class index from document and packages
92
+ def build_class_index(document) # rubocop:disable Metrics/MethodLength
93
+ index = {}
94
+
95
+ # Index top-level classes
96
+ document.classes&.each do |klass|
97
+ next unless klass.name
98
+
99
+ index[klass.name] = klass
100
+ end
101
+
102
+ # Index classes in packages
103
+ if document.packages
104
+ index_classes_in_packages(document.packages, "",
105
+ index)
106
+ end
107
+
108
+ index
109
+ end
110
+
111
+ # Recursively index classes in packages
112
+ def index_classes_in_packages(packages, parent_path, index) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
113
+ return unless packages
114
+
115
+ packages.each do |package|
116
+ next unless package.name
117
+
118
+ package_path = build_path(parent_path, package.name)
119
+
120
+ package.classes&.each do |klass|
121
+ next unless klass.name
122
+
123
+ qualified_name = "#{package_path}::#{klass.name}"
124
+ index[qualified_name] = klass
125
+ end
126
+
127
+ # Recurse into nested packages
128
+ index_classes_in_packages(package.packages, package_path, index)
129
+ end
130
+ end
131
+
132
+ # Build enum index from document and packages
133
+ def build_enum_index(document) # rubocop:disable Metrics/MethodLength
134
+ index = {}
135
+
136
+ # Index top-level enums
137
+ document.enums&.each do |enum|
138
+ next unless enum.name
139
+
140
+ index[enum.name] = enum
141
+ end
142
+
143
+ # Index enums in packages
144
+ if document.packages
145
+ index_enums_in_packages(document.packages, "",
146
+ index)
147
+ end
148
+
149
+ index
150
+ end
151
+
152
+ # Recursively index enums in packages
153
+ def index_enums_in_packages(packages, parent_path, index) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
154
+ return unless packages
155
+
156
+ packages.each do |package|
157
+ next unless package.name
158
+
159
+ package_path = build_path(parent_path, package.name)
160
+
161
+ package.enums&.each do |enum|
162
+ next unless enum.name
163
+
164
+ qualified_name = "#{package_path}::#{enum.name}"
165
+ index[qualified_name] = enum
166
+ end
167
+
168
+ # Recurse into nested packages
169
+ index_enums_in_packages(package.packages, package_path, index)
170
+ end
171
+ end
172
+
173
+ # Build data type index from document and packages
174
+ def build_data_type_index(document) # rubocop:disable Metrics/MethodLength
175
+ index = {}
176
+
177
+ # Index top-level data types
178
+ document.data_types&.each do |dt|
179
+ next unless dt.name
180
+
181
+ index[dt.name] = dt
182
+ end
183
+
184
+ # Index data types in packages
185
+ if document.packages
186
+ index_data_types_in_packages(document.packages, "",
187
+ index)
188
+ end
189
+
190
+ index
191
+ end
192
+
193
+ # Recursively index data types in packages
194
+ def index_data_types_in_packages(packages, parent_path, index) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
195
+ return unless packages
196
+
197
+ packages.each do |package|
198
+ next unless package.name
199
+
200
+ package_path = build_path(parent_path, package.name)
201
+
202
+ package.data_types&.each do |dt|
203
+ next unless dt.name
204
+
205
+ qualified_name = "#{package_path}::#{dt.name}"
206
+ index[qualified_name] = dt
207
+ end
208
+
209
+ # Recurse into nested packages
210
+ index_data_types_in_packages(package.packages, package_path, index)
211
+ end
212
+ end
213
+
214
+ # Build qualified path from parent and name
215
+ def build_path(parent_path, name)
216
+ return name if parent_path.empty?
217
+
218
+ "#{parent_path}::#{name}"
219
+ end
220
+
221
+ # Index collection by name
222
+ def index_by_name(collection)
223
+ index = {}
224
+ collection.each do |element|
225
+ next unless element.name
226
+
227
+ index[element.name] = element
228
+ end
229
+ index
230
+ end
231
+
232
+ # Index operations by signature (name + parameter types)
233
+ def index_by_signature(operations)
234
+ index = {}
235
+ operations.each do |operation|
236
+ next unless operation.name
237
+
238
+ signature = build_operation_signature(operation)
239
+ index[signature] = operation
240
+ end
241
+ index
242
+ end
243
+
244
+ # Build operation signature for matching
245
+ def build_operation_signature(operation)
246
+ return operation.name unless operation.owned_parameter
247
+
248
+ param_types = operation.owned_parameter.map do |param|
249
+ param.type || "unknown"
250
+ end.join(",")
251
+
252
+ "#{operation.name}(#{param_types})"
253
+ end
254
+
255
+ # Match elements between two indexes
256
+ def match_elements(xmi_index, qea_index) # rubocop:disable Metrics/MethodLength
257
+ matches = {}
258
+ xmi_only = []
259
+ qea_only = []
260
+
261
+ # Find matches and XMI-only elements
262
+ xmi_index.each do |key, xmi_element|
263
+ if qea_index.key?(key)
264
+ matches[key] = {
265
+ xmi: xmi_element,
266
+ qea: qea_index[key],
267
+ }
268
+ else
269
+ xmi_only << key
270
+ end
271
+ end
272
+
273
+ # Find QEA-only elements
274
+ qea_index.each_key do |key|
275
+ qea_only << key unless xmi_index.key?(key)
276
+ end
277
+
278
+ {
279
+ matches: matches,
280
+ xmi_only: xmi_only.sort,
281
+ qea_only: qea_only.sort,
282
+ }
283
+ end
284
+ end
285
+ end
286
+ end
287
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Verification
6
+ autoload :DocumentNormalizer,
7
+ "ea/qea/verification/document_normalizer"
8
+ autoload :StructureMatcher, "ea/qea/verification/structure_matcher"
9
+ autoload :ElementComparator, "ea/qea/verification/element_comparator"
10
+ autoload :ComparisonResult, "ea/qea/verification/comparison_result"
11
+ autoload :DocumentVerifier, "ea/qea/verification/document_verifier"
12
+ end
13
+ end
14
+ end