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,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
|