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,283 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Factory
|
|
6
|
+
# Builds and validates UML Document structure
|
|
7
|
+
# Ensures all required document sections are populated correctly
|
|
8
|
+
class DocumentBuilder
|
|
9
|
+
attr_reader :document
|
|
10
|
+
|
|
11
|
+
# Initialize builder with new document
|
|
12
|
+
# @param name [String] Document name
|
|
13
|
+
def initialize(name: "EA Model")
|
|
14
|
+
@document = Lutaml::Uml::Document.new
|
|
15
|
+
@document.name = name
|
|
16
|
+
# Don't initialize collections - they have default values
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Add packages to document
|
|
20
|
+
# @param packages [Array<Lutaml::Uml::Package>] Packages to add
|
|
21
|
+
# @return [self] For method chaining
|
|
22
|
+
def add_packages(packages)
|
|
23
|
+
return self if packages.nil? || packages.empty?
|
|
24
|
+
|
|
25
|
+
@document.packages.concat(packages)
|
|
26
|
+
self
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Add classes to document
|
|
30
|
+
# @param classes [Array<Lutaml::Uml::UmlClass>] Classes to add
|
|
31
|
+
# @return [self] For method chaining
|
|
32
|
+
def add_classes(classes)
|
|
33
|
+
return self if classes.nil? || classes.empty?
|
|
34
|
+
|
|
35
|
+
@document.classes.concat(classes)
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Add enums to document
|
|
40
|
+
# @param enums [Array<Lutaml::Uml::Enum>] Enums to add
|
|
41
|
+
# @return [self] For method chaining
|
|
42
|
+
def add_enums(enums)
|
|
43
|
+
return self if enums.nil? || enums.empty?
|
|
44
|
+
|
|
45
|
+
@document.enums.concat(enums)
|
|
46
|
+
self
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Add data types to document
|
|
50
|
+
# @param data_types [Array<Lutaml::Uml::DataType>] Data types to add
|
|
51
|
+
# @return [self] For method chaining
|
|
52
|
+
def add_data_types(data_types)
|
|
53
|
+
return self if data_types.nil? || data_types.empty?
|
|
54
|
+
|
|
55
|
+
@document.data_types.concat(data_types)
|
|
56
|
+
self
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Add instances to document
|
|
60
|
+
# @param instances [Array<Lutaml::Uml::Instance>] Instances to add
|
|
61
|
+
# @return [self] For method chaining
|
|
62
|
+
def add_instances(instances)
|
|
63
|
+
return self if instances.nil? || instances.empty?
|
|
64
|
+
|
|
65
|
+
@document.instances.concat(instances)
|
|
66
|
+
self
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Add associations to document
|
|
70
|
+
# @param associations [Array<Lutaml::Uml::Association>] Associations
|
|
71
|
+
# @return [self] For method chaining
|
|
72
|
+
def add_associations(associations)
|
|
73
|
+
return self if associations.nil? || associations.empty?
|
|
74
|
+
|
|
75
|
+
@document.associations.concat(associations)
|
|
76
|
+
self
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Add diagrams to document
|
|
80
|
+
# @param diagrams [Array<Lutaml::Uml::Diagram>] Diagrams
|
|
81
|
+
# @return [self] For method chaining
|
|
82
|
+
def add_diagrams(diagrams)
|
|
83
|
+
return self if diagrams.nil? || diagrams.empty?
|
|
84
|
+
|
|
85
|
+
@document.diagrams.concat(diagrams)
|
|
86
|
+
self
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Set document metadata
|
|
90
|
+
# @param title [String] Document title
|
|
91
|
+
# @param caption [String] Document caption
|
|
92
|
+
# @return [self] For method chaining
|
|
93
|
+
def set_metadata(title: nil, caption: nil)
|
|
94
|
+
@document.title = title if title
|
|
95
|
+
@document.caption = caption if caption
|
|
96
|
+
self
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Build and return the document
|
|
100
|
+
# @param validate [Boolean] Whether to validate before returning
|
|
101
|
+
# @return [Lutaml::Uml::Document] The built document
|
|
102
|
+
# @raise [ValidationError] If validation fails
|
|
103
|
+
def build(validate: true)
|
|
104
|
+
validate! if validate
|
|
105
|
+
@document
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Validate document integrity
|
|
109
|
+
# @return [Boolean] True if valid
|
|
110
|
+
# @raise [ValidationError] If validation fails
|
|
111
|
+
def validate! # rubocop:disable Metrics/MethodLength
|
|
112
|
+
errors = []
|
|
113
|
+
warnings = []
|
|
114
|
+
|
|
115
|
+
# Check for duplicate xmi_ids
|
|
116
|
+
check_duplicate_xmi_ids(errors)
|
|
117
|
+
|
|
118
|
+
# Check association references (warnings only for missing refs)
|
|
119
|
+
check_association_references(warnings)
|
|
120
|
+
|
|
121
|
+
# Print warnings if any
|
|
122
|
+
unless warnings.empty?
|
|
123
|
+
warn "Document validation warnings:"
|
|
124
|
+
warnings.each { |w| warn " - #{w}" }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
raise ValidationError, errors.join("; ") unless errors.empty?
|
|
128
|
+
|
|
129
|
+
true
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Get document statistics
|
|
133
|
+
# @return [Hash] Statistics about document contents
|
|
134
|
+
def stats
|
|
135
|
+
{
|
|
136
|
+
packages: @document.packages.size,
|
|
137
|
+
classes: @document.classes.size,
|
|
138
|
+
enums: @document.enums.size,
|
|
139
|
+
data_types: @document.data_types.size,
|
|
140
|
+
instances: @document.instances.size,
|
|
141
|
+
associations: @document.associations.size,
|
|
142
|
+
}
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
# Check for duplicate xmi_ids across all elements
|
|
148
|
+
# @param errors [Array<String>] Error accumulator
|
|
149
|
+
def check_duplicate_xmi_ids(errors)
|
|
150
|
+
xmi_ids = collect_all_xmi_ids
|
|
151
|
+
duplicates = xmi_ids.group_by { |id| id }
|
|
152
|
+
.select { |_, v| v.size > 1 }
|
|
153
|
+
.keys
|
|
154
|
+
|
|
155
|
+
unless duplicates.empty?
|
|
156
|
+
errors << "Duplicate xmi_ids found: #{duplicates.join(', ')}"
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Collect all xmi_ids from document
|
|
161
|
+
# @return [Array<String>] All xmi_ids
|
|
162
|
+
def collect_all_xmi_ids # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
|
|
163
|
+
ids = []
|
|
164
|
+
|
|
165
|
+
# Collect from top-level elements
|
|
166
|
+
ids.concat(@document.packages.filter_map(&:xmi_id))
|
|
167
|
+
ids.concat(@document.classes.filter_map(&:xmi_id))
|
|
168
|
+
ids.concat(@document.enums.filter_map(&:xmi_id))
|
|
169
|
+
ids.concat(@document.data_types.filter_map(&:xmi_id))
|
|
170
|
+
ids.concat(@document.instances.filter_map(&:xmi_id))
|
|
171
|
+
|
|
172
|
+
# Recursively collect from packages (where most classes actually are)
|
|
173
|
+
@document.packages.each do |package|
|
|
174
|
+
ids.concat(collect_package_xmi_ids(package))
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
ids
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Recursively collect all xmi_ids from a package and its descendants
|
|
181
|
+
# @param package [Lutaml::Uml::Package] Package to collect from
|
|
182
|
+
# @return [Array<String>] All xmi_ids in package hierarchy
|
|
183
|
+
def collect_package_xmi_ids(package) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
184
|
+
ids = []
|
|
185
|
+
|
|
186
|
+
# Collect from package's elements
|
|
187
|
+
ids.concat(package.classes.filter_map(&:xmi_id)) if package.classes
|
|
188
|
+
ids.concat(package.enums.filter_map(&:xmi_id)) if package.enums
|
|
189
|
+
if package.data_types
|
|
190
|
+
ids.concat(package.data_types.filter_map(&:xmi_id))
|
|
191
|
+
end
|
|
192
|
+
if package.instances
|
|
193
|
+
ids.concat(package.instances.filter_map(&:xmi_id))
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Recursively collect from child packages
|
|
197
|
+
package.packages&.each do |child_package|
|
|
198
|
+
ids.concat(collect_package_xmi_ids(child_package))
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
ids
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Check that all association references are valid
|
|
205
|
+
# @param warnings [Array<String>] Warning accumulator
|
|
206
|
+
def check_association_references(warnings) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
207
|
+
return if @document.associations.empty?
|
|
208
|
+
|
|
209
|
+
all_xmi_ids = collect_all_xmi_ids.to_set
|
|
210
|
+
invalid_associations = []
|
|
211
|
+
|
|
212
|
+
@document.associations.each do |assoc|
|
|
213
|
+
has_invalid_member = !check_association_end_valid?(
|
|
214
|
+
assoc, :member_end_xmi_id, all_xmi_ids
|
|
215
|
+
)
|
|
216
|
+
has_invalid_owner = !check_association_end_valid?(
|
|
217
|
+
assoc, :owner_end_xmi_id, all_xmi_ids
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
if has_invalid_member || has_invalid_owner
|
|
221
|
+
invalid_associations << assoc
|
|
222
|
+
if has_invalid_member
|
|
223
|
+
add_invalid_end_warning(assoc, :member_end_xmi_id, all_xmi_ids,
|
|
224
|
+
warnings)
|
|
225
|
+
end
|
|
226
|
+
if has_invalid_owner
|
|
227
|
+
add_invalid_end_warning(assoc, :owner_end_xmi_id, all_xmi_ids,
|
|
228
|
+
warnings)
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Remove invalid associations from document
|
|
234
|
+
unless invalid_associations.empty?
|
|
235
|
+
@document.associations.reject! do |a|
|
|
236
|
+
invalid_associations.include?(a)
|
|
237
|
+
end
|
|
238
|
+
warnings << "Removed #{invalid_associations.size} association(s) " \
|
|
239
|
+
"with invalid references"
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Check if association end reference is valid
|
|
244
|
+
# @param assoc [Lutaml::Uml::Association] Association
|
|
245
|
+
# @param attr [Symbol] Attribute to check (should be xmi_id attribute)
|
|
246
|
+
# @param valid_ids [Set<String>] Set of valid xmi_ids
|
|
247
|
+
# @return [Boolean] True if valid or nil
|
|
248
|
+
def check_association_end_valid?(assoc, attr, valid_ids)
|
|
249
|
+
value = assoc.public_send(attr)
|
|
250
|
+
return true if value.nil?
|
|
251
|
+
|
|
252
|
+
valid_ids.include?(value)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Add warning for invalid association end
|
|
256
|
+
# @param assoc [Lutaml::Uml::Association] Association
|
|
257
|
+
# @param attr [Symbol] Attribute to check (should be xmi_id attribute)
|
|
258
|
+
# @param valid_ids [Set<String>] Set of valid xmi_ids
|
|
259
|
+
# @param warnings [Array<String>] Warning accumulator
|
|
260
|
+
def add_invalid_end_warning(assoc, attr, valid_ids, warnings) # rubocop:disable Metrics/MethodLength
|
|
261
|
+
value = assoc.public_send(attr)
|
|
262
|
+
return if value.nil?
|
|
263
|
+
|
|
264
|
+
unless valid_ids.include?(value)
|
|
265
|
+
# Get the corresponding name attribute for better error messages
|
|
266
|
+
name_attr = attr.to_s.gsub("_xmi_id", "").to_sym
|
|
267
|
+
name_value = begin
|
|
268
|
+
assoc.public_send(name_attr)
|
|
269
|
+
rescue StandardError
|
|
270
|
+
nil
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
warnings << "Association #{assoc.xmi_id} references " \
|
|
274
|
+
"invalid #{name_attr}: #{name_value}"
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Custom validation error
|
|
279
|
+
class ValidationError < Ea::Error; end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Factory
|
|
6
|
+
# Main factory for orchestrating EA to UML transformation
|
|
7
|
+
# Implements Facade pattern for complete model transformation
|
|
8
|
+
class EaToUmlFactory
|
|
9
|
+
attr_reader :database, :options, :resolver
|
|
10
|
+
|
|
11
|
+
# Initialize factory with EA database
|
|
12
|
+
# @param database [Ea::Qea::Database] Loaded EA database
|
|
13
|
+
# @param options [Hash] Transformation options
|
|
14
|
+
# @option options [Boolean] :include_diagrams Include diagrams
|
|
15
|
+
# (default: true)
|
|
16
|
+
# @option options [Boolean] :validate Validate output (default: true)
|
|
17
|
+
# @option options [String] :document_name Document name
|
|
18
|
+
def initialize(database, options = {})
|
|
19
|
+
@database = database
|
|
20
|
+
@options = default_options.merge(options)
|
|
21
|
+
@resolver = ReferenceResolver.new
|
|
22
|
+
@transformers = {}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Create complete UML document from EA database
|
|
26
|
+
# @return [Lutaml::Uml::Document] Complete UML document
|
|
27
|
+
def create_document # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
28
|
+
builder = DocumentBuilder.new(
|
|
29
|
+
name: options[:document_name] || "EA Model",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Transform packages with hierarchy (includes all classes)
|
|
33
|
+
packages = transform_packages
|
|
34
|
+
|
|
35
|
+
# Transform associations (references classes by xmi_id)
|
|
36
|
+
associations = transform_associations
|
|
37
|
+
|
|
38
|
+
# Collect class-level associations from packages
|
|
39
|
+
# class-level associations contain associations with both directions
|
|
40
|
+
# and it may include associations in connector level
|
|
41
|
+
# i.e. owner_end -> member_end and member_end -> owner_end
|
|
42
|
+
class_associations = collect_class_association(packages)
|
|
43
|
+
|
|
44
|
+
# Orphaned classes: EA class objects whose package_id does not
|
|
45
|
+
# resolve to a known package. Attach them to the document root so
|
|
46
|
+
# their associations can resolve.
|
|
47
|
+
orphan_classes = transform_orphan_classes
|
|
48
|
+
|
|
49
|
+
# Build document with both connector-level and
|
|
50
|
+
# class-level associations
|
|
51
|
+
builder.add_packages(packages)
|
|
52
|
+
.add_classes(orphan_classes)
|
|
53
|
+
.add_associations(associations)
|
|
54
|
+
.add_associations(class_associations)
|
|
55
|
+
|
|
56
|
+
# Add diagrams if requested
|
|
57
|
+
if options[:include_diagrams]
|
|
58
|
+
builder.add_diagrams(transform_diagrams)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
builder.build(validate: options[:validate])
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Transform all packages with hierarchy
|
|
65
|
+
# @return [Array<Lutaml::Uml::Package>] Root packages
|
|
66
|
+
def transform_packages # rubocop:disable Metrics/MethodLength
|
|
67
|
+
root_packages = database.packages.select do |pkg|
|
|
68
|
+
pkg.parent_id.nil? || pkg.parent_id.zero?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
package_transformer = get_transformer(:package)
|
|
72
|
+
root_packages.filter_map do |ea_package|
|
|
73
|
+
uml_package = package_transformer.transform_with_hierarchy(
|
|
74
|
+
ea_package,
|
|
75
|
+
include_children: true,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
register_package_hierarchy(uml_package)
|
|
79
|
+
|
|
80
|
+
uml_package
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Transform EA class objects that have no resolvable parent package.
|
|
85
|
+
#
|
|
86
|
+
# EA occasionally stores class rows whose `package_id` does not
|
|
87
|
+
# correspond to any row in `t_package` (typically the result of a
|
|
88
|
+
# deleted package whose children were not reparented). These classes
|
|
89
|
+
# would otherwise be silently dropped, breaking any associations that
|
|
90
|
+
# reference them. Attach them to the document root instead.
|
|
91
|
+
#
|
|
92
|
+
# @return [Array<Lutaml::Uml::UmlClass>] Orphaned UML classes
|
|
93
|
+
def transform_orphan_classes
|
|
94
|
+
class_transformer = get_transformer(:class)
|
|
95
|
+
|
|
96
|
+
orphans = ea_class_objects.reject do |ea_object|
|
|
97
|
+
package_known?(ea_object.package_id)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
uml_classes = class_transformer.transform_collection(orphans)
|
|
101
|
+
uml_classes.each { |uml_class| register_element(uml_class) }
|
|
102
|
+
uml_classes
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# All EA class-like objects (classes and interfaces)
|
|
106
|
+
# @return [Array<Ea::Qea::Models::EaObject>]
|
|
107
|
+
def ea_class_objects
|
|
108
|
+
database.objects.find_by_type("Class") +
|
|
109
|
+
database.objects.find_by_type("Interface")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Whether a package_id resolves to a known package in t_package
|
|
113
|
+
# @param package_id [Integer, nil]
|
|
114
|
+
# @return [Boolean]
|
|
115
|
+
def package_known?(package_id)
|
|
116
|
+
return false if package_id.nil?
|
|
117
|
+
|
|
118
|
+
!database.find_package(package_id).nil?
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Transform all associations
|
|
122
|
+
# @return [Array<Lutaml::Uml::Association>] All UML associations
|
|
123
|
+
def transform_associations # rubocop:disable Metrics/MethodLength
|
|
124
|
+
association_transformer = get_transformer(:association)
|
|
125
|
+
|
|
126
|
+
ea_associations = database.connectors.select(&:association?)
|
|
127
|
+
|
|
128
|
+
uml_associations = association_transformer.transform_collection(
|
|
129
|
+
ea_associations,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
uml_associations.each do |uml_assoc|
|
|
133
|
+
register_element(uml_assoc)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
uml_associations
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Transform all diagrams
|
|
140
|
+
# @return [Array<Lutaml::Uml::Diagram>] All UML diagrams
|
|
141
|
+
def transform_diagrams
|
|
142
|
+
diagram_transformer = get_transformer(:diagram)
|
|
143
|
+
diagram_transformer.transform_collection(database.diagrams)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Register custom transformers, overriding registry defaults
|
|
147
|
+
# @param transformers [Hash] Custom transformer instances keyed by type
|
|
148
|
+
# @return [self] For method chaining
|
|
149
|
+
def with_transformers(transformers)
|
|
150
|
+
@transformers.merge!(transformers)
|
|
151
|
+
self
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private
|
|
155
|
+
|
|
156
|
+
def default_options
|
|
157
|
+
{
|
|
158
|
+
include_diagrams: true,
|
|
159
|
+
validate: true,
|
|
160
|
+
document_name: "EA Model",
|
|
161
|
+
}
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Get or create transformer by type using the TransformerRegistry
|
|
165
|
+
# @param type [Symbol] Transformer type
|
|
166
|
+
# @return [BaseTransformer] Transformer instance
|
|
167
|
+
def get_transformer(type)
|
|
168
|
+
return @transformers[type] if @transformers.key?(type)
|
|
169
|
+
|
|
170
|
+
transformer_class = TransformerRegistry.transformer_for(type)
|
|
171
|
+
unless transformer_class
|
|
172
|
+
raise ArgumentError, "Unknown transformer type: #{type}"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
@transformers[type] = transformer_class.new(database)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def register_package_hierarchy(package)
|
|
179
|
+
return if package.nil?
|
|
180
|
+
|
|
181
|
+
register_element(package)
|
|
182
|
+
register_package_members(package)
|
|
183
|
+
|
|
184
|
+
package.packages&.each do |child_package|
|
|
185
|
+
register_package_hierarchy(child_package)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
MEMBER_COLLECTIONS = %i[classes enums data_types instances].freeze
|
|
190
|
+
|
|
191
|
+
def register_package_members(package)
|
|
192
|
+
MEMBER_COLLECTIONS.each do |collection|
|
|
193
|
+
package.public_send(collection)&.each do |elem|
|
|
194
|
+
register_element(elem)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def register_element(element)
|
|
200
|
+
return if element.nil? || element.xmi_id.nil?
|
|
201
|
+
|
|
202
|
+
@resolver.register(element.xmi_id, element)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def collect_class_association(packages)
|
|
206
|
+
associations = []
|
|
207
|
+
|
|
208
|
+
packages.each do |package|
|
|
209
|
+
collect_package_associations(package, associations)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
associations
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def collect_package_associations(package, associations) # rubocop:disable Metrics/CyclomaticComplexity
|
|
216
|
+
package.classes&.each do |klass|
|
|
217
|
+
if klass.associations && !klass.associations.empty?
|
|
218
|
+
associations.concat(klass.associations)
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
package.packages&.each do |child_package|
|
|
223
|
+
collect_package_associations(child_package, associations)
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Factory
|
|
6
|
+
# Transforms EA objects (Enumeration type) to UML enums
|
|
7
|
+
class EnumTransformer < BaseTransformer
|
|
8
|
+
# Transform EA object to UML enum
|
|
9
|
+
# @param ea_object [EaObject] EA object model
|
|
10
|
+
# @return [Lutaml::Uml::Enum] UML enum
|
|
11
|
+
def transform(ea_object)
|
|
12
|
+
return nil if ea_object.nil?
|
|
13
|
+
return nil unless enum?(ea_object)
|
|
14
|
+
|
|
15
|
+
Lutaml::Uml::Enum.new.tap do |enum|
|
|
16
|
+
assign_enum_basic(enum, ea_object)
|
|
17
|
+
assign_enum_features(enum, ea_object)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def assign_enum_basic(enum, ea_object)
|
|
22
|
+
enum.name = ea_object.name
|
|
23
|
+
enum.xmi_id = normalize_guid_to_xmi_format(ea_object.ea_guid,
|
|
24
|
+
"EAID")
|
|
25
|
+
enum.visibility = map_visibility(ea_object.visibility)
|
|
26
|
+
enum.stereotype = [ea_object.stereotype] if valid_stereotype?(ea_object)
|
|
27
|
+
enum.definition = ea_object.note if note_present?(ea_object)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def note_present?(ea_object)
|
|
31
|
+
ea_object.note && !ea_object.note.empty?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def assign_enum_features(enum, ea_object)
|
|
35
|
+
enum.values = load_enum_values(ea_object.ea_object_id)
|
|
36
|
+
enum.tagged_values = load_tagged_values(ea_object.ea_guid)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def enum?(ea_object)
|
|
42
|
+
ea_object.enumeration? ||
|
|
43
|
+
(ea_object.stereotype && ea_object.stereotype.downcase == "enumeration")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def valid_stereotype?(ea_object)
|
|
47
|
+
ea_object.stereotype && !ea_object.stereotype.empty?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Load enum values (literals) from attributes
|
|
51
|
+
# @param object_id [Integer] Object ID
|
|
52
|
+
# @return [Array<Lutaml::Uml::Value>] Enum values
|
|
53
|
+
def load_enum_values(object_id)
|
|
54
|
+
return [] if object_id.nil?
|
|
55
|
+
|
|
56
|
+
ea_attrs = database.attributes_for_object(object_id)
|
|
57
|
+
.sort_by { |a| a.pos || 0 }
|
|
58
|
+
|
|
59
|
+
ea_attrs.filter_map { |ea_attr| build_enum_value(ea_attr) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def build_enum_value(ea_attr)
|
|
63
|
+
Lutaml::Uml::Value.new.tap do |value|
|
|
64
|
+
value.name = ea_attr.name
|
|
65
|
+
value.id = normalize_guid_to_xmi_format(ea_attr.ea_guid, "EAID")
|
|
66
|
+
value.definition = ea_attr.notes unless
|
|
67
|
+
ea_attr.notes.nil? || ea_attr.notes.empty?
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|