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,210 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Validation
6
+ # Collects and organizes validation messages, providing summary
7
+ # statistics and filtering capabilities
8
+ #
9
+ # @example Basic usage
10
+ # result = ValidationResult.new
11
+ # result.add_error(
12
+ # category: :missing_reference,
13
+ # entity_type: :association,
14
+ # entity_id: "123",
15
+ # entity_name: "MyAssociation",
16
+ # message: "member_end references non-existent class"
17
+ # )
18
+ # puts result.summary
19
+ class ValidationResult
20
+ attr_reader :messages
21
+
22
+ def initialize
23
+ @messages = []
24
+ end
25
+
26
+ # Adds an error message to the result
27
+ #
28
+ # @param args [Hash] Message attributes (see ValidationMessage)
29
+ # @return [ValidationMessage] The created message
30
+ def add_error(**args)
31
+ add_message(severity: :error, **args)
32
+ end
33
+
34
+ # Adds a warning message to the result
35
+ #
36
+ # @param args [Hash] Message attributes (see ValidationMessage)
37
+ # @return [ValidationMessage] The created message
38
+ def add_warning(**args)
39
+ add_message(severity: :warning, **args)
40
+ end
41
+
42
+ # Adds an info message to the result
43
+ #
44
+ # @param args [Hash] Message attributes (see ValidationMessage)
45
+ # @return [ValidationMessage] The created message
46
+ def add_info(**args)
47
+ add_message(severity: :info, **args)
48
+ end
49
+
50
+ # Adds a message to the result
51
+ #
52
+ # @param args [Hash] Message attributes (see ValidationMessage)
53
+ # @return [ValidationMessage] The created message
54
+ def add_message(**args)
55
+ message = ValidationMessage.new(**args)
56
+ @messages << message
57
+ message
58
+ end
59
+
60
+ # Checks if there are any error messages
61
+ #
62
+ # @return [Boolean]
63
+ def has_errors?
64
+ @messages.any?(&:error?)
65
+ end
66
+
67
+ # Checks if there are any warning messages
68
+ #
69
+ # @return [Boolean]
70
+ def has_warnings?
71
+ @messages.any?(&:warning?)
72
+ end
73
+
74
+ # Checks if there are any info messages
75
+ #
76
+ # @return [Boolean]
77
+ def has_info?
78
+ @messages.any?(&:info?)
79
+ end
80
+
81
+ # Checks if the result is valid (no errors)
82
+ #
83
+ # @return [Boolean]
84
+ def valid?
85
+ !has_errors?
86
+ end
87
+
88
+ # Returns all error messages
89
+ #
90
+ # @return [Array<ValidationMessage>]
91
+ def errors
92
+ @messages.select(&:error?)
93
+ end
94
+
95
+ # Returns all warning messages
96
+ #
97
+ # @return [Array<ValidationMessage>]
98
+ def warnings
99
+ @messages.select(&:warning?)
100
+ end
101
+
102
+ # Returns all info messages
103
+ #
104
+ # @return [Array<ValidationMessage>]
105
+ def info
106
+ @messages.select(&:info?)
107
+ end
108
+
109
+ # Returns messages filtered by severity
110
+ #
111
+ # @param severity [Symbol] The severity to filter by
112
+ # @return [Array<ValidationMessage>]
113
+ def by_severity(severity)
114
+ @messages.select { |m| m.severity == severity }
115
+ end
116
+
117
+ # Returns messages filtered by category
118
+ #
119
+ # @param category [Symbol] The category to filter by
120
+ # @return [Array<ValidationMessage>]
121
+ def by_category(category)
122
+ @messages.select { |m| m.category == category }
123
+ end
124
+
125
+ # Returns messages filtered by entity type
126
+ #
127
+ # @param entity_type [Symbol] The entity type to filter by
128
+ # @return [Array<ValidationMessage>]
129
+ def by_entity_type(entity_type)
130
+ @messages.select { |m| m.entity_type == entity_type }
131
+ end
132
+
133
+ # Returns messages grouped by category
134
+ #
135
+ # @return [Hash<Symbol, Array<ValidationMessage>>]
136
+ def grouped_by_category
137
+ @messages.group_by(&:category)
138
+ end
139
+
140
+ # Returns messages grouped by severity
141
+ #
142
+ # @return [Hash<Symbol, Array<ValidationMessage>>]
143
+ def grouped_by_severity
144
+ @messages.group_by(&:severity)
145
+ end
146
+
147
+ # Returns messages grouped by entity type
148
+ #
149
+ # @return [Hash<Symbol, Array<ValidationMessage>>]
150
+ def grouped_by_entity_type
151
+ @messages.group_by(&:entity_type)
152
+ end
153
+
154
+ # Returns summary statistics
155
+ #
156
+ # @return [Hash]
157
+ def statistics
158
+ {
159
+ total: @messages.size,
160
+ errors: errors.size,
161
+ warnings: warnings.size,
162
+ info: info.size,
163
+ by_category: grouped_by_category.transform_values(&:size),
164
+ by_entity_type: grouped_by_entity_type.transform_values(&:size),
165
+ }
166
+ end
167
+
168
+ # Returns a summary string
169
+ #
170
+ # @return [String]
171
+ def summary
172
+ stats = statistics
173
+ [
174
+ "Total Messages: #{stats[:total]}",
175
+ "Errors: #{stats[:errors]}",
176
+ "Warnings: #{stats[:warnings]}",
177
+ "Info: #{stats[:info]}",
178
+ ].join("\n")
179
+ end
180
+
181
+ # Merges another result into this one
182
+ #
183
+ # @param other [ValidationResult] The result to merge
184
+ # @return [self]
185
+ def merge!(other)
186
+ @messages.concat(other.messages)
187
+ self
188
+ end
189
+
190
+ # Returns a hash representation
191
+ #
192
+ # @return [Hash]
193
+ def to_h
194
+ {
195
+ statistics: statistics,
196
+ messages: @messages.map(&:to_h),
197
+ }
198
+ end
199
+
200
+ # Returns a JSON representation
201
+ #
202
+ # @return [String]
203
+ def to_json(*)
204
+ require "json"
205
+ to_h.to_json(*)
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Validation
6
+ # Registry for managing validators using the registry pattern
7
+ # Allows dynamic registration and retrieval of validators
8
+ #
9
+ # @example Registering and using validators
10
+ # registry = ValidatorRegistry.new
11
+ # registry.register(:package, PackageValidator)
12
+ # registry.register(:class, ClassValidator)
13
+ #
14
+ # validator = registry.get(:package)
15
+ # result = validator.new(context).validate
16
+ class ValidatorRegistry
17
+ def initialize
18
+ @validators = {}
19
+ end
20
+
21
+ # Registers a validator class
22
+ #
23
+ # @param name [Symbol] Validator name/key
24
+ # @param validator_class [Class] Validator class (must inherit from
25
+ # BaseValidator)
26
+ # @raise [ArgumentError] if validator_class is not a BaseValidator
27
+ # @return [void]
28
+ def register(name, validator_class)
29
+ unless validator_class.is_a?(Class)
30
+ raise ArgumentError,
31
+ "Expected a Class, got #{validator_class.class}"
32
+ end
33
+
34
+ @validators[name] = validator_class
35
+ end
36
+
37
+ # Retrieves a validator class by name
38
+ #
39
+ # @param name [Symbol] Validator name/key
40
+ # @return [Class, nil] Validator class or nil if not found
41
+ def get(name)
42
+ @validators[name]
43
+ end
44
+
45
+ # Retrieves a validator class by name, raising an error if not found
46
+ #
47
+ # @param name [Symbol] Validator name/key
48
+ # @return [Class]
49
+ # @raise [KeyError] if validator not found
50
+ def fetch(name)
51
+ @validators.fetch(name) do
52
+ raise KeyError, "Validator '#{name}' not registered"
53
+ end
54
+ end
55
+
56
+ # Checks if a validator is registered
57
+ #
58
+ # @param name [Symbol] Validator name/key
59
+ # @return [Boolean]
60
+ def registered?(name)
61
+ @validators.key?(name)
62
+ end
63
+
64
+ # Returns all registered validator names
65
+ #
66
+ # @return [Array<Symbol>]
67
+ def names
68
+ @validators.keys
69
+ end
70
+
71
+ # Returns all registered validators
72
+ #
73
+ # @return [Hash<Symbol, Class>]
74
+ def all
75
+ @validators.dup
76
+ end
77
+
78
+ # Unregisters a validator
79
+ #
80
+ # @param name [Symbol] Validator name/key
81
+ # @return [Class, nil] The unregistered validator class
82
+ def unregister(name)
83
+ @validators.delete(name)
84
+ end
85
+
86
+ # Clears all registered validators
87
+ #
88
+ # @return [void]
89
+ def clear
90
+ @validators.clear
91
+ end
92
+
93
+ # Creates a validator instance
94
+ #
95
+ # @param name [Symbol] Validator name/key
96
+ # @param context [Hash] Validation context (must include :result key)
97
+ # @return [BaseValidator] Validator instance
98
+ # @raise [KeyError] if validator not found
99
+ def create(name, context = {})
100
+ result = context[:result] || ValidationResult.new
101
+ fetch(name).new(result: result, context: context)
102
+ end
103
+
104
+ # Runs a validator and returns the result
105
+ #
106
+ # @param name [Symbol] Validator name/key
107
+ # @param context [Hash] Validation context (must include :result key)
108
+ # @return [ValidationResult]
109
+ # @raise [KeyError] if validator not found
110
+ def validate(name, context = {})
111
+ validator = create(name, context)
112
+ validator.call
113
+ validator.result
114
+ end
115
+
116
+ # Runs multiple validators and merges their results
117
+ #
118
+ # @param names [Array<Symbol>] Validator names/keys
119
+ # @param context [Hash] Validation context
120
+ # @return [ValidationResult] Merged validation result
121
+ def validate_all(names, context = {})
122
+ result = context[:result] || ValidationResult.new
123
+ context[:result] = result
124
+
125
+ names.each do |name|
126
+ validate(name, context)
127
+ end
128
+
129
+ result
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Validation
6
+ autoload :ValidationMessage, "ea/qea/validation/validation_message"
7
+ autoload :ValidationResult, "ea/qea/validation/validation_result"
8
+ autoload :BaseValidator, "ea/qea/validation/base_validator"
9
+ autoload :ValidatorRegistry, "ea/qea/validation/validator_registry"
10
+ autoload :AssociationValidator,
11
+ "ea/qea/validation/association_validator"
12
+ autoload :AttributeValidator, "ea/qea/validation/attribute_validator"
13
+ autoload :ClassValidator, "ea/qea/validation/class_validator"
14
+ autoload :DiagramValidator, "ea/qea/validation/diagram_validator"
15
+ autoload :OperationValidator, "ea/qea/validation/operation_validator"
16
+ autoload :PackageValidator, "ea/qea/validation/package_validator"
17
+ autoload :ReferentialIntegrityValidator,
18
+ "ea/qea/validation/database/referential_integrity_validator"
19
+ autoload :OrphanValidator,
20
+ "ea/qea/validation/database/orphan_validator"
21
+ autoload :CircularReferenceValidator,
22
+ "ea/qea/validation/database/circular_reference_validator"
23
+ autoload :Database, "ea/qea/validation/database"
24
+ autoload :Formatters, "ea/qea/validation/formatters"
25
+ autoload :ValidationEngine, "ea/qea/validation/validation_engine"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,264 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Verification
6
+ # Holds comparison results between XMI and QEA documents
7
+ class ComparisonResult
8
+ attr_reader :matches, :differences, :xmi_only, :qea_only,
9
+ :property_differences
10
+
11
+ def initialize # rubocop:disable Metrics/MethodLength
12
+ @matches = {
13
+ packages: 0,
14
+ classes: 0,
15
+ enums: 0,
16
+ data_types: 0,
17
+ associations: 0,
18
+ attributes: 0,
19
+ operations: 0,
20
+ }
21
+ @differences = []
22
+ @xmi_only = {
23
+ packages: [],
24
+ classes: [],
25
+ enums: [],
26
+ data_types: [],
27
+ associations: [],
28
+ }
29
+ @qea_only = {
30
+ packages: [],
31
+ classes: [],
32
+ enums: [],
33
+ data_types: [],
34
+ associations: [],
35
+ }
36
+ @property_differences = []
37
+ end
38
+
39
+ # Record matched elements
40
+ #
41
+ # @param type [Symbol] Element type (:packages, :classes, etc.)
42
+ # @param count [Integer] Number of matches
43
+ def add_matches(type, count)
44
+ @matches[type] = count
45
+ end
46
+
47
+ # Record XMI-only elements
48
+ #
49
+ # @param type [Symbol] Element type
50
+ # @param elements [Array<String>] Element names/paths
51
+ def add_xmi_only(type, elements)
52
+ @xmi_only[type] = elements
53
+ end
54
+
55
+ # Record QEA-only elements
56
+ #
57
+ # @param type [Symbol] Element type
58
+ # @param elements [Array<String>] Element names/paths
59
+ def add_qea_only(type, elements)
60
+ @qea_only[type] = elements
61
+ end
62
+
63
+ # Record a difference
64
+ #
65
+ # @param description [String] Difference description
66
+ def add_difference(description)
67
+ @differences << description
68
+ end
69
+
70
+ # Record property-level difference
71
+ #
72
+ # @param element_type [Symbol] Element type
73
+ # @param element_name [String] Element name
74
+ # @param differences [Array<String>] List of property differences
75
+ def add_property_difference(element_type, element_name, differences)
76
+ @property_differences << {
77
+ type: element_type,
78
+ name: element_name,
79
+ differences: differences,
80
+ }
81
+ end
82
+
83
+ # Check if documents are equivalent
84
+ # QEA is considered equivalent if it has >= information compared to XMI
85
+ #
86
+ # @return [Boolean] True if equivalent
87
+ def equivalent?
88
+ # No critical XMI-only elements (some are acceptable)
89
+ critical_xmi_only = xmi_only[:classes].any? ||
90
+ xmi_only[:packages].any?
91
+
92
+ # No property differences that indicate information loss
93
+ critical_property_diffs = property_differences.any? do |diff|
94
+ diff[:differences].any? { |d| d.include?("QEA has fewer") }
95
+ end
96
+
97
+ !critical_xmi_only && !critical_property_diffs
98
+ end
99
+
100
+ # Generate human-readable summary
101
+ #
102
+ # @return [String] Summary text
103
+ def summary # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
104
+ lines = []
105
+ lines << "=== Verification Summary ==="
106
+ lines << ""
107
+
108
+ # Matches
109
+ lines << "Matched Elements:"
110
+ matches.each do |type, count|
111
+ next if count.zero?
112
+
113
+ lines << " ✓ #{type.to_s.capitalize}: #{count}"
114
+ end
115
+ lines << ""
116
+
117
+ # XMI-only elements
118
+ if xmi_only.values.any?(&:any?)
119
+ lines << "XMI-Only Elements (missing in QEA):"
120
+ xmi_only.each do |type, elements|
121
+ next if elements.empty?
122
+
123
+ lines << " ✗ #{type.to_s.capitalize}: #{elements.size}"
124
+ elements.first(5).each do |elem|
125
+ lines << " - #{elem}"
126
+ end
127
+ if elements.size > 5
128
+ lines << " ... and #{elements.size - 5} more"
129
+ end
130
+ end
131
+ lines << ""
132
+ end
133
+
134
+ # QEA-only elements (acceptable, shows QEA richness)
135
+ if qea_only.values.any?(&:any?)
136
+ lines << "QEA-Only Elements (additional in QEA - acceptable):"
137
+ qea_only.each do |type, elements|
138
+ next if elements.empty?
139
+
140
+ lines << " + #{type.to_s.capitalize}: #{elements.size}"
141
+ end
142
+ lines << ""
143
+ end
144
+
145
+ # Property differences
146
+ if property_differences.any?
147
+ lines << "Property Differences:"
148
+ property_differences.first(10).each do |diff|
149
+ lines << " #{diff[:type]}: #{diff[:name]}"
150
+ diff[:differences].each do |d|
151
+ lines << " - #{d}"
152
+ end
153
+ end
154
+ if property_differences.size > 10
155
+ lines << " ... and #{property_differences.size - 10} more"
156
+ end
157
+ lines << ""
158
+ end
159
+
160
+ # Result
161
+ lines << "Result: " \
162
+ "#{equivalent? ? '✓ EQUIVALENT' : '✗ NOT EQUIVALENT'}"
163
+ lines << ""
164
+
165
+ lines << if equivalent?
166
+ "QEA contains all XMI information (possibly more)."
167
+ else
168
+ "Information loss detected - " \
169
+ "QEA missing critical elements."
170
+ end
171
+
172
+ lines.join("\n")
173
+ end
174
+
175
+ # Generate detailed report
176
+ #
177
+ # @return [Hash] Detailed report data
178
+ def to_report
179
+ {
180
+ equivalent: equivalent?,
181
+ matches: matches,
182
+ xmi_only: xmi_only,
183
+ qea_only: qea_only,
184
+ property_differences: property_differences,
185
+ summary: summary,
186
+ }
187
+ end
188
+
189
+ # Generate statistics
190
+ #
191
+ # @return [Hash] Statistics about the comparison
192
+ def statistics
193
+ {
194
+ total_matches: matches.values.sum,
195
+ total_xmi_only: xmi_only.values.sum(&:size),
196
+ total_qea_only: qea_only.values.sum(&:size),
197
+ total_property_diffs: property_differences.size,
198
+ equivalent: equivalent?,
199
+ }
200
+ end
201
+
202
+ # Check if there are any issues
203
+ #
204
+ # @return [Boolean] True if there are issues
205
+ def has_issues?
206
+ !equivalent?
207
+ end
208
+
209
+ # Get critical issues (information loss)
210
+ #
211
+ # @return [Array<String>] List of critical issues
212
+ def critical_issues # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
213
+ issues = []
214
+
215
+ # Missing packages
216
+ if xmi_only[:packages].any?
217
+ issues << "Missing #{xmi_only[:packages].size} packages in QEA"
218
+ end
219
+
220
+ # Missing classes
221
+ if xmi_only[:classes].any?
222
+ issues << "Missing #{xmi_only[:classes].size} classes in QEA"
223
+ end
224
+
225
+ # Property differences with information loss
226
+ property_differences.each do |diff|
227
+ diff[:differences].each do |d|
228
+ next unless d.include?("QEA has fewer")
229
+
230
+ issues << "#{diff[:name]}: #{d}"
231
+ end
232
+ end
233
+
234
+ issues
235
+ end
236
+
237
+ # Get acceptable differences (QEA has more)
238
+ #
239
+ # @return [Array<String>] List of acceptable differences
240
+ def acceptable_differences # rubocop:disable Metrics/MethodLength
241
+ acceptable = []
242
+
243
+ # QEA-only elements
244
+ qea_only.each do |type, elements|
245
+ next if elements.empty?
246
+
247
+ acceptable << "QEA has #{elements.size} additional #{type}"
248
+ end
249
+
250
+ # Property differences where QEA has more
251
+ property_differences.each do |diff|
252
+ diff[:differences].each do |d|
253
+ next unless d.include?("QEA has more")
254
+
255
+ acceptable << "#{diff[:name]}: #{d}"
256
+ end
257
+ end
258
+
259
+ acceptable
260
+ end
261
+ end
262
+ end
263
+ end
264
+ end