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,607 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "xmi"
|
|
4
|
+
|
|
5
|
+
module Ea
|
|
6
|
+
module Transformers
|
|
7
|
+
module QeaToXmi
|
|
8
|
+
# Orchestrates serialization of an Ea::Qea::Database to Sparx XMI.
|
|
9
|
+
#
|
|
10
|
+
# Walks the package tree starting at root packages, constructing
|
|
11
|
+
# xmi gem models (Xmi::Uml::UmlModel, Xmi::Uml::PackagedElement,
|
|
12
|
+
# Xmi::Uml::OwnedAttribute, Xmi::Uml::OwnedEnd, etc.) from each
|
|
13
|
+
# QEA row, then asks the xmi gem to render them via
|
|
14
|
+
# `to_xml(use_prefix: true)` to produce Sparx XMI in the canonical
|
|
15
|
+
# mixed-prefix style. The serialized output is run through
|
|
16
|
+
# {XmlSanitizer} to strip truly-empty elements that the xmi gem's
|
|
17
|
+
# round-trip-oriented VALUE_MAP emits but Sparx XMI does not.
|
|
18
|
+
#
|
|
19
|
+
# Element-kind dispatch (Class vs Enumeration vs DataType vs
|
|
20
|
+
# Instance) is registry-driven — see CLASSIFIER_BUILDERS. Adding
|
|
21
|
+
# a new kind = adding one entry to that hash, no method change.
|
|
22
|
+
# Polymorphism for XMI element shape lives in the xmi gem's models
|
|
23
|
+
# (xmi:type discriminator on PackagedElement), not here.
|
|
24
|
+
#
|
|
25
|
+
# This is the FULL-FIDELITY path — no Lutaml::Uml::Document
|
|
26
|
+
# intermediate. Sparx-specific concepts (multiplicities, tagged
|
|
27
|
+
# values, stereotypes, primitive types, instance specifications,
|
|
28
|
+
# association ends) come straight from the QEA tables.
|
|
29
|
+
#
|
|
30
|
+
# Phase 2 will extend the xmi gem with visibility / isAbstract /
|
|
31
|
+
# classifier / aggregation attributes that the QEA database
|
|
32
|
+
# contains but the xmi gem's models don't yet declare.
|
|
33
|
+
class Transformer
|
|
34
|
+
MODEL_NAME = "EA_Model"
|
|
35
|
+
EXPORTER = "Enterprise Architect"
|
|
36
|
+
EXPORTER_VERSION = "6.5"
|
|
37
|
+
|
|
38
|
+
RELATIONSHIP_AT_PACKAGE_LEVEL = {
|
|
39
|
+
"Association" => :association,
|
|
40
|
+
"Aggregation" => :association,
|
|
41
|
+
"Composition" => :association,
|
|
42
|
+
"Dependency" => :dependency,
|
|
43
|
+
"Usage" => :dependency,
|
|
44
|
+
}.freeze
|
|
45
|
+
|
|
46
|
+
# OCP registry: maps EaObject#transformer_type to the builder
|
|
47
|
+
# that constructs the corresponding Xmi::Uml element. To add a
|
|
48
|
+
# new element kind (Signal, Interface, ...), append one entry
|
|
49
|
+
# here — `build_classifier` requires no edit.
|
|
50
|
+
#
|
|
51
|
+
# Builders are lambdas evaluated via `instance_exec`, so they
|
|
52
|
+
# run inside the Transformer instance and can call its private
|
|
53
|
+
# helpers without `send`/`public_send` dispatch.
|
|
54
|
+
CLASSIFIER_BUILDERS = {
|
|
55
|
+
class: ->(obj) { build_class(obj) },
|
|
56
|
+
enumeration: ->(obj) { build_enumeration(obj) },
|
|
57
|
+
data_type: ->(obj) { build_data_type(obj) },
|
|
58
|
+
instance: ->(obj) { build_instance(obj) },
|
|
59
|
+
}.freeze
|
|
60
|
+
|
|
61
|
+
def initialize(database)
|
|
62
|
+
@database = database
|
|
63
|
+
@context = Context.new(database: database)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# @return [String] XMI XML document
|
|
67
|
+
#
|
|
68
|
+
# The xmi gem's VALUE_MAP is generation-friendly
|
|
69
|
+
# (`to: { nil: :omitted, ... }`), so empty collections and
|
|
70
|
+
# nil-valued attributes are skipped at the source. No
|
|
71
|
+
# post-processing pass is needed — the prior XmlSanitizer
|
|
72
|
+
# workaround (TODO 21 §1) has been removed.
|
|
73
|
+
def serialize
|
|
74
|
+
build_root.to_xml(use_prefix: true)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
# ---- Top-level construction --------------------------------------
|
|
80
|
+
|
|
81
|
+
def build_root
|
|
82
|
+
::Xmi::Sparx::Root.new(
|
|
83
|
+
documentation: build_documentation,
|
|
84
|
+
model: build_model,
|
|
85
|
+
extension: build_extension,
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def build_documentation
|
|
90
|
+
::Xmi::Documentation.new(
|
|
91
|
+
exporter: EXPORTER,
|
|
92
|
+
exporter_version: EXPORTER_VERSION,
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def build_model
|
|
97
|
+
::Xmi::Uml::UmlModel.new(
|
|
98
|
+
type: "uml:Model",
|
|
99
|
+
name: MODEL_NAME,
|
|
100
|
+
packaged_element: root_packages.map { |pkg| build_package(pkg) },
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def build_extension
|
|
105
|
+
::Xmi::Sparx::Extension.new(extender: EXPORTER)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def root_packages
|
|
109
|
+
@database.packages
|
|
110
|
+
.select(&:root?)
|
|
111
|
+
.sort_by { |p| [p.tpos || 0, p.name.to_s] }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# ---- Package tree ------------------------------------------------
|
|
115
|
+
|
|
116
|
+
def build_package(pkg)
|
|
117
|
+
::Xmi::Uml::PackagedElement.new(
|
|
118
|
+
type: "uml:Package",
|
|
119
|
+
id: @context.xmi_id_for(pkg, prefix: "EAPK"),
|
|
120
|
+
name: pkg.name,
|
|
121
|
+
owned_comment: package_comments(pkg),
|
|
122
|
+
packaged_element: package_children(pkg),
|
|
123
|
+
)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def package_children(pkg)
|
|
127
|
+
[
|
|
128
|
+
*subpackages(pkg),
|
|
129
|
+
*classifier_objects(pkg),
|
|
130
|
+
*package_level_relationships(pkg),
|
|
131
|
+
].compact
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def subpackages(pkg)
|
|
135
|
+
sorted_by_position(@context.child_packages(pkg.package_id))
|
|
136
|
+
.map { |sub| build_package(sub) }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def classifier_objects(pkg)
|
|
140
|
+
classifiers_in(pkg).map { |obj| build_classifier(obj) }.compact
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Connectors owned by this package: connectors whose start_object is
|
|
144
|
+
# a classifier in this package and whose type is a package-level
|
|
145
|
+
# relationship (Association / Dependency). Generalization and
|
|
146
|
+
# Realization are emitted inside the classifier itself.
|
|
147
|
+
def package_level_relationships(pkg)
|
|
148
|
+
emitted = Set.new
|
|
149
|
+
classifiers_in(pkg).flat_map do |obj|
|
|
150
|
+
@context.connectors_starting_at(obj.ea_object_id).filter_map do |conn|
|
|
151
|
+
key = RELATIONSHIP_AT_PACKAGE_LEVEL[conn.connector_type]
|
|
152
|
+
next nil unless key
|
|
153
|
+
next nil unless emitted.add?(conn.connector_id)
|
|
154
|
+
|
|
155
|
+
build_package_relationship(key, conn)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def build_package_relationship(kind, conn)
|
|
161
|
+
case kind
|
|
162
|
+
when :association then build_association(conn)
|
|
163
|
+
when :dependency then build_dependency(conn)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def package_comments(pkg)
|
|
168
|
+
notes_in(pkg).map { |obj| build_comment(obj) }
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# ---- Classifier dispatch (OCP registry) --------------------------
|
|
172
|
+
|
|
173
|
+
def build_classifier(obj)
|
|
174
|
+
kind = obj.transformer_type || obj.object_type&.downcase&.to_sym
|
|
175
|
+
builder = CLASSIFIER_BUILDERS[kind]
|
|
176
|
+
return nil unless builder
|
|
177
|
+
|
|
178
|
+
instance_exec(obj, &builder)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def build_class(obj)
|
|
182
|
+
::Xmi::Uml::PackagedElement.new(
|
|
183
|
+
type: class_xmi_type(obj),
|
|
184
|
+
id: @context.xmi_id_for(obj),
|
|
185
|
+
name: obj.name,
|
|
186
|
+
visibility: Visibility.from_scope(obj.scope),
|
|
187
|
+
is_abstract: Visibility.boolean_from_flag(obj.abstract),
|
|
188
|
+
generalization: generalizations_for(obj),
|
|
189
|
+
interface_realization: interface_realizations_for(obj),
|
|
190
|
+
owned_attribute: attributes_for(obj),
|
|
191
|
+
owned_operation: operations_for(obj),
|
|
192
|
+
)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def build_enumeration(obj)
|
|
196
|
+
::Xmi::Uml::PackagedElement.new(
|
|
197
|
+
type: "uml:Enumeration",
|
|
198
|
+
id: @context.xmi_id_for(obj),
|
|
199
|
+
name: obj.name,
|
|
200
|
+
visibility: Visibility.from_scope(obj.scope),
|
|
201
|
+
owned_literal: enum_literals(obj),
|
|
202
|
+
)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def build_data_type(obj)
|
|
206
|
+
::Xmi::Uml::PackagedElement.new(
|
|
207
|
+
type: primitive?(obj) ? "uml:PrimitiveType" : "uml:DataType",
|
|
208
|
+
id: @context.xmi_id_for(obj),
|
|
209
|
+
name: obj.name,
|
|
210
|
+
visibility: Visibility.from_scope(obj.scope),
|
|
211
|
+
owned_attribute: attributes_for(obj),
|
|
212
|
+
owned_operation: operations_for(obj),
|
|
213
|
+
)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def build_instance(obj)
|
|
217
|
+
::Xmi::Uml::PackagedElement.new(
|
|
218
|
+
type: "uml:InstanceSpecification",
|
|
219
|
+
id: @context.xmi_id_for(obj),
|
|
220
|
+
name: obj.name,
|
|
221
|
+
visibility: Visibility.from_scope(obj.scope),
|
|
222
|
+
classifier: classifier_ref_for(obj),
|
|
223
|
+
slot: slots_for(obj),
|
|
224
|
+
)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def build_comment(obj)
|
|
228
|
+
::Xmi::Uml::OwnedComment.new(
|
|
229
|
+
type: "uml:Comment",
|
|
230
|
+
id: @context.xmi_id_for(obj),
|
|
231
|
+
body_element: obj.note || obj.name || "",
|
|
232
|
+
)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# ---- Children of a classifier ------------------------------------
|
|
236
|
+
|
|
237
|
+
def generalizations_for(obj)
|
|
238
|
+
inheritance_connectors(obj, "Generalization")
|
|
239
|
+
.filter_map { |conn| build_generalization(conn) }
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Realization connectors point from a Class (client) to an
|
|
243
|
+
# Interface (supplier). Sparx's strict OMG output emits these
|
|
244
|
+
# as `<interfaceRealization>` children of the client class
|
|
245
|
+
# rather than as package-level `<packagedElement type=
|
|
246
|
+
# "uml:Realization">`. We do the same.
|
|
247
|
+
def interface_realizations_for(obj)
|
|
248
|
+
realization_connectors(obj).map { |conn| build_interface_realization(conn) }
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def attributes_for(obj)
|
|
252
|
+
sorted_by_position(@context.attributes_for(obj.ea_object_id))
|
|
253
|
+
.map { |attr| build_attribute(attr) }
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def operations_for(obj)
|
|
257
|
+
sorted_by_position(@context.operations_for(obj.ea_object_id))
|
|
258
|
+
.map { |op| build_operation(op) }
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def enum_literals(obj)
|
|
262
|
+
sorted_by_position(@context.attributes_for(obj.ea_object_id))
|
|
263
|
+
.map { |attr| build_owned_literal(attr) }
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# ---- Leaf element builders --------------------------------------
|
|
267
|
+
|
|
268
|
+
def build_attribute(attr)
|
|
269
|
+
parent_guid = parent_guid_for_attribute(attr)
|
|
270
|
+
::Xmi::Uml::OwnedAttribute.new(
|
|
271
|
+
type: "uml:Property",
|
|
272
|
+
id: @context.xmi_id_for(attr),
|
|
273
|
+
name: attr.name,
|
|
274
|
+
visibility: Visibility.from_scope(attr.scope),
|
|
275
|
+
is_static: Visibility.boolean_from_flag(attr.isstatic),
|
|
276
|
+
is_ordered: Visibility.boolean_from_flag(attr.isordered),
|
|
277
|
+
is_derived: Visibility.boolean_from_flag(attr.derived),
|
|
278
|
+
uml_type: type_reference_model(attr.type, attr.classifier),
|
|
279
|
+
upper_value: build_upper_value(attr.upperbound, seed: "mult-attr-#{attr.id}-upper", parent_guid: parent_guid),
|
|
280
|
+
lower_value: build_lower_value(attr.lowerbound, seed: "mult-attr-#{attr.id}-lower", parent_guid: parent_guid),
|
|
281
|
+
)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def build_operation(op)
|
|
285
|
+
::Xmi::Uml::OwnedOperation.new(
|
|
286
|
+
id: @context.xmi_id_for(op),
|
|
287
|
+
name: op.name,
|
|
288
|
+
visibility: Visibility.from_scope(op.scope),
|
|
289
|
+
is_static: Visibility.boolean_from_flag(op.isstatic),
|
|
290
|
+
is_abstract: Visibility.boolean_from_flag(op.abstract),
|
|
291
|
+
is_query: Visibility.boolean_from_flag(op.pure),
|
|
292
|
+
concurrency: op.concurrency&.downcase,
|
|
293
|
+
owned_parameter: operation_parameters(op),
|
|
294
|
+
)
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def build_owned_literal(attr)
|
|
298
|
+
::Xmi::Uml::OwnedLiteral.new(
|
|
299
|
+
type: "uml:EnumerationLiteral",
|
|
300
|
+
id: @context.xmi_id_for(attr),
|
|
301
|
+
name: attr.name,
|
|
302
|
+
)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def build_generalization(conn)
|
|
306
|
+
parent = @context.object_by_id(conn.end_object_id)
|
|
307
|
+
return nil unless parent
|
|
308
|
+
|
|
309
|
+
::Xmi::Uml::AssociationGeneralization.new(
|
|
310
|
+
type: "uml:Generalization",
|
|
311
|
+
id: @context.xmi_id_for(conn),
|
|
312
|
+
general: @context.xmi_id_for(parent),
|
|
313
|
+
)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def build_interface_realization(conn)
|
|
317
|
+
supplier = @context.object_by_id(conn.end_object_id)
|
|
318
|
+
::Xmi::Uml::InterfaceRealization.new(
|
|
319
|
+
type: "uml:InterfaceRealization",
|
|
320
|
+
id: @context.xmi_id_for(conn),
|
|
321
|
+
name: conn.name,
|
|
322
|
+
client: @context.xmi_id_for(conn.start_object_id),
|
|
323
|
+
supplier: supplier ? @context.xmi_id_for(supplier) : nil,
|
|
324
|
+
contract: supplier ? @context.xmi_id_for(supplier) : nil,
|
|
325
|
+
)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def build_dependency(conn)
|
|
329
|
+
client = @context.object_by_id(conn.start_object_id)
|
|
330
|
+
supplier = @context.object_by_id(conn.end_object_id)
|
|
331
|
+
return nil unless client && supplier
|
|
332
|
+
|
|
333
|
+
::Xmi::Uml::PackagedElement.new(
|
|
334
|
+
type: "uml:Dependency",
|
|
335
|
+
id: @context.xmi_id_for(conn),
|
|
336
|
+
client: @context.xmi_id_for(client),
|
|
337
|
+
supplier: @context.xmi_id_for(supplier),
|
|
338
|
+
)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Sparx serialisation order for `uml:Association` is
|
|
342
|
+
# destination member-end first, source member-end second.
|
|
343
|
+
# Reordering these breaks round-trip fidelity with Sparx EA
|
|
344
|
+
# (the importer treats the first member-end as the destination
|
|
345
|
+
# role). Don't reorder without verifying against a Sparx
|
|
346
|
+
# round-trip fixture.
|
|
347
|
+
def build_association(conn)
|
|
348
|
+
dest_end = build_association_end(conn, side: :destination)
|
|
349
|
+
src_end = build_association_end(conn, side: :source)
|
|
350
|
+
|
|
351
|
+
::Xmi::Uml::PackagedElement.new(
|
|
352
|
+
type: "uml:Association",
|
|
353
|
+
id: @context.xmi_id_for(conn),
|
|
354
|
+
name: conn.name,
|
|
355
|
+
member_ends: [
|
|
356
|
+
::Xmi::Uml::MemberEnd.new(idref: dest_end.xmi_id),
|
|
357
|
+
::Xmi::Uml::MemberEnd.new(idref: src_end.xmi_id),
|
|
358
|
+
],
|
|
359
|
+
owned_end: [dest_end.model, src_end.model],
|
|
360
|
+
)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def build_association_end(conn, side:)
|
|
364
|
+
end_id = @context.end_xmi_id_for(@context.xmi_id_for(conn), side: side)
|
|
365
|
+
target_id = side == :source ? conn.start_object_id : conn.end_object_id
|
|
366
|
+
target_obj = @context.object_by_id(target_id)
|
|
367
|
+
target_ref = target_obj ? @context.xmi_id_for(target_obj) : nil
|
|
368
|
+
bounds = Cardinality.parse(cardinality_for(conn, side))
|
|
369
|
+
containment = containment_for(conn, side)
|
|
370
|
+
|
|
371
|
+
model = ::Xmi::Uml::OwnedEnd.new(
|
|
372
|
+
type: "uml:Property",
|
|
373
|
+
id: end_id,
|
|
374
|
+
name: role_name_for(conn, side),
|
|
375
|
+
visibility: visibility_for_end(conn, side),
|
|
376
|
+
aggregation: Visibility.aggregation_from_containment(containment),
|
|
377
|
+
association: @context.xmi_id_for(conn),
|
|
378
|
+
uml_type: target_ref ? ::Xmi::Uml::Type.new(idref: target_ref) : nil,
|
|
379
|
+
upper_value: build_upper_value(bounds[:upper], seed: "mult-#{conn.connector_id}-#{side}-upper", parent_guid: conn.ea_guid),
|
|
380
|
+
lower_value: build_lower_value(bounds[:lower], seed: "mult-#{conn.connector_id}-#{side}-lower", parent_guid: conn.ea_guid),
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
AssociationEnd.new(end_id, model)
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# ---- Multiplicity helpers ---------------------------------------
|
|
387
|
+
|
|
388
|
+
# Always emit both bounds — UML defaults (lower=0, upper=-1) are
|
|
389
|
+
# used when the EA field is blank. Matches real Sparx XMI, which
|
|
390
|
+
# never omits `<upperValue>`/`<lowerValue>` on a Property.
|
|
391
|
+
def build_upper_value(raw, seed:, parent_guid:)
|
|
392
|
+
::Xmi::Uml::UpperValue.new(
|
|
393
|
+
type: "uml:LiteralUnlimitedNatural",
|
|
394
|
+
id: @context.id_allocator.allocate(
|
|
395
|
+
prefix: IdAllocator::LITERAL_INTEGER,
|
|
396
|
+
seed: seed,
|
|
397
|
+
parent_guid: parent_guid,
|
|
398
|
+
),
|
|
399
|
+
value: Cardinality.normalize_upper(raw),
|
|
400
|
+
)
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def build_lower_value(raw, seed:, parent_guid:)
|
|
404
|
+
::Xmi::Uml::LowerValue.new(
|
|
405
|
+
type: "uml:LiteralInteger",
|
|
406
|
+
id: @context.id_allocator.allocate(
|
|
407
|
+
prefix: IdAllocator::LITERAL_INTEGER,
|
|
408
|
+
seed: seed,
|
|
409
|
+
parent_guid: parent_guid,
|
|
410
|
+
),
|
|
411
|
+
value: Cardinality.normalize_lower(raw),
|
|
412
|
+
)
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# ---- Operation parameters ---------------------------------------
|
|
416
|
+
|
|
417
|
+
def operation_parameters(op)
|
|
418
|
+
params = sorted_by_position(@context.params_for_operation(op.operationid))
|
|
419
|
+
.reject(&:return?)
|
|
420
|
+
.map { |p| build_owned_parameter(p) }
|
|
421
|
+
params << build_return_parameter(op) if op.type && !op.type.empty?
|
|
422
|
+
params
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def build_owned_parameter(param)
|
|
426
|
+
::Xmi::Uml::OwnedParameter.new(
|
|
427
|
+
id: @context.xmi_id_for(param),
|
|
428
|
+
name: param.name,
|
|
429
|
+
direction: param.kind&.downcase,
|
|
430
|
+
)
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def build_return_parameter(op)
|
|
434
|
+
::Xmi::Uml::OwnedParameter.new(
|
|
435
|
+
id: @context.id_allocator.allocate(
|
|
436
|
+
prefix: IdAllocator::RETURN_PARAMETER,
|
|
437
|
+
seed: "return-#{op.operationid}",
|
|
438
|
+
parent_guid: op.ea_guid,
|
|
439
|
+
),
|
|
440
|
+
name: "return",
|
|
441
|
+
direction: "return",
|
|
442
|
+
)
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
# ---- Shared helpers ---------------------------------------------
|
|
446
|
+
|
|
447
|
+
def class_xmi_type(obj)
|
|
448
|
+
obj.interface? ? "uml:Interface" : "uml:Class"
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
def inheritance_connectors(obj, type)
|
|
452
|
+
@context.connectors_for(obj.ea_object_id).select do |conn|
|
|
453
|
+
conn.start_object_id == obj.ea_object_id && conn.connector_type == type
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
# Realization connectors owned by this class — those where
|
|
458
|
+
# this object is the source (client) and the connector type
|
|
459
|
+
# is Realization. Each emits an `<interfaceRealization>`.
|
|
460
|
+
def realization_connectors(obj)
|
|
461
|
+
inheritance_connectors(obj, "Realization")
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def classifiers_in(pkg)
|
|
465
|
+
@context.objects_in_package(pkg.package_id)
|
|
466
|
+
.reject { |o| note?(o) || package_object?(o) }
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def notes_in(pkg)
|
|
470
|
+
@context.objects_in_package(pkg.package_id).select { |o| note?(o) }
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
def note?(obj)
|
|
474
|
+
obj.object_type == "Note" || obj.object_type == "Text"
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
def package_object?(obj)
|
|
478
|
+
obj.object_type == "Package"
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
def sorted_by_position(records)
|
|
482
|
+
records.sort_by { |r| [r.sort_position, r.name.to_s] }
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
def primitive?(obj)
|
|
486
|
+
obj.object_type == "PrimitiveType" ||
|
|
487
|
+
(obj.gentype == "Java" && obj.stereotype_is?("primitive"))
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
def type_reference(type_name, classifier_guid)
|
|
491
|
+
return nil if type_name.nil? || type_name.empty?
|
|
492
|
+
|
|
493
|
+
if classifier_guid
|
|
494
|
+
GuidFormat.ea_guid_to_xmi_id(classifier_guid)
|
|
495
|
+
else
|
|
496
|
+
"EAnone_#{type_name}"
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def type_reference_model(type_name, classifier_guid)
|
|
501
|
+
ref = type_reference(type_name, classifier_guid)
|
|
502
|
+
ref ? ::Xmi::Uml::Type.new(idref: ref) : nil
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
def cardinality_for(conn, side)
|
|
506
|
+
side == :source ? conn.sourcecard : conn.destcard
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
def role_name_for(conn, side)
|
|
510
|
+
side == :source ? conn.sourcerole : conn.destrole
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
def containment_for(conn, side)
|
|
514
|
+
side == :source ? conn.sourcecontainment : conn.destcontainment
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
# EA's t_connector does not expose per-end visibility (the
|
|
518
|
+
# source/dest scopes are stored only on the role's target
|
|
519
|
+
# object, which has its own visibility). Leave ownedEnd
|
|
520
|
+
# visibility unset unless a future schema change exposes it.
|
|
521
|
+
def visibility_for_end(_conn, _side)
|
|
522
|
+
nil
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
# The owning element for an attribute's synthesised IDs is the
|
|
526
|
+
# attribute's classifier (parent object), not the attribute
|
|
527
|
+
# itself — Sparx encodes the parent class GUID in the suffix.
|
|
528
|
+
def parent_guid_for_attribute(attr)
|
|
529
|
+
parent = @context.object_by_id(attr.ea_object_id)
|
|
530
|
+
parent&.ea_guid
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
# InstanceSpecification classifier reference. EA stores this
|
|
534
|
+
# directly in t_object.classifier as the classifier's
|
|
535
|
+
# ea_object_id (NOT pdata1, which is a separate column used
|
|
536
|
+
# for other purposes on InstanceSpecification rows).
|
|
537
|
+
def classifier_ref_for(obj)
|
|
538
|
+
classifier_id = obj.classifier.to_i
|
|
539
|
+
return nil if classifier_id.zero?
|
|
540
|
+
|
|
541
|
+
classifier = @context.object_by_id(classifier_id)
|
|
542
|
+
classifier ? @context.xmi_id_for(classifier) : nil
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
# InstanceSpecification slots, parsed from EA's RunState column.
|
|
546
|
+
# Each `@VAR;Variable=<name>;Value=<v>;Op=<op>;@ENDVAR;` block
|
|
547
|
+
# becomes one UML Slot with an OpaqueExpression value. The
|
|
548
|
+
# `definingFeature` is resolved by looking up the named
|
|
549
|
+
# attribute on the instance's classifier.
|
|
550
|
+
def slots_for(obj)
|
|
551
|
+
RunState.parse(obj.runstate).map do |binding|
|
|
552
|
+
build_slot(obj, binding)
|
|
553
|
+
end
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
def build_slot(instance, binding)
|
|
557
|
+
::Xmi::Uml::Slot.new(
|
|
558
|
+
type: "uml:Slot",
|
|
559
|
+
id: slot_id_for(instance, binding),
|
|
560
|
+
defining_feature: defining_feature_for(instance, binding),
|
|
561
|
+
value: [build_slot_value(instance, binding)],
|
|
562
|
+
)
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
def build_slot_value(instance, binding)
|
|
566
|
+
::Xmi::Uml::OpaqueExpression.new(
|
|
567
|
+
type: "uml:OpaqueExpression",
|
|
568
|
+
id: opaque_expression_id_for(instance, binding),
|
|
569
|
+
body_attribute: binding.body,
|
|
570
|
+
)
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
def slot_id_for(instance, binding)
|
|
574
|
+
@context.id_allocator.allocate(
|
|
575
|
+
prefix: IdAllocator::SLOT,
|
|
576
|
+
seed: "slot-#{instance.ea_object_id}-#{binding.variable}",
|
|
577
|
+
parent_guid: instance.ea_guid,
|
|
578
|
+
)
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
def opaque_expression_id_for(instance, binding)
|
|
582
|
+
@context.id_allocator.allocate(
|
|
583
|
+
prefix: IdAllocator::OPAQUE_EXPRESSION,
|
|
584
|
+
seed: "oe-#{instance.ea_object_id}-#{binding.variable}",
|
|
585
|
+
parent_guid: instance.ea_guid,
|
|
586
|
+
)
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
# Resolve the definingFeature EAID for a RunState binding by
|
|
590
|
+
# looking up the named attribute on the instance's classifier.
|
|
591
|
+
# Returns nil when the classifier or attribute can't be found —
|
|
592
|
+
# the slot still emits, just without the definingFeature ref.
|
|
593
|
+
def defining_feature_for(instance, binding)
|
|
594
|
+
classifier_id = instance.classifier.to_i
|
|
595
|
+
return nil if classifier_id.zero?
|
|
596
|
+
|
|
597
|
+
classifier = @context.object_by_id(classifier_id)
|
|
598
|
+
return nil unless classifier
|
|
599
|
+
|
|
600
|
+
attr = @context.attributes_for(classifier.ea_object_id)
|
|
601
|
+
.find { |a| a.name == binding.variable }
|
|
602
|
+
attr ? @context.xmi_id_for(attr) : nil
|
|
603
|
+
end
|
|
604
|
+
end
|
|
605
|
+
end
|
|
606
|
+
end
|
|
607
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Transformers
|
|
5
|
+
module QeaToXmi
|
|
6
|
+
# Pure-function mapper from EA's integer scope/containment codes
|
|
7
|
+
# to UML visibility / aggregation wire strings.
|
|
8
|
+
#
|
|
9
|
+
# EA stores visibility as an integer in `t_attribute.scope`,
|
|
10
|
+
# `t_operation.scope`, `t_object.scope`. The encoding:
|
|
11
|
+
#
|
|
12
|
+
# 0 → Public
|
|
13
|
+
# 1 → Private
|
|
14
|
+
# 2 → Protected
|
|
15
|
+
# 3 → Package
|
|
16
|
+
#
|
|
17
|
+
# EA stores aggregation kind in `t_connector.sourcecontainment`
|
|
18
|
+
# and `t_connector.destcontainment`. The encoding:
|
|
19
|
+
#
|
|
20
|
+
# 0 → None (omitted)
|
|
21
|
+
# 1 → Shared (UML aggregation="shared")
|
|
22
|
+
# 2 → Composite (UML aggregation="composite")
|
|
23
|
+
#
|
|
24
|
+
# Wire-side values are lower-case per the UML XMI schema.
|
|
25
|
+
module Visibility
|
|
26
|
+
SCOPE_MAP = {
|
|
27
|
+
0 => "public",
|
|
28
|
+
1 => "private",
|
|
29
|
+
2 => "protected",
|
|
30
|
+
3 => "package",
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
AGGREGATION_MAP = {
|
|
34
|
+
0 => nil,
|
|
35
|
+
1 => "shared",
|
|
36
|
+
2 => "composite",
|
|
37
|
+
}.freeze
|
|
38
|
+
|
|
39
|
+
module_function
|
|
40
|
+
|
|
41
|
+
# @param raw [String, Integer, nil]
|
|
42
|
+
# @return [String, nil] UML visibility token, or nil if EA's
|
|
43
|
+
# scope field is blank / unrecognised.
|
|
44
|
+
def from_scope(raw)
|
|
45
|
+
return nil if raw.nil? || raw.to_s.strip.empty?
|
|
46
|
+
|
|
47
|
+
key = raw.to_i
|
|
48
|
+
SCOPE_MAP[key]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @param raw [String, Integer, nil]
|
|
52
|
+
# @return [String, nil] UML aggregation token, or nil if EA's
|
|
53
|
+
# containment field indicates no aggregation.
|
|
54
|
+
def aggregation_from_containment(raw)
|
|
55
|
+
return nil if raw.nil? || raw.to_s.strip.empty?
|
|
56
|
+
|
|
57
|
+
key = raw.to_i
|
|
58
|
+
AGGREGATION_MAP[key]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @param raw [String, Integer, nil] EA's abstract flag ("1"/"0")
|
|
62
|
+
# @return [Boolean, nil] true / false, or nil if unspecified.
|
|
63
|
+
# Returns actual Ruby booleans (not strings) — the xmi gem's
|
|
64
|
+
# `is_*` attributes are typed as `:boolean`.
|
|
65
|
+
def boolean_from_flag(raw)
|
|
66
|
+
return nil if raw.nil? || raw.to_s.strip.empty?
|
|
67
|
+
|
|
68
|
+
raw.to_s == "1"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|