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,277 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Verification
|
|
6
|
+
# Compares matched element pairs to find differences
|
|
7
|
+
class ElementComparator
|
|
8
|
+
# Compare package properties
|
|
9
|
+
#
|
|
10
|
+
# @param xmi_pkg [Lutaml::Uml::Package] XMI package
|
|
11
|
+
# @param qea_pkg [Lutaml::Uml::Package] QEA package
|
|
12
|
+
# @return [Hash] Comparison result with :equal and :differences
|
|
13
|
+
def compare_packages(xmi_pkg, qea_pkg) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
14
|
+
differences = []
|
|
15
|
+
|
|
16
|
+
# Compare basic properties
|
|
17
|
+
unless names_equal?(xmi_pkg.name, qea_pkg.name)
|
|
18
|
+
differences << "Name: '#{xmi_pkg.name}' vs '#{qea_pkg.name}'"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Compare collection counts
|
|
22
|
+
compare_collection_count(
|
|
23
|
+
xmi_pkg.classes, qea_pkg.classes, "classes", differences
|
|
24
|
+
)
|
|
25
|
+
compare_collection_count(
|
|
26
|
+
xmi_pkg.enums, qea_pkg.enums, "enums", differences
|
|
27
|
+
)
|
|
28
|
+
compare_collection_count(
|
|
29
|
+
xmi_pkg.data_types, qea_pkg.data_types, "data_types", differences
|
|
30
|
+
)
|
|
31
|
+
compare_collection_count(
|
|
32
|
+
xmi_pkg.packages, qea_pkg.packages, "packages", differences
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
{
|
|
36
|
+
equal: differences.empty?,
|
|
37
|
+
differences: differences,
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Compare class properties
|
|
42
|
+
#
|
|
43
|
+
# @param xmi_class [Lutaml::Uml::UmlClass] XMI class
|
|
44
|
+
# @param qea_class [Lutaml::Uml::UmlClass] QEA class
|
|
45
|
+
# @return [Hash] Comparison result with :equal and :differences
|
|
46
|
+
def compare_classes(xmi_class, qea_class) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
47
|
+
differences = []
|
|
48
|
+
|
|
49
|
+
# Compare name
|
|
50
|
+
unless names_equal?(xmi_class.name, qea_class.name)
|
|
51
|
+
differences << "Name: '#{xmi_class.name}' vs '#{qea_class.name}'"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Compare is_abstract
|
|
55
|
+
if xmi_class.is_abstract != qea_class.is_abstract
|
|
56
|
+
differences << "is_abstract: #{xmi_class.is_abstract} " \
|
|
57
|
+
"vs #{qea_class.is_abstract}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Compare type
|
|
61
|
+
if normalize_value(xmi_class.type) != normalize_value(qea_class.type)
|
|
62
|
+
differences << "type: '#{xmi_class.type}' vs '#{qea_class.type}'"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Compare modifier
|
|
66
|
+
if normalize_value(xmi_class.modifier) !=
|
|
67
|
+
normalize_value(qea_class.modifier)
|
|
68
|
+
differences << "modifier: '#{xmi_class.modifier}' " \
|
|
69
|
+
"vs '#{qea_class.modifier}'"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Compare collection counts
|
|
73
|
+
compare_collection_count(
|
|
74
|
+
xmi_class.attributes, qea_class.attributes,
|
|
75
|
+
"attributes", differences
|
|
76
|
+
)
|
|
77
|
+
compare_collection_count(
|
|
78
|
+
xmi_class.operations, qea_class.operations,
|
|
79
|
+
"operations", differences
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
{
|
|
83
|
+
equal: differences.empty?,
|
|
84
|
+
differences: differences,
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Compare attribute properties
|
|
89
|
+
#
|
|
90
|
+
# @param xmi_attr [Lutaml::Uml::TopElementAttribute] XMI attribute
|
|
91
|
+
# @param qea_attr [Lutaml::Uml::TopElementAttribute] QEA attribute
|
|
92
|
+
# @return [Hash] Comparison result with :equal and :differences
|
|
93
|
+
def compare_attributes(xmi_attr, qea_attr) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
94
|
+
differences = []
|
|
95
|
+
|
|
96
|
+
# Compare name
|
|
97
|
+
unless names_equal?(xmi_attr.name, qea_attr.name)
|
|
98
|
+
differences << "Name: '#{xmi_attr.name}' vs '#{qea_attr.name}'"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Compare type
|
|
102
|
+
if normalize_value(xmi_attr.type) !=
|
|
103
|
+
normalize_value(qea_attr.type)
|
|
104
|
+
differences << "type: '#{xmi_attr.type}' vs '#{qea_attr.type}'"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Compare visibility
|
|
108
|
+
if normalize_value(xmi_attr.visibility) !=
|
|
109
|
+
normalize_value(qea_attr.visibility)
|
|
110
|
+
differences << "visibility: '#{xmi_attr.visibility}' " \
|
|
111
|
+
"vs '#{qea_attr.visibility}'"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Compare cardinality if present
|
|
115
|
+
xmi_card = xmi_attr.cardinality
|
|
116
|
+
qea_card = qea_attr.cardinality
|
|
117
|
+
if xmi_card || qea_card
|
|
118
|
+
if xmi_card && qea_card
|
|
119
|
+
unless cardinalities_equal?(xmi_card, qea_card)
|
|
120
|
+
differences << "cardinality: #{format_cardinality(xmi_card)} " \
|
|
121
|
+
"vs #{format_cardinality(qea_card)}"
|
|
122
|
+
end
|
|
123
|
+
elsif xmi_card || qea_card
|
|
124
|
+
differences << "cardinality: #{format_cardinality(xmi_card)} " \
|
|
125
|
+
"vs #{format_cardinality(qea_card)}"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
{
|
|
130
|
+
equal: differences.empty?,
|
|
131
|
+
differences: differences,
|
|
132
|
+
}
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Compare operation properties
|
|
136
|
+
#
|
|
137
|
+
# @param xmi_op [Lutaml::Uml::Operation] XMI operation
|
|
138
|
+
# @param qea_op [Lutaml::Uml::Operation] QEA operation
|
|
139
|
+
# @return [Hash] Comparison result with :equal and :differences
|
|
140
|
+
def compare_operations(xmi_op, qea_op) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
141
|
+
differences = []
|
|
142
|
+
|
|
143
|
+
# Compare name
|
|
144
|
+
unless names_equal?(xmi_op.name, qea_op.name)
|
|
145
|
+
differences << "Name: '#{xmi_op.name}' vs '#{qea_op.name}'"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Compare return type
|
|
149
|
+
if normalize_value(xmi_op.return_type) !=
|
|
150
|
+
normalize_value(qea_op.return_type)
|
|
151
|
+
differences << "return_type: '#{xmi_op.return_type}' " \
|
|
152
|
+
"vs '#{qea_op.return_type}'"
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Compare visibility
|
|
156
|
+
if normalize_value(xmi_op.visibility) !=
|
|
157
|
+
normalize_value(qea_op.visibility)
|
|
158
|
+
differences << "visibility: '#{xmi_op.visibility}' " \
|
|
159
|
+
"vs '#{qea_op.visibility}'"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Compare parameter counts
|
|
163
|
+
compare_collection_count(
|
|
164
|
+
xmi_op.parameters, qea_op.parameters, "parameters", differences
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
{
|
|
168
|
+
equal: differences.empty?,
|
|
169
|
+
differences: differences,
|
|
170
|
+
}
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Compare association properties
|
|
174
|
+
#
|
|
175
|
+
# @param xmi_assoc [Lutaml::Uml::Association] XMI association
|
|
176
|
+
# @param qea_assoc [Lutaml::Uml::Association] QEA association
|
|
177
|
+
# @return [Hash] Comparison result with :equal and :differences
|
|
178
|
+
def compare_associations(xmi_assoc, qea_assoc) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
179
|
+
differences = []
|
|
180
|
+
|
|
181
|
+
# Compare owner end
|
|
182
|
+
unless normalize_value(xmi_assoc.owner_end) ==
|
|
183
|
+
normalize_value(qea_assoc.owner_end)
|
|
184
|
+
differences << "owner_end: '#{xmi_assoc.owner_end}' " \
|
|
185
|
+
"vs '#{qea_assoc.owner_end}'"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Compare member end
|
|
189
|
+
unless normalize_value(xmi_assoc.member_end) ==
|
|
190
|
+
normalize_value(qea_assoc.member_end)
|
|
191
|
+
differences << "member_end: '#{xmi_assoc.member_end}' " \
|
|
192
|
+
"vs '#{qea_assoc.member_end}'"
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Compare owner end cardinality
|
|
196
|
+
if xmi_assoc.owner_end_cardinality &&
|
|
197
|
+
qea_assoc.owner_end_cardinality &&
|
|
198
|
+
!cardinalities_equal?(
|
|
199
|
+
xmi_assoc.owner_end_cardinality,
|
|
200
|
+
qea_assoc.owner_end_cardinality,
|
|
201
|
+
)
|
|
202
|
+
differences << "owner_end_cardinality: " \
|
|
203
|
+
"#{format_cardinality(xmi_assoc
|
|
204
|
+
.owner_end_cardinality)} " \
|
|
205
|
+
"vs #{format_cardinality(qea_assoc
|
|
206
|
+
.owner_end_cardinality)}"
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Compare member end cardinality
|
|
210
|
+
if xmi_assoc.member_end_cardinality &&
|
|
211
|
+
qea_assoc.member_end_cardinality &&
|
|
212
|
+
!cardinalities_equal?(
|
|
213
|
+
xmi_assoc.member_end_cardinality,
|
|
214
|
+
qea_assoc.member_end_cardinality,
|
|
215
|
+
)
|
|
216
|
+
differences << "member_end_cardinality: " \
|
|
217
|
+
"#{format_cardinality(xmi_assoc
|
|
218
|
+
.member_end_cardinality)} " \
|
|
219
|
+
"vs #{format_cardinality(qea_assoc
|
|
220
|
+
.member_end_cardinality)}"
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
{
|
|
224
|
+
equal: differences.empty?,
|
|
225
|
+
differences: differences,
|
|
226
|
+
}
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
private
|
|
230
|
+
|
|
231
|
+
# Compare names with normalization
|
|
232
|
+
def names_equal?(name1, name2)
|
|
233
|
+
normalize_value(name1) == normalize_value(name2)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Normalize value for comparison
|
|
237
|
+
def normalize_value(value)
|
|
238
|
+
return nil if value.nil?
|
|
239
|
+
return value unless value.is_a?(String)
|
|
240
|
+
|
|
241
|
+
value.strip
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Compare collection counts
|
|
245
|
+
def compare_collection_count(xmi_coll, qea_coll, name, differences) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
246
|
+
xmi_count = xmi_coll&.size || 0
|
|
247
|
+
qea_count = qea_coll&.size || 0
|
|
248
|
+
|
|
249
|
+
return if xmi_count == qea_count
|
|
250
|
+
|
|
251
|
+
suffix = if qea_count < xmi_count
|
|
252
|
+
"QEA has fewer"
|
|
253
|
+
else
|
|
254
|
+
"QEA has more (acceptable)"
|
|
255
|
+
end
|
|
256
|
+
differences << "#{name}: #{xmi_count} (XMI) vs " \
|
|
257
|
+
"#{qea_count} (QEA) - #{suffix}"
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Check if cardinalities are equal
|
|
261
|
+
def cardinalities_equal?(card1, card2)
|
|
262
|
+
return true if card1.nil? && card2.nil?
|
|
263
|
+
return false if card1.nil? || card2.nil?
|
|
264
|
+
|
|
265
|
+
card1.min == card2.min && card1.max == card2.max
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Format cardinality for display
|
|
269
|
+
def format_cardinality(card)
|
|
270
|
+
return "nil" if card.nil?
|
|
271
|
+
|
|
272
|
+
"\#{card.min}..\#{card.max}"
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Verification
|
|
6
|
+
# Matches corresponding elements between XMI and QEA documents
|
|
7
|
+
# by qualified name/path
|
|
8
|
+
class StructureMatcher
|
|
9
|
+
# Match packages between documents
|
|
10
|
+
#
|
|
11
|
+
# @param xmi_doc [Lutaml::Uml::Document] XMI document
|
|
12
|
+
# @param qea_doc [Lutaml::Uml::Document] QEA document
|
|
13
|
+
# @return [Hash] Hash with :matches, :xmi_only, :qea_only
|
|
14
|
+
def match_packages(xmi_doc, qea_doc)
|
|
15
|
+
xmi_packages = build_package_index(xmi_doc.packages)
|
|
16
|
+
qea_packages = build_package_index(qea_doc.packages)
|
|
17
|
+
|
|
18
|
+
match_elements(xmi_packages, qea_packages)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Match classes between documents
|
|
22
|
+
#
|
|
23
|
+
# @param xmi_doc [Lutaml::Uml::Document] XMI document
|
|
24
|
+
# @param qea_doc [Lutaml::Uml::Document] QEA document
|
|
25
|
+
# @return [Hash] Hash with :matches, :xmi_only, :qea_only
|
|
26
|
+
def match_classes(xmi_doc, qea_doc)
|
|
27
|
+
xmi_classes = build_class_index(xmi_doc)
|
|
28
|
+
qea_classes = build_class_index(qea_doc)
|
|
29
|
+
|
|
30
|
+
match_elements(xmi_classes, qea_classes)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Match attributes between classes
|
|
34
|
+
#
|
|
35
|
+
# @param xmi_class [Lutaml::Uml::UmlClass] XMI class
|
|
36
|
+
# @param qea_class [Lutaml::Uml::UmlClass] QEA class
|
|
37
|
+
# @return [Hash] Hash with :matches, :xmi_only, :qea_only
|
|
38
|
+
def match_attributes(xmi_class, qea_class)
|
|
39
|
+
xmi_attrs = index_by_name(xmi_class.attributes || [])
|
|
40
|
+
qea_attrs = index_by_name(qea_class.attributes || [])
|
|
41
|
+
|
|
42
|
+
match_elements(xmi_attrs, qea_attrs)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Match operations between classes
|
|
46
|
+
#
|
|
47
|
+
# @param xmi_class [Lutaml::Uml::UmlClass] XMI class
|
|
48
|
+
# @param qea_class [Lutaml::Uml::UmlClass] QEA class
|
|
49
|
+
# @return [Hash] Hash with :matches, :xmi_only, :qea_only
|
|
50
|
+
def match_operations(xmi_class, qea_class)
|
|
51
|
+
xmi_ops = index_by_signature(xmi_class.operations || [])
|
|
52
|
+
qea_ops = index_by_signature(qea_class.operations || [])
|
|
53
|
+
|
|
54
|
+
match_elements(xmi_ops, qea_ops)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Build qualified name index for document
|
|
58
|
+
#
|
|
59
|
+
# @param document [Lutaml::Uml::Document] The document
|
|
60
|
+
# @return [Hash] Hash mapping qualified names to elements
|
|
61
|
+
def build_qualified_names(document)
|
|
62
|
+
{
|
|
63
|
+
packages: build_package_index(document.packages),
|
|
64
|
+
classes: build_class_index(document),
|
|
65
|
+
enums: build_enum_index(document),
|
|
66
|
+
data_types: build_data_type_index(document),
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
# Build package index with qualified paths
|
|
73
|
+
def build_package_index(packages, parent_path = "") # rubocop:disable Metrics/MethodLength
|
|
74
|
+
index = {}
|
|
75
|
+
return index unless packages
|
|
76
|
+
|
|
77
|
+
packages.each do |package|
|
|
78
|
+
next unless package.name
|
|
79
|
+
|
|
80
|
+
qualified_path = build_path(parent_path, package.name)
|
|
81
|
+
index[qualified_path] = package
|
|
82
|
+
|
|
83
|
+
# Recursively index nested packages
|
|
84
|
+
nested = build_package_index(package.packages, qualified_path)
|
|
85
|
+
index.merge!(nested)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
index
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Build class index from document and packages
|
|
92
|
+
def build_class_index(document) # rubocop:disable Metrics/MethodLength
|
|
93
|
+
index = {}
|
|
94
|
+
|
|
95
|
+
# Index top-level classes
|
|
96
|
+
document.classes&.each do |klass|
|
|
97
|
+
next unless klass.name
|
|
98
|
+
|
|
99
|
+
index[klass.name] = klass
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Index classes in packages
|
|
103
|
+
if document.packages
|
|
104
|
+
index_classes_in_packages(document.packages, "",
|
|
105
|
+
index)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
index
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Recursively index classes in packages
|
|
112
|
+
def index_classes_in_packages(packages, parent_path, index) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
|
|
113
|
+
return unless packages
|
|
114
|
+
|
|
115
|
+
packages.each do |package|
|
|
116
|
+
next unless package.name
|
|
117
|
+
|
|
118
|
+
package_path = build_path(parent_path, package.name)
|
|
119
|
+
|
|
120
|
+
package.classes&.each do |klass|
|
|
121
|
+
next unless klass.name
|
|
122
|
+
|
|
123
|
+
qualified_name = "#{package_path}::#{klass.name}"
|
|
124
|
+
index[qualified_name] = klass
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Recurse into nested packages
|
|
128
|
+
index_classes_in_packages(package.packages, package_path, index)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Build enum index from document and packages
|
|
133
|
+
def build_enum_index(document) # rubocop:disable Metrics/MethodLength
|
|
134
|
+
index = {}
|
|
135
|
+
|
|
136
|
+
# Index top-level enums
|
|
137
|
+
document.enums&.each do |enum|
|
|
138
|
+
next unless enum.name
|
|
139
|
+
|
|
140
|
+
index[enum.name] = enum
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Index enums in packages
|
|
144
|
+
if document.packages
|
|
145
|
+
index_enums_in_packages(document.packages, "",
|
|
146
|
+
index)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
index
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Recursively index enums in packages
|
|
153
|
+
def index_enums_in_packages(packages, parent_path, index) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
|
|
154
|
+
return unless packages
|
|
155
|
+
|
|
156
|
+
packages.each do |package|
|
|
157
|
+
next unless package.name
|
|
158
|
+
|
|
159
|
+
package_path = build_path(parent_path, package.name)
|
|
160
|
+
|
|
161
|
+
package.enums&.each do |enum|
|
|
162
|
+
next unless enum.name
|
|
163
|
+
|
|
164
|
+
qualified_name = "#{package_path}::#{enum.name}"
|
|
165
|
+
index[qualified_name] = enum
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Recurse into nested packages
|
|
169
|
+
index_enums_in_packages(package.packages, package_path, index)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Build data type index from document and packages
|
|
174
|
+
def build_data_type_index(document) # rubocop:disable Metrics/MethodLength
|
|
175
|
+
index = {}
|
|
176
|
+
|
|
177
|
+
# Index top-level data types
|
|
178
|
+
document.data_types&.each do |dt|
|
|
179
|
+
next unless dt.name
|
|
180
|
+
|
|
181
|
+
index[dt.name] = dt
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Index data types in packages
|
|
185
|
+
if document.packages
|
|
186
|
+
index_data_types_in_packages(document.packages, "",
|
|
187
|
+
index)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
index
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Recursively index data types in packages
|
|
194
|
+
def index_data_types_in_packages(packages, parent_path, index) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
|
|
195
|
+
return unless packages
|
|
196
|
+
|
|
197
|
+
packages.each do |package|
|
|
198
|
+
next unless package.name
|
|
199
|
+
|
|
200
|
+
package_path = build_path(parent_path, package.name)
|
|
201
|
+
|
|
202
|
+
package.data_types&.each do |dt|
|
|
203
|
+
next unless dt.name
|
|
204
|
+
|
|
205
|
+
qualified_name = "#{package_path}::#{dt.name}"
|
|
206
|
+
index[qualified_name] = dt
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Recurse into nested packages
|
|
210
|
+
index_data_types_in_packages(package.packages, package_path, index)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Build qualified path from parent and name
|
|
215
|
+
def build_path(parent_path, name)
|
|
216
|
+
return name if parent_path.empty?
|
|
217
|
+
|
|
218
|
+
"#{parent_path}::#{name}"
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Index collection by name
|
|
222
|
+
def index_by_name(collection)
|
|
223
|
+
index = {}
|
|
224
|
+
collection.each do |element|
|
|
225
|
+
next unless element.name
|
|
226
|
+
|
|
227
|
+
index[element.name] = element
|
|
228
|
+
end
|
|
229
|
+
index
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Index operations by signature (name + parameter types)
|
|
233
|
+
def index_by_signature(operations)
|
|
234
|
+
index = {}
|
|
235
|
+
operations.each do |operation|
|
|
236
|
+
next unless operation.name
|
|
237
|
+
|
|
238
|
+
signature = build_operation_signature(operation)
|
|
239
|
+
index[signature] = operation
|
|
240
|
+
end
|
|
241
|
+
index
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Build operation signature for matching
|
|
245
|
+
def build_operation_signature(operation)
|
|
246
|
+
return operation.name unless operation.owned_parameter
|
|
247
|
+
|
|
248
|
+
param_types = operation.owned_parameter.map do |param|
|
|
249
|
+
param.type || "unknown"
|
|
250
|
+
end.join(",")
|
|
251
|
+
|
|
252
|
+
"#{operation.name}(#{param_types})"
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Match elements between two indexes
|
|
256
|
+
def match_elements(xmi_index, qea_index) # rubocop:disable Metrics/MethodLength
|
|
257
|
+
matches = {}
|
|
258
|
+
xmi_only = []
|
|
259
|
+
qea_only = []
|
|
260
|
+
|
|
261
|
+
# Find matches and XMI-only elements
|
|
262
|
+
xmi_index.each do |key, xmi_element|
|
|
263
|
+
if qea_index.key?(key)
|
|
264
|
+
matches[key] = {
|
|
265
|
+
xmi: xmi_element,
|
|
266
|
+
qea: qea_index[key],
|
|
267
|
+
}
|
|
268
|
+
else
|
|
269
|
+
xmi_only << key
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Find QEA-only elements
|
|
274
|
+
qea_index.each_key do |key|
|
|
275
|
+
qea_only << key unless xmi_index.key?(key)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
{
|
|
279
|
+
matches: matches,
|
|
280
|
+
xmi_only: xmi_only.sort,
|
|
281
|
+
qea_only: qea_only.sort,
|
|
282
|
+
}
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Verification
|
|
6
|
+
autoload :DocumentNormalizer,
|
|
7
|
+
"ea/qea/verification/document_normalizer"
|
|
8
|
+
autoload :StructureMatcher, "ea/qea/verification/structure_matcher"
|
|
9
|
+
autoload :ElementComparator, "ea/qea/verification/element_comparator"
|
|
10
|
+
autoload :ComparisonResult, "ea/qea/verification/comparison_result"
|
|
11
|
+
autoload :DocumentVerifier, "ea/qea/verification/document_verifier"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|