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,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Verification
6
+ # Normalizes UML documents for comparison by removing XMI IDs,
7
+ # sorting collections, and normalizing strings
8
+ class DocumentNormalizer
9
+ # Normalize a document for comparison
10
+ #
11
+ # @param document [Lutaml::Uml::Document] The document to normalize
12
+ # @return [Lutaml::Uml::Document] A normalized copy
13
+ def normalize(document)
14
+ remove_xmi_ids(document)
15
+ sort_collections(document)
16
+ normalize_strings_in_document(document)
17
+ document
18
+ end
19
+
20
+ # Remove all XMI IDs from document
21
+ #
22
+ # @param document [Lutaml::Uml::Document] The document to process
23
+ # @return [void]
24
+ def remove_xmi_ids(document) # rubocop:disable Metrics/AbcSize
25
+ # Remove XMI IDs from packages recursively
26
+ process_packages(document.packages) if document.packages
27
+
28
+ # Remove XMI IDs from classes
29
+ process_classes(document.classes) if document.classes
30
+
31
+ # Remove XMI IDs from associations
32
+ process_associations(document.associations) if document.associations
33
+
34
+ # Remove XMI IDs from enums
35
+ process_enums(document.enums) if document.enums
36
+
37
+ # Remove XMI IDs from data types
38
+ process_data_types(document.data_types) if document.data_types
39
+ end
40
+
41
+ # Sort collections in document for consistent comparison
42
+ #
43
+ # @param document [Lutaml::Uml::Document] The document to process
44
+ # @return [void]
45
+ def sort_collections(document) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
46
+ # Sort top-level collections by name
47
+ document.packages&.sort_by! { |p| p.name || "" }
48
+ document.classes&.sort_by! { |c| c.name || "" }
49
+ document.enums&.sort_by! { |e| e.name || "" }
50
+ document.data_types&.sort_by! { |dt| dt.name || "" }
51
+ document.associations&.sort_by! do |a|
52
+ "#{a.owner_end}#{a.member_end}"
53
+ end
54
+
55
+ # Sort nested collections recursively
56
+ sort_package_collections(document.packages) if document.packages
57
+ sort_class_collections(document.classes) if document.classes
58
+ end
59
+
60
+ # Normalize strings (trim whitespace, normalize case for comparison)
61
+ #
62
+ # @param text [String, nil] The text to normalize
63
+ # @return [String, nil] Normalized text
64
+ def normalize_string(text)
65
+ return nil if text.nil?
66
+ return text unless text.is_a?(String)
67
+
68
+ text.strip
69
+ end
70
+
71
+ private
72
+
73
+ # Process packages recursively to remove XMI IDs
74
+ def process_packages(packages) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity
75
+ packages.each do |package|
76
+ package.xmi_id = nil
77
+ process_classes(package.classes) if package.classes
78
+ process_enums(package.enums) if package.enums
79
+ process_data_types(package.data_types) if package.data_types
80
+ process_packages(package.packages) if package.packages
81
+ end
82
+ end
83
+
84
+ # Process classes to remove XMI IDs
85
+ def process_classes(classes) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
86
+ classes.each do |klass|
87
+ klass.xmi_id = nil
88
+
89
+ # Remove XMI IDs from attributes
90
+ klass.attributes&.each do |attr|
91
+ attr.xmi_id = nil
92
+ end
93
+
94
+ # Remove XMI IDs from operations
95
+ klass.operations&.each do |op|
96
+ op.xmi_id = nil
97
+ op.parameters&.each do |param|
98
+ param.xmi_id = nil if param.class.attributes.key?(:xmi_id)
99
+ end
100
+ end
101
+
102
+ # Process nested classes
103
+ if klass.is_a?(Lutaml::Uml::Package) && klass.classes
104
+ process_classes(klass.classes)
105
+ end
106
+ end
107
+ end
108
+
109
+ # Process associations to remove XMI IDs
110
+ def process_associations(associations)
111
+ associations.each do |assoc|
112
+ assoc.xmi_id = nil
113
+ if assoc.owner_end_xmi_id
114
+ assoc.owner_end_xmi_id = nil
115
+ end
116
+ if assoc.member_end_xmi_id
117
+ assoc.member_end_xmi_id = nil
118
+ end
119
+ end
120
+ end
121
+
122
+ # Process enums to remove XMI IDs
123
+ def process_enums(enums)
124
+ enums.each do |enum|
125
+ enum.xmi_id = nil
126
+ end
127
+ end
128
+
129
+ # Process data types to remove XMI IDs
130
+ def process_data_types(data_types)
131
+ data_types.each do |dt|
132
+ dt.xmi_id = nil
133
+ end
134
+ end
135
+
136
+ # Sort collections within packages
137
+ def sort_package_collections(packages) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
138
+ packages.each do |package|
139
+ package.classes&.sort_by! { |c| c.name || "" }
140
+ package.enums&.sort_by! { |e| e.name || "" }
141
+ package.data_types&.sort_by! { |dt| dt.name || "" }
142
+ package.packages&.sort_by! { |p| p.name || "" }
143
+
144
+ sort_class_collections(package.classes) if package.classes
145
+ sort_package_collections(package.packages) if package.packages
146
+ end
147
+ end
148
+
149
+ # Sort collections within classes
150
+ def sort_class_collections(classes) # rubocop:disable Metrics/CyclomaticComplexity
151
+ classes.each do |klass|
152
+ klass.attributes&.sort_by! { |a| a.name || "" }
153
+ klass.operations&.sort_by! { |o| o.name || "" }
154
+ klass.associations&.sort_by! do |a|
155
+ "#{a.owner_end}#{a.member_end}"
156
+ end
157
+ end
158
+ end
159
+
160
+ # Normalize strings throughout document
161
+ def normalize_strings_in_document(document)
162
+ # This is a placeholder for string normalization
163
+ # Can be extended to normalize strings throughout the document
164
+ # For now, normalization happens during comparison
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,322 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Verification
6
+ # Main orchestrator for document verification
7
+ # Verifies that QEA-parsed documents contain at least as much
8
+ # information as XMI-parsed equivalents
9
+ class DocumentVerifier
10
+ attr_reader :normalizer, :matcher, :comparator, :result
11
+
12
+ def initialize
13
+ @normalizer = DocumentNormalizer.new
14
+ @matcher = StructureMatcher.new
15
+ @comparator = ElementComparator.new
16
+ @result = ComparisonResult.new
17
+ end
18
+
19
+ # Main verification method
20
+ #
21
+ # @param xmi_path [String] Path to XMI file
22
+ # @param qea_path [String] Path to QEA file
23
+ # @return [ComparisonResult] Verification result
24
+ def verify(xmi_path, qea_path) # rubocop:disable Metrics/MethodLength
25
+ # Parse documents
26
+ xmi_doc = parse_xmi(xmi_path)
27
+ qea_doc = parse_qea(qea_path)
28
+
29
+ # Normalize documents
30
+ xmi_normalized = normalizer.normalize(xmi_doc)
31
+ qea_normalized = normalizer.normalize(qea_doc)
32
+
33
+ # Perform verification
34
+ verify_structure(xmi_normalized, qea_normalized)
35
+ verify_names(xmi_normalized, qea_normalized)
36
+ verify_properties(xmi_normalized, qea_normalized)
37
+ verify_relationships(xmi_normalized, qea_normalized)
38
+
39
+ result
40
+ end
41
+
42
+ # Verify document with already loaded documents
43
+ #
44
+ # @param xmi_doc [Lutaml::Uml::Document] XMI document
45
+ # @param qea_doc [Lutaml::Uml::Document] QEA document
46
+ # @return [ComparisonResult] Verification result
47
+ def verify_documents(xmi_doc, qea_doc)
48
+ # Normalize documents
49
+ xmi_normalized = normalizer.normalize(xmi_doc)
50
+ qea_normalized = normalizer.normalize(qea_doc)
51
+
52
+ # Perform verification
53
+ verify_structure(xmi_normalized, qea_normalized)
54
+ verify_names(xmi_normalized, qea_normalized)
55
+ verify_properties(xmi_normalized, qea_normalized)
56
+ verify_relationships(xmi_normalized, qea_normalized)
57
+
58
+ result
59
+ end
60
+
61
+ # Reset cached match results (call between verifications)
62
+ def reset_cache
63
+ @cached_class_matches = nil
64
+ @cached_package_matches = nil
65
+ end
66
+
67
+ # Compare element counts
68
+ #
69
+ # @param xmi_doc [Lutaml::Uml::Document] XMI document
70
+ # @param qea_doc [Lutaml::Uml::Document] QEA document
71
+ # @return [void]
72
+ def verify_structure(xmi_doc, qea_doc) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
73
+ # Compare package counts (cache for reuse in verify_properties)
74
+ @cached_package_matches = matcher.match_packages(xmi_doc, qea_doc)
75
+ result.add_matches(:packages, @cached_package_matches[:matches].size)
76
+ result.add_xmi_only(:packages, @cached_package_matches[:xmi_only])
77
+ result.add_qea_only(:packages, @cached_package_matches[:qea_only])
78
+
79
+ # Compare class counts (cache for reuse in verify_properties)
80
+ @cached_class_matches = matcher.match_classes(xmi_doc, qea_doc)
81
+ result.add_matches(:classes, @cached_class_matches[:matches].size)
82
+ result.add_xmi_only(:classes, @cached_class_matches[:xmi_only])
83
+ result.add_qea_only(:classes, @cached_class_matches[:qea_only])
84
+
85
+ # Compare enum counts
86
+ xmi_enums = count_all_enums(xmi_doc)
87
+ qea_enums = count_all_enums(qea_doc)
88
+ if qea_enums < xmi_enums
89
+ result.add_difference(
90
+ "Enums: #{xmi_enums} (XMI) vs #{qea_enums} (QEA) - QEA has fewer",
91
+ )
92
+ end
93
+
94
+ # Compare data type counts
95
+ xmi_dt = count_all_data_types(xmi_doc)
96
+ qea_dt = count_all_data_types(qea_doc)
97
+ if qea_dt < xmi_dt
98
+ result.add_difference(
99
+ "Data types: #{xmi_dt} (XMI) vs #{qea_dt} (QEA) - QEA has fewer",
100
+ )
101
+ end
102
+
103
+ # Compare association counts
104
+ xmi_assocs = count_all_associations(xmi_doc)
105
+ qea_assocs = count_all_associations(qea_doc)
106
+ result.add_matches(:associations, [xmi_assocs, qea_assocs].min)
107
+ if qea_assocs < xmi_assocs
108
+ result.add_difference(
109
+ "Associations: #{xmi_assocs} (XMI) " \
110
+ "vs #{qea_assocs} (QEA) - QEA has fewer",
111
+ )
112
+ end
113
+ end
114
+
115
+ # Verify element names are preserved
116
+ #
117
+ # @param xmi_doc [Lutaml::Uml::Document] XMI document
118
+ # @param qea_doc [Lutaml::Uml::Document] QEA document
119
+ # @return [void]
120
+ def verify_names(xmi_doc, qea_doc)
121
+ # Package names verified in match_packages
122
+ # Class names verified in match_classes
123
+ # Additional verification could be added here
124
+ end
125
+
126
+ # Verify properties of matched elements
127
+ #
128
+ # @param xmi_doc [Lutaml::Uml::Document] XMI document
129
+ # @param qea_doc [Lutaml::Uml::Document] QEA document
130
+ # @return [void]
131
+ def verify_properties(xmi_doc, qea_doc)
132
+ # Verify class properties (reuse cached matches from verify_structure)
133
+ class_matches = @cached_class_matches || matcher.match_classes(
134
+ xmi_doc, qea_doc
135
+ )
136
+ verify_class_properties(class_matches[:matches])
137
+
138
+ # Verify package properties (reuse cached matches from verify_structure)
139
+ package_matches = @cached_package_matches || matcher.match_packages(
140
+ xmi_doc, qea_doc
141
+ )
142
+ verify_package_properties(package_matches[:matches])
143
+ end
144
+
145
+ # Verify relationships (associations, generalizations)
146
+ #
147
+ # @param xmi_doc [Lutaml::Uml::Document] XMI document
148
+ # @param qea_doc [Lutaml::Uml::Document] QEA document
149
+ # @return [void]
150
+ def verify_relationships(xmi_doc, qea_doc)
151
+ # Verify associations are preserved
152
+ verify_associations(xmi_doc, qea_doc)
153
+ end
154
+
155
+ private
156
+
157
+ # Parse XMI file
158
+ def parse_xmi(xmi_path)
159
+ Ea::Xmi::Parser.parse(File.new(xmi_path))
160
+ end
161
+
162
+ # Parse QEA file
163
+ def parse_qea(qea_path)
164
+ Ea::Qea.parse(qea_path)
165
+ end
166
+
167
+ # Count all enums in document including nested
168
+ def count_all_enums(document)
169
+ count = document.enums&.size || 0
170
+ count + count_enums_in_packages(document.packages)
171
+ end
172
+
173
+ # Count enums in packages recursively
174
+ def count_enums_in_packages(packages)
175
+ return 0 unless packages
176
+
177
+ count = 0
178
+ packages.each do |package|
179
+ count += package.enums&.size || 0
180
+ count += count_enums_in_packages(package.packages)
181
+ end
182
+ count
183
+ end
184
+
185
+ # Count all data types in document including nested
186
+ def count_all_data_types(document)
187
+ count = document.data_types&.size || 0
188
+ count + count_data_types_in_packages(document.packages)
189
+ end
190
+
191
+ # Count data types in packages recursively
192
+ def count_data_types_in_packages(packages)
193
+ return 0 unless packages
194
+
195
+ count = 0
196
+ packages.each do |package|
197
+ count += package.data_types&.size || 0
198
+ count += count_data_types_in_packages(package.packages)
199
+ end
200
+ count
201
+ end
202
+
203
+ # Count all associations in document including nested
204
+ def count_all_associations(document)
205
+ count = document.associations&.size || 0
206
+ count + count_associations_in_classes(document.classes) + count_associations_in_packages(document.packages)
207
+ end
208
+
209
+ # Count associations in classes
210
+ def count_associations_in_classes(classes)
211
+ return 0 unless classes
212
+
213
+ count = 0
214
+ classes.each do |klass|
215
+ count += klass.associations&.size || 0
216
+ end
217
+ count
218
+ end
219
+
220
+ # Count associations in packages recursively
221
+ def count_associations_in_packages(packages)
222
+ return 0 unless packages
223
+
224
+ count = 0
225
+ packages.each do |package|
226
+ count += count_associations_in_classes(package.classes)
227
+ count += count_associations_in_packages(package.packages)
228
+ end
229
+ count
230
+ end
231
+
232
+ # Verify class properties
233
+ def verify_class_properties(matches) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
234
+ attr_count = 0
235
+ op_count = 0
236
+
237
+ matches.each do |qualified_name, pair| # rubocop:disable Metrics/BlockLength
238
+ xmi_class = pair[:xmi]
239
+ qea_class = pair[:qea]
240
+
241
+ # Compare class properties
242
+ comparison = comparator.compare_classes(xmi_class, qea_class)
243
+ unless comparison[:equal]
244
+ result.add_property_difference(
245
+ :class,
246
+ qualified_name,
247
+ comparison[:differences],
248
+ )
249
+ end
250
+
251
+ # Compare attributes
252
+ attr_matches = matcher.match_attributes(xmi_class, qea_class)
253
+ attr_count += attr_matches[:matches].size
254
+
255
+ attr_matches[:matches].each do |attr_name, attr_pair|
256
+ attr_comparison = comparator.compare_attributes(
257
+ attr_pair[:xmi],
258
+ attr_pair[:qea],
259
+ )
260
+ unless attr_comparison[:equal]
261
+ result.add_property_difference(
262
+ :attribute,
263
+ "#{qualified_name}.#{attr_name}",
264
+ attr_comparison[:differences],
265
+ )
266
+ end
267
+ end
268
+
269
+ # Compare operations
270
+ op_matches = matcher.match_operations(xmi_class, qea_class)
271
+ op_count += op_matches[:matches].size
272
+
273
+ op_matches[:matches].each do |op_sig, op_pair|
274
+ op_comparison = comparator.compare_operations(
275
+ op_pair[:xmi],
276
+ op_pair[:qea],
277
+ )
278
+ unless op_comparison[:equal]
279
+ result.add_property_difference(
280
+ :operation,
281
+ "#{qualified_name}.#{op_sig}",
282
+ op_comparison[:differences],
283
+ )
284
+ end
285
+ end
286
+ end
287
+
288
+ result.add_matches(:attributes, attr_count)
289
+ result.add_matches(:operations, op_count)
290
+ end
291
+
292
+ # Verify package properties
293
+ def verify_package_properties(matches)
294
+ matches.each do |qualified_path, pair|
295
+ comparison = comparator.compare_packages(pair[:xmi], pair[:qea])
296
+ unless comparison[:equal]
297
+ result.add_property_difference(
298
+ :package,
299
+ qualified_path,
300
+ comparison[:differences],
301
+ )
302
+ end
303
+ end
304
+ end
305
+
306
+ # Verify associations
307
+ def verify_associations(xmi_doc, qea_doc)
308
+ # Simple count-based verification for now
309
+ # Could be enhanced to match associations by endpoints
310
+ xmi_assocs = count_all_associations(xmi_doc)
311
+ qea_assocs = count_all_associations(qea_doc)
312
+
313
+ if qea_assocs < xmi_assocs
314
+ result.add_difference(
315
+ "Missing #{xmi_assocs - qea_assocs} associations in QEA",
316
+ )
317
+ end
318
+ end
319
+ end
320
+ end
321
+ end
322
+ end