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.
- checksums.yaml +4 -4
- data/CLAUDE.md +125 -0
- data/Rakefile +12 -4
- data/TODO.next/00-publish-blocking-bugs.md +74 -0
- data/TODO.next/01-standalone-ea-gem-identity.md +76 -0
- data/TODO.next/02-optional-lutaml-uml-dependency.md +47 -0
- data/TODO.next/03-slim-lutaml-uml.md +79 -0
- data/TODO.next/04-loader-registry-for-uml-repository.md +49 -0
- data/TODO.next/05-extract-shared-transformer-methods.md +14 -0
- data/TODO.next/06-deduplicate-stereotype-loading.md +17 -0
- data/TODO.next/07-transformer-registry-in-factory.md +20 -0
- data/TODO.next/08-connector-type-registry.md +27 -0
- data/TODO.next/09-element-renderer-registry.md +29 -0
- data/TODO.next/10-connector-renderer-lsp.md +18 -0
- data/TODO.next/11-consolidate-style-knowledge.md +33 -0
- data/TODO.next/12-data-driven-from-db-row.md +24 -0
- data/TODO.next/13-extract-duplicated-methods.md +17 -0
- data/TODO.next/14-remove-dead-code.md +10 -0
- data/TODO.next/15-narrow-exception-handling.md +39 -0
- data/TODO.next/16-repository-indexes.md +28 -0
- data/TODO.next/17-fix-spec-quality-and-coverage.md +32 -0
- data/TODO.next/18-xmi-tool-specific-parser-architecture.md +172 -0
- data/TODO.next/19-fix-ea-gemspec-dependency-declarations.md +56 -0
- data/TODO.next/20-ci-requires-unreleased-lutaml-uml.md +63 -0
- data/TODO.next/21-qeatoxmi-via-xmi-gem.md +340 -0
- data/TODO.next/22-strip-respond-to-from-qeatoxmi-specs.md +32 -0
- data/TODO.next/23-cleanup-idallocator.md +41 -0
- data/TODO.next/24-tighten-parity-specs.md +42 -0
- data/TODO.next/25-sparx-eaid-format-for-synthesized-ids.md +62 -0
- data/TODO.next/26-fix-uppervalue-lowervalue-count-gap.md +51 -0
- data/TODO.next/27-extract-cardinality-module.md +68 -0
- data/TODO.next/28-extract-xml-sanitizer.md +51 -0
- data/TODO.next/29-ocp-registry-for-classifier-builders.md +58 -0
- data/TODO.next/30-struct-return-for-association-end.md +37 -0
- data/TODO.next/31-idallocator-specs.md +27 -0
- data/TODO.next/32-phase2-gap-sentinel-specs.md +53 -0
- data/TODO.next/33-normalize-lower-cleanup.md +30 -0
- data/TODO.next/34-document-member-end-order-rt-prefix.md +29 -0
- data/TODO.next/35-walk-runstate-for-instance-slots.md +76 -0
- data/TODO.next/36-wire-interface-realization.md +50 -0
- data/TODO.next/37-visibility-returns-real-booleans.md +36 -0
- data/config/diagram_styles.yml +200 -0
- data/config/model_transformations.yml +266 -0
- data/config/qea_schema.yml +1024 -0
- data/docs/ea_to_uml_type_mapping.md +89 -0
- data/docs/xmi_qea_conversion_capabilities.md +99 -0
- data/examples/lur/20251010_current_plateau_v5.1.lur +0 -0
- data/examples/lur/basic.lur +0 -0
- data/examples/lur/test-output.lur +0 -0
- data/examples/lur/test.lur +0 -0
- data/examples/lur_basic_usage.rb +221 -0
- data/examples/lur_cli_workflow.rb +263 -0
- data/examples/lur_statistics.rb +326 -0
- data/examples/qea/20251010_current_plateau_v5.1.qea +0 -0
- data/examples/qea/ArcGISWorkspace_template.qea +0 -0
- data/examples/qea/README_qea_parser.adoc +230 -0
- data/examples/qea/UmlModel_template.qea +0 -0
- data/examples/qea/basic.qea +0 -0
- data/examples/qea/simple.qea +0 -0
- data/examples/qea/simple_example.qea +0 -0
- data/examples/qea/test.qea +0 -0
- data/examples/qea_standalone_query.rb +73 -0
- data/examples/qea_to_repository.rb +51 -0
- data/examples/smoke_test_real_qea.rb +81 -0
- data/exe/ea +7 -0
- data/lib/ea/cli/app.rb +72 -0
- data/lib/ea/cli/command/base.rb +80 -0
- data/lib/ea/cli/command/convert.rb +62 -0
- data/lib/ea/cli/command/diagrams.rb +81 -0
- data/lib/ea/cli/command/list.rb +61 -0
- data/lib/ea/cli/command/parse.rb +29 -0
- data/lib/ea/cli/command/stats.rb +20 -0
- data/lib/ea/cli/command/validate.rb +41 -0
- data/lib/ea/cli/command.rb +15 -0
- data/lib/ea/cli/error.rb +34 -0
- data/lib/ea/cli/output/formatter.rb +34 -0
- data/lib/ea/cli/output/json_formatter.rb +20 -0
- data/lib/ea/cli/output/table_formatter.rb +42 -0
- data/lib/ea/cli/output/yaml_formatter.rb +20 -0
- data/lib/ea/cli/output.rb +56 -0
- data/lib/ea/cli.rb +17 -0
- data/lib/ea/diagram/configuration.rb +379 -0
- data/lib/ea/diagram/element_renderers/base_renderer.rb +77 -0
- data/lib/ea/diagram/element_renderers/class_renderer.rb +323 -0
- data/lib/ea/diagram/element_renderers/connector_renderer.rb +41 -0
- data/lib/ea/diagram/element_renderers/package_renderer.rb +61 -0
- data/lib/ea/diagram/element_renderers.rb +43 -0
- data/lib/ea/diagram/extractor.rb +560 -0
- data/lib/ea/diagram/layout_engine.rb +170 -0
- data/lib/ea/diagram/path_builder.rb +202 -0
- data/lib/ea/diagram/style_parser.rb +42 -0
- data/lib/ea/diagram/style_resolver.rb +276 -0
- data/lib/ea/diagram/svg_renderer.rb +274 -0
- data/lib/ea/diagram/util.rb +73 -0
- data/lib/ea/diagram.rb +47 -0
- data/lib/ea/qea/benchmark.rb +210 -0
- data/lib/ea/qea/database.rb +308 -0
- data/lib/ea/qea/factory/association_builder.rb +203 -0
- data/lib/ea/qea/factory/association_transformer.rb +91 -0
- data/lib/ea/qea/factory/attribute_tag_transformer.rb +57 -0
- data/lib/ea/qea/factory/attribute_transformer.rb +93 -0
- data/lib/ea/qea/factory/base_transformer.rb +177 -0
- data/lib/ea/qea/factory/class_transformer.rb +116 -0
- data/lib/ea/qea/factory/constraint_transformer.rb +75 -0
- data/lib/ea/qea/factory/data_type_transformer.rb +77 -0
- data/lib/ea/qea/factory/diagram_transformer.rb +157 -0
- data/lib/ea/qea/factory/document_builder.rb +283 -0
- data/lib/ea/qea/factory/ea_to_uml_factory.rb +229 -0
- data/lib/ea/qea/factory/enum_transformer.rb +74 -0
- data/lib/ea/qea/factory/generalization_builder.rb +227 -0
- data/lib/ea/qea/factory/generalization_transformer.rb +98 -0
- data/lib/ea/qea/factory/instance_transformer.rb +68 -0
- data/lib/ea/qea/factory/object_property_transformer.rb +58 -0
- data/lib/ea/qea/factory/operation_transformer.rb +66 -0
- data/lib/ea/qea/factory/package_transformer.rb +145 -0
- data/lib/ea/qea/factory/reference_resolver.rb +99 -0
- data/lib/ea/qea/factory/stereotype_loader.rb +39 -0
- data/lib/ea/qea/factory/tagged_value_transformer.rb +38 -0
- data/lib/ea/qea/factory/transformer_registry.rb +80 -0
- data/lib/ea/qea/factory.rb +37 -0
- data/lib/ea/qea/file_detector.rb +178 -0
- data/lib/ea/qea/infrastructure/database_connection.rb +100 -0
- data/lib/ea/qea/infrastructure/schema_reader.rb +136 -0
- data/lib/ea/qea/infrastructure/table_reader.rb +224 -0
- data/lib/ea/qea/infrastructure.rb +12 -0
- data/lib/ea/qea/models/base_model.rb +59 -0
- data/lib/ea/qea/models/ea_attribute.rb +109 -0
- data/lib/ea/qea/models/ea_attribute_tag.rb +100 -0
- data/lib/ea/qea/models/ea_complexity_type.rb +79 -0
- data/lib/ea/qea/models/ea_connector.rb +160 -0
- data/lib/ea/qea/models/ea_connector_type.rb +60 -0
- data/lib/ea/qea/models/ea_constraint_type.rb +63 -0
- data/lib/ea/qea/models/ea_datatype.rb +104 -0
- data/lib/ea/qea/models/ea_diagram.rb +115 -0
- data/lib/ea/qea/models/ea_diagram_link.rb +78 -0
- data/lib/ea/qea/models/ea_diagram_object.rb +73 -0
- data/lib/ea/qea/models/ea_diagram_type.rb +56 -0
- data/lib/ea/qea/models/ea_document.rb +63 -0
- data/lib/ea/qea/models/ea_object.rb +223 -0
- data/lib/ea/qea/models/ea_object_constraint.rb +53 -0
- data/lib/ea/qea/models/ea_object_property.rb +87 -0
- data/lib/ea/qea/models/ea_object_type.rb +73 -0
- data/lib/ea/qea/models/ea_operation.rb +127 -0
- data/lib/ea/qea/models/ea_operation_param.rb +76 -0
- data/lib/ea/qea/models/ea_package.rb +78 -0
- data/lib/ea/qea/models/ea_script.rb +62 -0
- data/lib/ea/qea/models/ea_status_type.rb +66 -0
- data/lib/ea/qea/models/ea_stereotype.rb +57 -0
- data/lib/ea/qea/models/ea_tagged_value.rb +99 -0
- data/lib/ea/qea/models/ea_xref.rb +165 -0
- data/lib/ea/qea/models.rb +35 -0
- data/lib/ea/qea/repositories/base_repository.rb +225 -0
- data/lib/ea/qea/repositories/object_repository.rb +219 -0
- data/lib/ea/qea/repositories.rb +10 -0
- data/lib/ea/qea/services/configuration.rb +211 -0
- data/lib/ea/qea/services/database_loader.rb +191 -0
- data/lib/ea/qea/services.rb +10 -0
- data/lib/ea/qea/validation/association_validator.rb +73 -0
- data/lib/ea/qea/validation/attribute_validator.rb +91 -0
- data/lib/ea/qea/validation/base_validator.rb +331 -0
- data/lib/ea/qea/validation/class_validator.rb +121 -0
- data/lib/ea/qea/validation/database/circular_reference_validator.rb +109 -0
- data/lib/ea/qea/validation/database/orphan_validator.rb +153 -0
- data/lib/ea/qea/validation/database/referential_integrity_validator.rb +128 -0
- data/lib/ea/qea/validation/database.rb +16 -0
- data/lib/ea/qea/validation/diagram_validator.rb +112 -0
- data/lib/ea/qea/validation/formatters/json_formatter.rb +137 -0
- data/lib/ea/qea/validation/formatters/text_formatter.rb +235 -0
- data/lib/ea/qea/validation/formatters.rb +12 -0
- data/lib/ea/qea/validation/operation_validator.rb +71 -0
- data/lib/ea/qea/validation/package_validator.rb +111 -0
- data/lib/ea/qea/validation/validation_engine.rb +387 -0
- data/lib/ea/qea/validation/validation_message.rb +144 -0
- data/lib/ea/qea/validation/validation_result.rb +210 -0
- data/lib/ea/qea/validation/validator_registry.rb +134 -0
- data/lib/ea/qea/validation.rb +28 -0
- data/lib/ea/qea/verification/comparison_result.rb +264 -0
- data/lib/ea/qea/verification/document_normalizer.rb +169 -0
- data/lib/ea/qea/verification/document_verifier.rb +322 -0
- data/lib/ea/qea/verification/element_comparator.rb +277 -0
- data/lib/ea/qea/verification/structure_matcher.rb +287 -0
- data/lib/ea/qea/verification.rb +14 -0
- data/lib/ea/qea.rb +185 -0
- data/lib/ea/transformations/configuration.rb +333 -0
- data/lib/ea/transformations/format_registry.rb +366 -0
- data/lib/ea/transformations/parsers/base_parser.rb +482 -0
- data/lib/ea/transformations/parsers/qea_parser.rb +401 -0
- data/lib/ea/transformations/parsers/xmi_parser.rb +243 -0
- data/lib/ea/transformations/transformation_engine.rb +390 -0
- data/lib/ea/transformations.rb +85 -0
- data/lib/ea/transformers/qea_to_xmi/association_end.rb +19 -0
- data/lib/ea/transformers/qea_to_xmi/cardinality.rb +96 -0
- data/lib/ea/transformers/qea_to_xmi/context.rb +106 -0
- data/lib/ea/transformers/qea_to_xmi/guid_format.rb +56 -0
- data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +92 -0
- data/lib/ea/transformers/qea_to_xmi/run_state.rb +107 -0
- data/lib/ea/transformers/qea_to_xmi/transformer.rb +607 -0
- data/lib/ea/transformers/qea_to_xmi/visibility.rb +73 -0
- data/lib/ea/transformers/qea_to_xmi.rb +29 -0
- data/lib/ea/transformers/uml_to_xmi/id_generator.rb +54 -0
- data/lib/ea/transformers/uml_to_xmi/transformer.rb +152 -0
- data/lib/ea/transformers/uml_to_xmi/writer.rb +96 -0
- data/lib/ea/transformers/uml_to_xmi.rb +16 -0
- data/lib/ea/transformers.rb +34 -0
- data/lib/ea/version.rb +1 -1
- data/lib/ea/xmi/liquid_drops/association_drop.rb +56 -0
- data/lib/ea/xmi/liquid_drops/attribute_drop.rb +72 -0
- data/lib/ea/xmi/liquid_drops/cardinality_drop.rb +35 -0
- data/lib/ea/xmi/liquid_drops/connector_drop.rb +54 -0
- data/lib/ea/xmi/liquid_drops/constraint_drop.rb +29 -0
- data/lib/ea/xmi/liquid_drops/data_type_drop.rb +63 -0
- data/lib/ea/xmi/liquid_drops/dependency_drop.rb +36 -0
- data/lib/ea/xmi/liquid_drops/diagram_drop.rb +34 -0
- data/lib/ea/xmi/liquid_drops/enum_drop.rb +49 -0
- data/lib/ea/xmi/liquid_drops/enum_owned_literal_drop.rb +25 -0
- data/lib/ea/xmi/liquid_drops/generalization_attribute_drop.rb +87 -0
- data/lib/ea/xmi/liquid_drops/generalization_drop.rb +127 -0
- data/lib/ea/xmi/liquid_drops/klass_drop.rb +191 -0
- data/lib/ea/xmi/liquid_drops/operation_drop.rb +29 -0
- data/lib/ea/xmi/liquid_drops/package_drop.rb +108 -0
- data/lib/ea/xmi/liquid_drops/root_drop.rb +34 -0
- data/lib/ea/xmi/liquid_drops/source_target_drop.rb +43 -0
- data/lib/ea/xmi/lookup_service.rb +89 -0
- data/lib/ea/xmi/parser.rb +919 -0
- data/lib/ea/xmi.rb +35 -0
- data/lib/ea.rb +10 -1
- 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
|