ea 0.5.1 → 0.6.0
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/TODO.complete/01-t-xref-parsing.md +1 -1
- data/TODO.complete/02-per-label-geometry.md +1 -1
- data/TODO.complete/03-object-properties-constraints.md +1 -1
- data/TODO.complete/04-operation-params.md +1 -1
- data/TODO.complete/05-pdata-flag-matrix.md +1 -1
- data/TODO.complete/06-emf-image-rendering.md +1 -1
- data/TODO.complete/07-auxiliary-tables.md +1 -1
- data/TODO.complete/10-mdg-stereotype-registry.md +1 -1
- data/TODO.complete/11-gml-xsd-generation.md +1 -1
- data/TODO.complete/13-shapescript-interpreter.md +12 -23
- data/TODO.complete/20-cli-diff.md +1 -1
- data/TODO.complete/21-cli-render.md +1 -1
- data/TODO.complete/22-cli-export.md +1 -1
- data/TODO.complete/23-cli-mdg.md +1 -1
- data/TODO.complete/30-headerlines-ocp-refactor.md +1 -1
- data/TODO.complete/32-spec-coverage.md +1 -1
- data/TODO.complete/33-blocked-parity-gaps.md +1 -1
- data/TODO.complete/36-stereotype-icon-wiring.md +1 -1
- data/TODO.complete/37-cli-lint.md +1 -1
- data/TODO.complete/38-cli-query.md +1 -1
- data/TODO.complete/39-cli-info.md +1 -1
- data/TODO.complete/40-ocl-evaluator.md +1 -1
- data/TODO.complete/41-round-trip-spec.md +1 -1
- data/TODO.complete/42-html-diff-report.md +1 -1
- data/TODO.complete/43-json-schema-export.md +1 -1
- data/TODO.complete/44-remaining-qea-tables.md +1 -1
- data/TODO.complete/45-shapescript-extensions.md +1 -1
- data/TODO.complete/46-xsd-consolidation.md +1 -1
- data/TODO.complete/47-headerlines-legacy-removal.md +1 -1
- data/TODO.complete/51-remove-respond-to.md +42 -15
- data/TODO.complete/52-xsd-decouple-fixtures.md +1 -1
- data/TODO.complete/54-wire-stereotype-icon-renderer.md +1 -1
- data/TODO.complete/55-wire-shapescript.md +1 -1
- data/TODO.complete/56-wire-emf-renderer.md +1 -1
- data/TODO.complete/57-wire-ocl-evaluator.md +1 -1
- data/TODO.complete/58-diff-modifications.md +1 -1
- data/TODO.complete/59-delete-toy-xmi-export.md +1 -1
- data/TODO.complete/60-curate-json-export.md +1 -1
- data/TODO.complete/61-plantuml-relationships.md +1 -1
- data/TODO.complete/62-changelog.md +1 -1
- data/TODO.complete/63-dependabot-fix.md +1 -1
- data/TODO.complete/64-qeatoxmi-connectors.md +15 -0
- data/TODO.complete/65-qeatoxmi-diagrams.md +15 -0
- data/TODO.complete/66-qeatoxmi-style-tags-docs.md +35 -0
- data/TODO.complete/67-ocl-collection-ops.md +20 -0
- data/TODO.complete/68-ocl-comparison-ops.md +24 -0
- data/TODO.complete/69-spec-new-models.md +20 -0
- data/TODO.complete/70-specs-for-all-models.md +21 -0
- data/TODO.complete/71-autoload-audit.md +26 -0
- data/TODO.complete/72-extension-serializer-ocp-naming.md +26 -0
- data/TODO.complete/73-ocl-validator-latent-bugs.md +50 -0
- data/TODO.complete/74-visibility-symbol-dry.md +39 -0
- data/TODO.complete/75-database-table-collection-registry.md +32 -0
- data/TODO.complete/76-stale-todo-cleanup.md +52 -0
- data/TODO.complete/77-eliminate-banned-patterns-from-specs.md +55 -0
- data/TODO.complete/78-mdg-stereotype-emission.md +66 -0
- data/TODO.complete/79-equivalence-fixes.md +57 -0
- data/TODO.complete/80-performance-extension-serializer-indexes.md +43 -0
- data/TODO.complete/81-dry-scope-guid-extraction.md +33 -0
- data/TODO.complete/82-public-send-audit-conclusion.md +25 -0
- data/TODO.complete/83-dry-shared-xml-escape-guid-format.md +52 -0
- data/TODO.complete/84-ocp-markers-shape-registry.md +54 -0
- data/TODO.complete/85-frozen-literal-rubocop-normalize-type-spec.md +38 -0
- data/TODO.complete/86-frozen-sweep-lint-query-specs.md +55 -0
- data/TODO.complete/STATUS.md +26 -5
- data/lib/ea/cli/app.rb +1 -0
- data/lib/ea/cli/command/convert.rb +1 -1
- data/lib/ea/cli/command/export.rb +10 -1
- data/lib/ea/cli/command/mdg.rb +1 -10
- data/lib/ea/cli/command/render.rb +2 -1
- data/lib/ea/diff/html_reporter.rb +1 -5
- data/lib/ea/export/json/generator.rb +5 -6
- data/lib/ea/export/plantuml/generator.rb +5 -3
- data/lib/ea/guid_format.rb +91 -0
- data/lib/ea/mdg/document.rb +2 -1
- data/lib/ea/mdg/loader.rb +2 -1
- data/lib/ea/mdg/registry.rb +57 -2
- data/lib/ea/mdg/xml/applies_to.rb +1 -1
- data/lib/ea/mdg/xml/apply.rb +1 -1
- data/lib/ea/mdg/xml/content.rb +1 -1
- data/lib/ea/mdg/xml/documentation.rb +1 -1
- data/lib/ea/mdg/xml/stereotype.rb +1 -1
- data/lib/ea/mdg/xml/stereotypes.rb +1 -1
- data/lib/ea/mdg/xml/tag.rb +1 -1
- data/lib/ea/mdg/xml/tagged_values.rb +1 -1
- data/lib/ea/mdg/xml/technology.rb +1 -1
- data/lib/ea/mdg/xml/uml_profile.rb +1 -1
- data/lib/ea/mdg/xml/uml_profiles.rb +1 -1
- data/lib/ea/mdg/xml.rb +2 -1
- data/lib/ea/mdg.rb +2 -1
- data/lib/ea/model/ghost_label.rb +2 -1
- data/lib/ea/ocl/evaluator.rb +39 -0
- data/lib/ea/ocl/nodes.rb +9 -0
- data/lib/ea/ocl/parser.rb +60 -6
- data/lib/ea/qea/database.rb +57 -2
- data/lib/ea/qea/factory/reference_resolver.rb +1 -1
- data/lib/ea/qea/models/ea_attribute.rb +1 -17
- data/lib/ea/qea/models/ea_connector.rb +3 -2
- data/lib/ea/qea/models/ea_object.rb +14 -9
- data/lib/ea/qea/models/ea_operation.rb +1 -17
- data/lib/ea/qea/models/ea_xref.rb +3 -0
- data/lib/ea/qea/models/scope_predicate.rb +29 -0
- data/lib/ea/qea/models.rb +1 -0
- data/lib/ea/qea/repositories/base_repository.rb +4 -1
- data/lib/ea/qea/validation/base_validator.rb +3 -48
- data/lib/ea/qea/validation/database/ocl_constraint_validator.rb +23 -13
- data/lib/ea/shapescript/renderer.rb +1 -3
- data/lib/ea/sources/qea/diagram_builder.rb +1 -0
- data/lib/ea/sources/qea/id_normalizer.rb +2 -2
- data/lib/ea/svg/ea_emitter/background.rb +2 -1
- data/lib/ea/svg/ea_emitter/element/attribute_line_builder.rb +1 -6
- data/lib/ea/svg/ea_emitter/element/operation_renderer.rb +2 -7
- data/lib/ea/svg/ea_emitter/elements.rb +1 -3
- data/lib/ea/svg/ea_emitter/ghost_labels.rb +2 -1
- data/lib/ea/svg/ea_emitter/label/end_label.rb +1 -6
- data/lib/ea/svg/ea_emitter/marker/package_import.rb +1 -1
- data/lib/ea/svg/ea_emitter/markers.rb +52 -23
- data/lib/ea/svg/ea_emitter/text_renderer.rb +1 -4
- data/lib/ea/svg/ea_emitter/visibility_symbol.rb +37 -0
- data/lib/ea/svg/ea_emitter.rb +1 -0
- data/lib/ea/svg/parity/source.rb +0 -2
- data/lib/ea/svg/parity/suite.rb +0 -1
- data/lib/ea/transformers/qea_to_xmi/cardinality.rb +40 -28
- data/lib/ea/transformers/qea_to_xmi/context.rb +35 -1
- data/lib/ea/transformers/qea_to_xmi/extension_serializer.rb +686 -0
- data/lib/ea/transformers/qea_to_xmi/guid_format.rb +5 -48
- data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +55 -54
- data/lib/ea/transformers/qea_to_xmi/primitive_types.rb +105 -0
- data/lib/ea/transformers/qea_to_xmi/profile_serializer.rb +163 -0
- data/lib/ea/transformers/qea_to_xmi/run_state.rb +7 -6
- data/lib/ea/transformers/qea_to_xmi/transformer.rb +423 -76
- data/lib/ea/transformers/qea_to_xmi/visibility.rb +30 -19
- data/lib/ea/transformers/qea_to_xmi.rb +5 -2
- data/lib/ea/transformers.rb +8 -2
- data/lib/ea/validation.rb +1 -1
- data/lib/ea/version.rb +1 -1
- data/lib/ea/xmi/liquid_drops/attribute_drop.rb +12 -4
- data/lib/ea/xmi/liquid_drops/klass_drop.rb +1 -2
- data/lib/ea/xmi/lookup_service.rb +4 -0
- data/lib/ea/xmi/parser.rb +30 -1
- data/lib/ea/xml_escape.rb +21 -0
- data/lib/ea.rb +2 -0
- metadata +34 -11
- data/lutaml-uml-0.5.1.gem +0 -0
|
@@ -34,6 +34,7 @@ module Ea
|
|
|
34
34
|
MODEL_NAME = "EA_Model"
|
|
35
35
|
EXPORTER = "Enterprise Architect"
|
|
36
36
|
EXPORTER_VERSION = "6.5"
|
|
37
|
+
BUILD_ID = "1624"
|
|
37
38
|
|
|
38
39
|
RELATIONSHIP_AT_PACKAGE_LEVEL = {
|
|
39
40
|
"Association" => :association,
|
|
@@ -55,12 +56,36 @@ module Ea
|
|
|
55
56
|
class: ->(obj) { build_class(obj) },
|
|
56
57
|
enumeration: ->(obj) { build_enumeration(obj) },
|
|
57
58
|
data_type: ->(obj) { build_data_type(obj) },
|
|
59
|
+
signal: ->(obj) { build_class(obj, xmi_type: "uml:Signal") },
|
|
58
60
|
instance: ->(obj) { build_instance(obj) },
|
|
61
|
+
association_element: ->(obj) { build_association_element(obj) },
|
|
59
62
|
}.freeze
|
|
60
63
|
|
|
61
|
-
|
|
64
|
+
# What each builder in CLASSIFIER_BUILDERS actually emits, keyed
|
|
65
|
+
# by the same symbols. Every builder must appear here: a kind
|
|
66
|
+
# left out silently loses its attribute bounds or its nested
|
|
67
|
+
# children — wrong XML, no exception — so the spec asserts these
|
|
68
|
+
# keys match CLASSIFIER_BUILDERS exactly.
|
|
69
|
+
#
|
|
70
|
+
# `:owned_attribute` — the builder emits `<ownedAttribute>` and so
|
|
71
|
+
# preallocates attribute bound IDs. Enumerations emit literals
|
|
72
|
+
# (never bounds) and instances emit slots.
|
|
73
|
+
# `:nested_classifier` — the builder walks children, so a kind
|
|
74
|
+
# listed here can adopt an object that carries a parentid.
|
|
75
|
+
# `build_class` is the only builder that walks children.
|
|
76
|
+
CLASSIFIER_CAPABILITIES = {
|
|
77
|
+
class: %i[owned_attribute nested_classifier],
|
|
78
|
+
signal: %i[owned_attribute nested_classifier],
|
|
79
|
+
data_type: %i[owned_attribute],
|
|
80
|
+
enumeration: [],
|
|
81
|
+
instance: [],
|
|
82
|
+
association_element: []
|
|
83
|
+
}.freeze
|
|
84
|
+
|
|
85
|
+
def initialize(database, mdg_registry: nil)
|
|
62
86
|
@database = database
|
|
63
|
-
@
|
|
87
|
+
@mdg_registry = mdg_registry
|
|
88
|
+
@context = Context.new(database: database)
|
|
64
89
|
end
|
|
65
90
|
|
|
66
91
|
# @return [String] XMI XML document
|
|
@@ -70,8 +95,89 @@ module Ea
|
|
|
70
95
|
# nil-valued attributes are skipped at the source. No
|
|
71
96
|
# post-processing pass is needed — the prior XmlSanitizer
|
|
72
97
|
# workaround (TODO 21 §1) has been removed.
|
|
73
|
-
|
|
74
|
-
|
|
98
|
+
# Serialize to XMI XML.
|
|
99
|
+
#
|
|
100
|
+
# Default produces model tree only (parseable by the xmi gem's
|
|
101
|
+
# Sparx parser for round-trip). Pass `with_extensions: true` to
|
|
102
|
+
# also emit EA-specific `<connectors>` and `<diagrams>` sections
|
|
103
|
+
# inside `<xmi:Extension>`. Those use post-processing string
|
|
104
|
+
# injection that the xmi gem's parser doesn't model, so the
|
|
105
|
+
# output is NOT round-trippable when extensions are included.
|
|
106
|
+
#
|
|
107
|
+
# @param with_extensions [Boolean]
|
|
108
|
+
# @return [String] XMI XML
|
|
109
|
+
def serialize(with_extensions: true)
|
|
110
|
+
xml = build_root.to_xml(use_prefix: true)
|
|
111
|
+
xml = normalize_namespaces(xml)
|
|
112
|
+
xml = plain_parameter_type(xml)
|
|
113
|
+
with_extensions ? inject_extension_content(xml) : xml
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# EA writes a return parameter's type unprefixed:
|
|
117
|
+
# `<ownedParameter … type="EAnone_void"/>`. Its reference export
|
|
118
|
+
# carries 12 of these and no `xmi:type` on ownedParameter at all.
|
|
119
|
+
#
|
|
120
|
+
# `xmi:type` is the XMI metaclass discriminator; `type` is the
|
|
121
|
+
# classifier reference. Two different attributes — but
|
|
122
|
+
# lutaml-model matches by local name and collapses them into one
|
|
123
|
+
# slot, so the xmi gem can only emit one of them. It keeps the
|
|
124
|
+
# namespaced form, which is right for general UML XMI, and
|
|
125
|
+
# rewriting that in the shared model would break round-tripping
|
|
126
|
+
# for every non-Sparx consumer. Restoring the reference EA
|
|
127
|
+
# actually writes is this exporter's job, so it happens here.
|
|
128
|
+
#
|
|
129
|
+
# Scoped to ownedParameter: no other element wants it. Walks
|
|
130
|
+
# complete name="value" pairs rather than scanning the tag as
|
|
131
|
+
# text — a parameter name is free text out of EA and may itself
|
|
132
|
+
# contain the literal `xmi:type=`, which a raw substitution
|
|
133
|
+
# would happily rewrite inside the quotes.
|
|
134
|
+
ATTRIBUTE_PAIR = /([A-Za-z_][\w:.-]*)=("[^"]*")/
|
|
135
|
+
|
|
136
|
+
def plain_parameter_type(xml)
|
|
137
|
+
xml.gsub(/<ownedParameter\b[^>]*?>/m) do |tag|
|
|
138
|
+
tag.gsub(ATTRIBUTE_PAIR) do
|
|
139
|
+
name = Regexp.last_match(1)
|
|
140
|
+
name == "xmi:type" ? %(type=#{Regexp.last_match(2)}) : Regexp.last_match(0)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# EA's reference XMI uses the 2011-07-01 XMI/UML namespace URIs;
|
|
146
|
+
# the xmi gem's Root model hard-codes 2013-10-01. Post-process
|
|
147
|
+
# the serialized XML to match EA's namespace version. This is a
|
|
148
|
+
# targeted string replacement on the xmlns declarations only,
|
|
149
|
+
# not a semantic change.
|
|
150
|
+
def normalize_namespaces(xml)
|
|
151
|
+
xml.gsub("http://www.omg.org/spec/XMI/20131001",
|
|
152
|
+
"http://www.omg.org/spec/XMI/20110701")
|
|
153
|
+
.gsub("http://www.omg.org/spec/UML/20131001/UMLDI",
|
|
154
|
+
"http://www.omg.org/spec/UML/20110701/UMLDI")
|
|
155
|
+
.gsub("http://www.omg.org/spec/UML/20131001/UMLDC",
|
|
156
|
+
"http://www.omg.org/spec/UML/20110701/UMLDC")
|
|
157
|
+
.gsub("http://www.omg.org/spec/UML/20131001",
|
|
158
|
+
"http://www.omg.org/spec/UML/20110701")
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Injects EA-specific extension content (elements, connectors,
|
|
162
|
+
# diagrams) into the serialized XMI. The xmi gem's Extension
|
|
163
|
+
# type is modeled but doesn't declare these children. We
|
|
164
|
+
# post-process the serialized XML to insert them inside the
|
|
165
|
+
# existing `<xmi:Extension>` element.
|
|
166
|
+
#
|
|
167
|
+
# Delegated to {ExtensionSerializer} — this class only handles
|
|
168
|
+
# the string substitution mechanics.
|
|
169
|
+
def inject_extension_content(xml)
|
|
170
|
+
content = ExtensionSerializer.new(@database, @context).call
|
|
171
|
+
return xml if content.empty?
|
|
172
|
+
|
|
173
|
+
if xml.include?("<xmi:Extension")
|
|
174
|
+
xml.sub(%r{<xmi:Extension ([^>]+)>\s*</xmi:Extension>},
|
|
175
|
+
"<xmi:Extension \\1>\n#{content}\n\t</xmi:Extension>")
|
|
176
|
+
.sub(/<xmi:Extension ([^>]+)\/>/,
|
|
177
|
+
"<xmi:Extension \\1>\n#{content}\n\t</xmi:Extension>")
|
|
178
|
+
else
|
|
179
|
+
xml
|
|
180
|
+
end
|
|
75
181
|
end
|
|
76
182
|
|
|
77
183
|
private
|
|
@@ -90,6 +196,7 @@ module Ea
|
|
|
90
196
|
::Xmi::Documentation.new(
|
|
91
197
|
exporter: EXPORTER,
|
|
92
198
|
exporter_version: EXPORTER_VERSION,
|
|
199
|
+
exporter_id: BUILD_ID,
|
|
93
200
|
)
|
|
94
201
|
end
|
|
95
202
|
|
|
@@ -97,7 +204,7 @@ module Ea
|
|
|
97
204
|
::Xmi::Uml::UmlModel.new(
|
|
98
205
|
type: "uml:Model",
|
|
99
206
|
name: MODEL_NAME,
|
|
100
|
-
packaged_element: root_packages.map { |pkg| build_package(pkg) },
|
|
207
|
+
packaged_element: root_packages.map { |pkg| build_package(pkg) } + profile_elements,
|
|
101
208
|
)
|
|
102
209
|
end
|
|
103
210
|
|
|
@@ -111,6 +218,13 @@ module Ea
|
|
|
111
218
|
.sort_by { |p| [p.tpos || 0, p.name.to_s] }
|
|
112
219
|
end
|
|
113
220
|
|
|
221
|
+
# MDG stereotype definitions to include in the model tree.
|
|
222
|
+
# Empty when no MDG registry is wired. Each registered MDG
|
|
223
|
+
# contributes its stereotypes and extension relationships.
|
|
224
|
+
def profile_elements
|
|
225
|
+
ProfileSerializer.new(@mdg_registry).call
|
|
226
|
+
end
|
|
227
|
+
|
|
114
228
|
# ---- Package tree ------------------------------------------------
|
|
115
229
|
|
|
116
230
|
def build_package(pkg)
|
|
@@ -132,12 +246,128 @@ module Ea
|
|
|
132
246
|
end
|
|
133
247
|
|
|
134
248
|
def subpackages(pkg)
|
|
135
|
-
sorted_by_position(@context.child_packages(pkg.package_id))
|
|
136
|
-
|
|
249
|
+
@context.sorted_by_position(@context.child_packages(pkg.package_id))
|
|
250
|
+
.map { |sub| build_package(sub) }
|
|
137
251
|
end
|
|
138
252
|
|
|
253
|
+
# EA emits a package's own classifiers first, then the children
|
|
254
|
+
# nobody adopted. The global LI counter follows that order.
|
|
139
255
|
def classifier_objects(pkg)
|
|
140
|
-
|
|
256
|
+
top_level, orphaned = package_level_classifiers(pkg)
|
|
257
|
+
.partition { |obj| obj.parentid.to_i.zero? }
|
|
258
|
+
(top_level + orphaned).filter_map { |obj| build_classifier_with_bounds(obj) }
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Everything EA leaves at package level: an object stays here
|
|
262
|
+
# unless a classifier in the same package actually adopts it as
|
|
263
|
+
# a nestedClassifier. A child of an enumeration, of an
|
|
264
|
+
# instance, of a type we don't build, of a parent in another
|
|
265
|
+
# package, or of one that isn't there at all has no adopter.
|
|
266
|
+
#
|
|
267
|
+
# Adoption is decided from the package's own contents, never
|
|
268
|
+
# from what the walk has emitted so far — a child can sort
|
|
269
|
+
# before its own parent, and asking a set that the walk is
|
|
270
|
+
# still filling would emit that child twice.
|
|
271
|
+
def package_level_classifiers(pkg)
|
|
272
|
+
buildable_objects_in(pkg).reject { |obj| adopted?(obj) }
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# A parent adopts a child only if it is a nesting kind in the
|
|
276
|
+
# same package AND the ParentID chain above the child actually
|
|
277
|
+
# terminates. A corrupt model can point an object at itself or
|
|
278
|
+
# round a cycle; leaving those adopted would drop them from the
|
|
279
|
+
# export, since the parent that owes them an emission is never
|
|
280
|
+
# reached. Shared with nested_classifiers_under so the two
|
|
281
|
+
# cannot disagree about who owns a child.
|
|
282
|
+
def adopted?(obj)
|
|
283
|
+
parent = nesting_parent_of(obj)
|
|
284
|
+
!parent.nil? && !cyclic_ancestry?(obj)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def nesting_parent_of(obj)
|
|
288
|
+
parent_id = obj.parentid.to_i
|
|
289
|
+
return nil unless parent_id.positive?
|
|
290
|
+
|
|
291
|
+
parent = @context.object_by_id(parent_id)
|
|
292
|
+
return nil unless parent && parent.package_id == obj.package_id
|
|
293
|
+
return nil unless nests_classifiers?(parent)
|
|
294
|
+
|
|
295
|
+
parent
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def cyclic_ancestry?(obj)
|
|
299
|
+
seen = Set[obj.ea_object_id]
|
|
300
|
+
current = obj
|
|
301
|
+
while (current = nesting_parent_of(current))
|
|
302
|
+
return true unless seen.add?(current.ea_object_id)
|
|
303
|
+
end
|
|
304
|
+
false
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# EA's class-centric LI allocation: attribute bounds first (name
|
|
308
|
+
# order), then the classifier itself, then the association-end
|
|
309
|
+
# bounds anchored here. The memoized allocator lets later
|
|
310
|
+
# builders reuse these IDs.
|
|
311
|
+
def build_classifier_with_bounds(obj)
|
|
312
|
+
preallocate_attribute_bounds(obj)
|
|
313
|
+
element = build_classifier(obj)
|
|
314
|
+
preallocate_end_bounds(obj)
|
|
315
|
+
element
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# EA allocates a classifier's attribute LI bounds in attribute
|
|
319
|
+
# NAME order, while the document emits attributes in Pos order.
|
|
320
|
+
# Pre-allocating here (memoized) makes the later Pos-ordered
|
|
321
|
+
# build reuse the name-ordered counters. Only owners that go on
|
|
322
|
+
# to emit ownedAttribute take counters — an enumeration's
|
|
323
|
+
# literals never carry bounds.
|
|
324
|
+
def preallocate_attribute_bounds(obj)
|
|
325
|
+
return unless emits_attributes?(obj)
|
|
326
|
+
|
|
327
|
+
@context.attributes_for(obj.ea_object_id)
|
|
328
|
+
.sort_by { |a| a.name.to_s }
|
|
329
|
+
.each { |attr| build_attribute_bounds(attr) }
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# Lower before upper — the global LI counter follows this order.
|
|
333
|
+
# @return [Array(Xmi::Uml::LowerValue, Xmi::Uml::UpperValue)]
|
|
334
|
+
def build_attribute_bounds(attr)
|
|
335
|
+
attr_id = @context.xmi_id_for(attr)
|
|
336
|
+
bounds = Cardinality.attribute_bounds(attr.lowerbound, attr.upperbound)
|
|
337
|
+
[build_lower_value(bounds[:lower],
|
|
338
|
+
seed: "mult-attr-#{attr.id}-lower", owner_id: attr_id),
|
|
339
|
+
build_upper_value(bounds[:upper],
|
|
340
|
+
seed: "mult-attr-#{attr.id}-upper", owner_id: attr_id)]
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# EA's global LI counter is allocated class-centrically, not in
|
|
344
|
+
# document order: after a classifier's attribute bounds come the
|
|
345
|
+
# association-end bounds whose OPPOSITE endpoint is that
|
|
346
|
+
# classifier (dst ends of connectors starting here, src ends of
|
|
347
|
+
# connectors ending here). The memoized allocator lets the
|
|
348
|
+
# Association element reuse these IDs when it is built later.
|
|
349
|
+
def preallocate_end_bounds(obj)
|
|
350
|
+
association_ends_anchored_at(obj)
|
|
351
|
+
.each { |conn, side| build_association_end(conn, side: side) }
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# The ends anchored at a classifier, ordered by connector GUID
|
|
355
|
+
# (ascending string order — the only key that fits every tied
|
|
356
|
+
# case in the reference exports; names, connector ids and
|
|
357
|
+
# opposite-endpoint ids all contradict at least one).
|
|
358
|
+
def association_ends_anchored_at(obj)
|
|
359
|
+
@context.connectors_for(obj.ea_object_id)
|
|
360
|
+
.flat_map { |conn| anchored_end_sides(conn, obj.ea_object_id) }
|
|
361
|
+
.sort_by { |conn, side| [conn.ea_guid.to_s, side == :destination ? 0 : 1] }
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def anchored_end_sides(conn, object_id)
|
|
365
|
+
return [] unless RELATIONSHIP_AT_PACKAGE_LEVEL[conn.connector_type] == :association
|
|
366
|
+
|
|
367
|
+
pairs = []
|
|
368
|
+
pairs << [conn, :destination] if conn.start_object_id == object_id
|
|
369
|
+
pairs << [conn, :source] if conn.end_object_id == object_id
|
|
370
|
+
pairs
|
|
141
371
|
end
|
|
142
372
|
|
|
143
373
|
# Connectors owned by this package: connectors whose start_object is
|
|
@@ -146,7 +376,7 @@ module Ea
|
|
|
146
376
|
# Realization are emitted inside the classifier itself.
|
|
147
377
|
def package_level_relationships(pkg)
|
|
148
378
|
emitted = Set.new
|
|
149
|
-
|
|
379
|
+
relationship_sources_in(pkg).flat_map do |obj|
|
|
150
380
|
@context.connectors_starting_at(obj.ea_object_id).filter_map do |conn|
|
|
151
381
|
key = RELATIONSHIP_AT_PACKAGE_LEVEL[conn.connector_type]
|
|
152
382
|
next nil unless key
|
|
@@ -157,6 +387,13 @@ module Ea
|
|
|
157
387
|
end
|
|
158
388
|
end
|
|
159
389
|
|
|
390
|
+
# Relationship discovery must also see Package objects —
|
|
391
|
+
# package-to-package Dependency connectors hang off them even
|
|
392
|
+
# though they never emit as classifiers.
|
|
393
|
+
def relationship_sources_in(pkg)
|
|
394
|
+
@context.objects_in_package(pkg.package_id).reject { |o| note?(o) }
|
|
395
|
+
end
|
|
396
|
+
|
|
160
397
|
def build_package_relationship(kind, conn)
|
|
161
398
|
case kind
|
|
162
399
|
when :association then build_association(conn)
|
|
@@ -171,16 +408,29 @@ module Ea
|
|
|
171
408
|
# ---- Classifier dispatch (OCP registry) --------------------------
|
|
172
409
|
|
|
173
410
|
def build_classifier(obj)
|
|
174
|
-
|
|
175
|
-
builder = CLASSIFIER_BUILDERS[kind]
|
|
411
|
+
builder = CLASSIFIER_BUILDERS[obj.transformer_type]
|
|
176
412
|
return nil unless builder
|
|
177
413
|
|
|
178
414
|
instance_exec(obj, &builder)
|
|
179
415
|
end
|
|
180
416
|
|
|
181
|
-
|
|
417
|
+
# Objects that build no classifier at all (a Component parent, a
|
|
418
|
+
# Note) reach these predicates too, and answer no to both.
|
|
419
|
+
def capabilities_of(obj)
|
|
420
|
+
CLASSIFIER_CAPABILITIES.fetch(obj.transformer_type, [])
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def emits_attributes?(obj)
|
|
424
|
+
capabilities_of(obj).include?(:owned_attribute)
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def nests_classifiers?(obj)
|
|
428
|
+
capabilities_of(obj).include?(:nested_classifier)
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
def build_class(obj, xmi_type: nil)
|
|
182
432
|
::Xmi::Uml::PackagedElement.new(
|
|
183
|
-
type: class_xmi_type(obj),
|
|
433
|
+
type: xmi_type || class_xmi_type(obj),
|
|
184
434
|
id: @context.xmi_id_for(obj),
|
|
185
435
|
name: obj.name,
|
|
186
436
|
visibility: Visibility.from_scope(obj.scope),
|
|
@@ -189,9 +439,16 @@ module Ea
|
|
|
189
439
|
interface_realization: interface_realizations_for(obj),
|
|
190
440
|
owned_attribute: attributes_for(obj),
|
|
191
441
|
owned_operation: operations_for(obj),
|
|
442
|
+
nested_classifier: nested_children_for(obj),
|
|
192
443
|
)
|
|
193
444
|
end
|
|
194
445
|
|
|
446
|
+
# Recursive: nested children carry their own nested children,
|
|
447
|
+
# walked with the same class-centric bound preallocation.
|
|
448
|
+
def nested_children_for(obj)
|
|
449
|
+
nested_classifiers_under(obj).filter_map { |child| build_classifier_with_bounds(child) }
|
|
450
|
+
end
|
|
451
|
+
|
|
195
452
|
def build_enumeration(obj)
|
|
196
453
|
::Xmi::Uml::PackagedElement.new(
|
|
197
454
|
type: "uml:Enumeration",
|
|
@@ -224,6 +481,15 @@ module Ea
|
|
|
224
481
|
)
|
|
225
482
|
end
|
|
226
483
|
|
|
484
|
+
# EA stores some associations as t_object rows. Its export
|
|
485
|
+
# emits them bare — id and name only, no ownedEnds and no
|
|
486
|
+
# visibility. The connector-backed associations
|
|
487
|
+
# (build_association) are a separate path.
|
|
488
|
+
def build_association_element(obj)
|
|
489
|
+
::Xmi::Uml::PackagedElement.new(type: "uml:Association", id: @context.xmi_id_for(obj),
|
|
490
|
+
name: obj.name)
|
|
491
|
+
end
|
|
492
|
+
|
|
227
493
|
def build_comment(obj)
|
|
228
494
|
::Xmi::Uml::OwnedComment.new(
|
|
229
495
|
type: "uml:Comment",
|
|
@@ -245,28 +511,31 @@ module Ea
|
|
|
245
511
|
# rather than as package-level `<packagedElement type=
|
|
246
512
|
# "uml:Realization">`. We do the same.
|
|
247
513
|
def interface_realizations_for(obj)
|
|
248
|
-
realization_connectors(obj).
|
|
514
|
+
realization_connectors(obj).filter_map { |conn| build_interface_realization(conn) }
|
|
249
515
|
end
|
|
250
516
|
|
|
251
517
|
def attributes_for(obj)
|
|
252
|
-
sorted_by_position(@context.attributes_for(obj.ea_object_id))
|
|
253
|
-
|
|
518
|
+
@context.sorted_by_position(@context.attributes_for(obj.ea_object_id))
|
|
519
|
+
.map { |attr| build_attribute(attr) }
|
|
254
520
|
end
|
|
255
521
|
|
|
256
522
|
def operations_for(obj)
|
|
257
|
-
sorted_by_position(@context.operations_for(obj.ea_object_id))
|
|
258
|
-
|
|
523
|
+
@context.sorted_by_position(@context.operations_for(obj.ea_object_id))
|
|
524
|
+
.map { |op| build_operation(op) }
|
|
259
525
|
end
|
|
260
526
|
|
|
261
527
|
def enum_literals(obj)
|
|
262
|
-
sorted_by_position(@context.attributes_for(obj.ea_object_id))
|
|
263
|
-
|
|
528
|
+
@context.sorted_by_position(@context.attributes_for(obj.ea_object_id))
|
|
529
|
+
.map { |attr| build_owned_literal(attr) }
|
|
264
530
|
end
|
|
265
531
|
|
|
266
532
|
# ---- Leaf element builders --------------------------------------
|
|
267
533
|
|
|
268
534
|
def build_attribute(attr)
|
|
269
|
-
|
|
535
|
+
# An attribute always carries both bounds; association ends
|
|
536
|
+
# check their single card field instead and stay bare when it
|
|
537
|
+
# is blank.
|
|
538
|
+
lower, upper = build_attribute_bounds(attr)
|
|
270
539
|
::Xmi::Uml::OwnedAttribute.new(
|
|
271
540
|
type: "uml:Property",
|
|
272
541
|
id: @context.xmi_id_for(attr),
|
|
@@ -276,8 +545,8 @@ module Ea
|
|
|
276
545
|
is_ordered: Visibility.boolean_from_flag(attr.isordered),
|
|
277
546
|
is_derived: Visibility.boolean_from_flag(attr.derived),
|
|
278
547
|
uml_type: type_reference_model(attr.type, attr.classifier),
|
|
279
|
-
upper_value:
|
|
280
|
-
lower_value:
|
|
548
|
+
upper_value: upper,
|
|
549
|
+
lower_value: lower,
|
|
281
550
|
)
|
|
282
551
|
end
|
|
283
552
|
|
|
@@ -289,7 +558,7 @@ module Ea
|
|
|
289
558
|
is_static: Visibility.boolean_from_flag(op.isstatic),
|
|
290
559
|
is_abstract: Visibility.boolean_from_flag(op.abstract),
|
|
291
560
|
is_query: Visibility.boolean_from_flag(op.pure),
|
|
292
|
-
concurrency: op.concurrency
|
|
561
|
+
concurrency: normalize_concurrency(op.concurrency),
|
|
293
562
|
owned_parameter: operation_parameters(op),
|
|
294
563
|
)
|
|
295
564
|
end
|
|
@@ -314,30 +583,43 @@ module Ea
|
|
|
314
583
|
end
|
|
315
584
|
|
|
316
585
|
def build_interface_realization(conn)
|
|
586
|
+
client = @context.object_by_id(conn.start_object_id)
|
|
317
587
|
supplier = @context.object_by_id(conn.end_object_id)
|
|
588
|
+
return nil unless client && supplier
|
|
589
|
+
|
|
590
|
+
supplier_ref = @context.xmi_id_for(supplier)
|
|
318
591
|
::Xmi::Uml::InterfaceRealization.new(
|
|
319
|
-
type: "uml:InterfaceRealization",
|
|
320
|
-
|
|
321
|
-
|
|
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,
|
|
592
|
+
type: "uml:InterfaceRealization", id: @context.xmi_id_for(conn),
|
|
593
|
+
name: conn.name, client: @context.xmi_id_for(client),
|
|
594
|
+
supplier: supplier_ref, contract: supplier_ref
|
|
325
595
|
)
|
|
326
596
|
end
|
|
327
597
|
|
|
328
598
|
def build_dependency(conn)
|
|
329
|
-
client =
|
|
330
|
-
supplier =
|
|
599
|
+
client = dependency_endpoint_ref(conn.start_object_id)
|
|
600
|
+
supplier = dependency_endpoint_ref(conn.end_object_id)
|
|
331
601
|
return nil unless client && supplier
|
|
332
602
|
|
|
333
603
|
::Xmi::Uml::PackagedElement.new(
|
|
334
604
|
type: "uml:Dependency",
|
|
335
605
|
id: @context.xmi_id_for(conn),
|
|
336
|
-
client:
|
|
337
|
-
supplier:
|
|
606
|
+
client: client,
|
|
607
|
+
supplier: supplier,
|
|
338
608
|
)
|
|
339
609
|
end
|
|
340
610
|
|
|
611
|
+
# Package-object endpoints resolve to their t_package via PDATA1
|
|
612
|
+
# and reference it as EAPK_ (matching EA); everything else keeps
|
|
613
|
+
# its own EAID_ reference.
|
|
614
|
+
def dependency_endpoint_ref(object_id)
|
|
615
|
+
obj = @context.object_by_id(object_id)
|
|
616
|
+
return nil unless obj
|
|
617
|
+
return @context.xmi_id_for(obj) unless package_object?(obj)
|
|
618
|
+
|
|
619
|
+
pkg = @database.find_package(obj.pdata1.to_i)
|
|
620
|
+
pkg && @context.xmi_id_for(pkg, prefix: "EAPK")
|
|
621
|
+
end
|
|
622
|
+
|
|
341
623
|
# Sparx serialisation order for `uml:Association` is
|
|
342
624
|
# destination member-end first, source member-end second.
|
|
343
625
|
# Reordering these breaks round-trip fidelity with Sparx EA
|
|
@@ -365,8 +647,18 @@ module Ea
|
|
|
365
647
|
target_id = side == :source ? conn.start_object_id : conn.end_object_id
|
|
366
648
|
target_obj = @context.object_by_id(target_id)
|
|
367
649
|
target_ref = target_obj ? @context.xmi_id_for(target_obj) : nil
|
|
368
|
-
bounds = Cardinality.parse(cardinality_for(conn, side))
|
|
369
650
|
containment = containment_for(conn, side)
|
|
651
|
+
# EA emits lower/upper only when the end's cardinality is set —
|
|
652
|
+
# a blank card gets a bare ownedEnd. Allocation order is lower
|
|
653
|
+
# before upper, with the END's id (EAID_dst…/EAID_src…) as the
|
|
654
|
+
# tail source.
|
|
655
|
+
raw_card = cardinality_for(conn, side)
|
|
656
|
+
lower = upper = nil
|
|
657
|
+
unless raw_card.to_s.strip.empty?
|
|
658
|
+
bounds = Cardinality.parse(raw_card)
|
|
659
|
+
lower = build_lower_value(bounds[:lower], seed: "mult-#{conn.connector_id}-#{side}-lower", owner_id: end_id)
|
|
660
|
+
upper = build_upper_value(bounds[:upper], seed: "mult-#{conn.connector_id}-#{side}-upper", owner_id: end_id)
|
|
661
|
+
end
|
|
370
662
|
|
|
371
663
|
model = ::Xmi::Uml::OwnedEnd.new(
|
|
372
664
|
type: "uml:Property",
|
|
@@ -376,8 +668,8 @@ module Ea
|
|
|
376
668
|
aggregation: Visibility.aggregation_from_containment(containment),
|
|
377
669
|
association: @context.xmi_id_for(conn),
|
|
378
670
|
uml_type: target_ref ? ::Xmi::Uml::Type.new(idref: target_ref) : nil,
|
|
379
|
-
upper_value:
|
|
380
|
-
lower_value:
|
|
671
|
+
upper_value: upper,
|
|
672
|
+
lower_value: lower,
|
|
381
673
|
)
|
|
382
674
|
|
|
383
675
|
AssociationEnd.new(end_id, model)
|
|
@@ -385,28 +677,28 @@ module Ea
|
|
|
385
677
|
|
|
386
678
|
# ---- Multiplicity helpers ---------------------------------------
|
|
387
679
|
|
|
388
|
-
#
|
|
389
|
-
#
|
|
390
|
-
#
|
|
391
|
-
def build_upper_value(raw, seed:,
|
|
680
|
+
# Callers emit bounds only when the EA cardinality field is set —
|
|
681
|
+
# EA's reference exports omit `<upperValue>`/`<lowerValue>`
|
|
682
|
+
# entirely on ends whose card is blank.
|
|
683
|
+
def build_upper_value(raw, seed:, owner_id:)
|
|
392
684
|
::Xmi::Uml::UpperValue.new(
|
|
393
685
|
type: "uml:LiteralUnlimitedNatural",
|
|
394
686
|
id: @context.id_allocator.allocate(
|
|
395
687
|
prefix: IdAllocator::LITERAL_INTEGER,
|
|
396
688
|
seed: seed,
|
|
397
|
-
|
|
689
|
+
owner_id: owner_id,
|
|
398
690
|
),
|
|
399
691
|
value: Cardinality.normalize_upper(raw),
|
|
400
692
|
)
|
|
401
693
|
end
|
|
402
694
|
|
|
403
|
-
def build_lower_value(raw, seed:,
|
|
695
|
+
def build_lower_value(raw, seed:, owner_id:)
|
|
404
696
|
::Xmi::Uml::LowerValue.new(
|
|
405
697
|
type: "uml:LiteralInteger",
|
|
406
698
|
id: @context.id_allocator.allocate(
|
|
407
699
|
prefix: IdAllocator::LITERAL_INTEGER,
|
|
408
700
|
seed: seed,
|
|
409
|
-
|
|
701
|
+
owner_id: owner_id,
|
|
410
702
|
),
|
|
411
703
|
value: Cardinality.normalize_lower(raw),
|
|
412
704
|
)
|
|
@@ -415,18 +707,31 @@ module Ea
|
|
|
415
707
|
# ---- Operation parameters ---------------------------------------
|
|
416
708
|
|
|
417
709
|
def operation_parameters(op)
|
|
418
|
-
params =
|
|
710
|
+
params = @context.params_for_operation(op.operationid)
|
|
419
711
|
.reject(&:return?)
|
|
420
712
|
.map { |p| build_owned_parameter(p) }
|
|
421
|
-
params << build_return_parameter(op) if op
|
|
713
|
+
params << build_return_parameter(op) if synthesized_return?(op)
|
|
422
714
|
params
|
|
423
715
|
end
|
|
424
716
|
|
|
717
|
+
# An untyped operation has no return to describe. One with no
|
|
718
|
+
# usable GUID has nothing to anchor the RT id's tail on, and the
|
|
719
|
+
# extension block skips it for the same reason — emitting it
|
|
720
|
+
# here would leave the two describing different operations.
|
|
721
|
+
# Type is blank-checked the way PrimitiveTypes normalizes names,
|
|
722
|
+
# so a whitespace-only Type cannot synthesize a return that
|
|
723
|
+
# primitive discovery then treats as absent.
|
|
724
|
+
def synthesized_return?(op)
|
|
725
|
+
return false if PrimitiveTypes.normalize_name(op.type).empty?
|
|
726
|
+
|
|
727
|
+
@context.identifiable?(op)
|
|
728
|
+
end
|
|
729
|
+
|
|
425
730
|
def build_owned_parameter(param)
|
|
426
731
|
::Xmi::Uml::OwnedParameter.new(
|
|
427
732
|
id: @context.xmi_id_for(param),
|
|
428
733
|
name: param.name,
|
|
429
|
-
direction: param.kind
|
|
734
|
+
direction: normalize_direction(param.kind),
|
|
430
735
|
)
|
|
431
736
|
end
|
|
432
737
|
|
|
@@ -435,10 +740,11 @@ module Ea
|
|
|
435
740
|
id: @context.id_allocator.allocate(
|
|
436
741
|
prefix: IdAllocator::RETURN_PARAMETER,
|
|
437
742
|
seed: "return-#{op.operationid}",
|
|
438
|
-
|
|
743
|
+
owner_id: @context.xmi_id_for(op),
|
|
439
744
|
),
|
|
440
745
|
name: "return",
|
|
441
746
|
direction: "return",
|
|
747
|
+
type: type_reference(op.type, op.classifier),
|
|
442
748
|
)
|
|
443
749
|
end
|
|
444
750
|
|
|
@@ -448,6 +754,26 @@ module Ea
|
|
|
448
754
|
obj.interface? ? "uml:Interface" : "uml:Class"
|
|
449
755
|
end
|
|
450
756
|
|
|
757
|
+
# EA's operation concurrency defaults to "Sequential" — omit
|
|
758
|
+
# it so the xmi gem doesn't emit concurrency="sequential"
|
|
759
|
+
# (EA's reference XMI omits the default).
|
|
760
|
+
def normalize_concurrency(raw)
|
|
761
|
+
return nil if raw.nil? || raw.to_s.strip.empty?
|
|
762
|
+
|
|
763
|
+
value = raw.to_s.downcase
|
|
764
|
+
value == "sequential" ? nil : value
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
# UML parameter direction defaults to "in" — omit it so the
|
|
768
|
+
# xmi gem doesn't emit direction="in" (EA's reference XMI
|
|
769
|
+
# omits the default).
|
|
770
|
+
def normalize_direction(raw)
|
|
771
|
+
return nil if raw.nil? || raw.to_s.strip.empty?
|
|
772
|
+
|
|
773
|
+
value = raw.to_s.downcase
|
|
774
|
+
value == "in" ? nil : value
|
|
775
|
+
end
|
|
776
|
+
|
|
451
777
|
def inheritance_connectors(obj, type)
|
|
452
778
|
@context.connectors_for(obj.ea_object_id).select do |conn|
|
|
453
779
|
conn.start_object_id == obj.ea_object_id && conn.connector_type == type
|
|
@@ -455,15 +781,32 @@ module Ea
|
|
|
455
781
|
end
|
|
456
782
|
|
|
457
783
|
# Realization connectors owned by this class — those where
|
|
458
|
-
# this object is the source (client)
|
|
459
|
-
#
|
|
784
|
+
# this object is the source (client). Each emits an
|
|
785
|
+
# `<interfaceRealization>`. Matched via the model predicate so
|
|
786
|
+
# both EA spellings (Realization/Realisation) are recognized.
|
|
460
787
|
def realization_connectors(obj)
|
|
461
|
-
|
|
788
|
+
@context.connectors_for(obj.ea_object_id).select do |conn|
|
|
789
|
+
conn.start_object_id == obj.ea_object_id && conn.realization?
|
|
790
|
+
end
|
|
462
791
|
end
|
|
463
792
|
|
|
464
|
-
|
|
793
|
+
# EA walks classifiers in tree-position order (t_object.TPos,
|
|
794
|
+
# ties broken by name then object id), not insertion order.
|
|
795
|
+
# The global LI counter depends on it.
|
|
796
|
+
def buildable_objects_in(pkg)
|
|
465
797
|
@context.objects_in_package(pkg.package_id)
|
|
466
798
|
.reject { |o| note?(o) || package_object?(o) }
|
|
799
|
+
.sort_by { |o| [o.sort_position, o.name.to_s, o.ea_object_id] }
|
|
800
|
+
end
|
|
801
|
+
|
|
802
|
+
# Children nested under a classifier via t_object.ParentID.
|
|
803
|
+
# Level-1 order in EA's reference is descending object id;
|
|
804
|
+
# deeper levels don't match any column we've found — the LI
|
|
805
|
+
# counters inside deep nests may drift until that's derived.
|
|
806
|
+
def nested_classifiers_under(obj)
|
|
807
|
+
@context.objects_in_package(obj.package_id)
|
|
808
|
+
.select { |o| o.parentid.to_i == obj.ea_object_id && adopted?(o) }
|
|
809
|
+
.sort_by { |o| [o.sort_position, -o.ea_object_id] }
|
|
467
810
|
end
|
|
468
811
|
|
|
469
812
|
def notes_in(pkg)
|
|
@@ -478,27 +821,39 @@ module Ea
|
|
|
478
821
|
obj.object_type == "Package"
|
|
479
822
|
end
|
|
480
823
|
|
|
481
|
-
def sorted_by_position(records)
|
|
482
|
-
records.sort_by { |r| [r.sort_position, r.name.to_s] }
|
|
483
|
-
end
|
|
484
|
-
|
|
485
824
|
def primitive?(obj)
|
|
486
825
|
obj.object_type == "PrimitiveType" ||
|
|
487
826
|
(obj.gentype == "Java" && obj.stereotype_is?("primitive"))
|
|
488
827
|
end
|
|
489
828
|
|
|
490
|
-
|
|
491
|
-
|
|
829
|
+
# The fallback id comes from the same module that emits the
|
|
830
|
+
# <primitivetypes> definitions, so the two normalize the name
|
|
831
|
+
# identically. A name that is blank once stripped gets no
|
|
832
|
+
# reference at all — PrimitiveTypes defines nothing for it.
|
|
833
|
+
def type_reference(type_name, classifier)
|
|
834
|
+
return nil if PrimitiveTypes.normalize_name(type_name).empty?
|
|
492
835
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
836
|
+
classifier_ref(classifier) || PrimitiveTypes.definition_id(type_name)
|
|
837
|
+
end
|
|
838
|
+
|
|
839
|
+
# QEA stores t_attribute.Classifier / t_operation.Classifier as
|
|
840
|
+
# an INTEGER t_object id ("0" = unresolved); EAP-era data can
|
|
841
|
+
# carry a GUID instead. Resolve either to the object's xmi id.
|
|
842
|
+
def classifier_ref(classifier)
|
|
843
|
+
text = PrimitiveTypes.normalize_name(classifier)
|
|
844
|
+
return nil if PrimitiveTypes.blank_classifier?(text)
|
|
845
|
+
return GuidFormat.ea_guid_to_xmi_id(text) unless text.match?(/\A\d+\z/)
|
|
846
|
+
|
|
847
|
+
obj = @context.object_by_id(text.to_i)
|
|
848
|
+
obj && @context.xmi_id_for(obj)
|
|
498
849
|
end
|
|
499
850
|
|
|
500
|
-
def type_reference_model(type_name,
|
|
501
|
-
|
|
851
|
+
def type_reference_model(type_name, classifier)
|
|
852
|
+
if (href = PrimitiveTypes.href_for(type_name, classifier))
|
|
853
|
+
return ::Xmi::Uml::Type.new(href: href)
|
|
854
|
+
end
|
|
855
|
+
|
|
856
|
+
ref = type_reference(type_name, classifier)
|
|
502
857
|
ref ? ::Xmi::Uml::Type.new(idref: ref) : nil
|
|
503
858
|
end
|
|
504
859
|
|
|
@@ -511,7 +866,7 @@ module Ea
|
|
|
511
866
|
end
|
|
512
867
|
|
|
513
868
|
def containment_for(conn, side)
|
|
514
|
-
side == :source ? conn.
|
|
869
|
+
side == :source ? conn.sourceisaggregate : conn.destisaggregate
|
|
515
870
|
end
|
|
516
871
|
|
|
517
872
|
# EA's t_connector does not expose per-end visibility (the
|
|
@@ -522,14 +877,6 @@ module Ea
|
|
|
522
877
|
nil
|
|
523
878
|
end
|
|
524
879
|
|
|
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
880
|
# InstanceSpecification classifier reference. EA stores this
|
|
534
881
|
# directly in t_object.classifier as the classifier's
|
|
535
882
|
# ea_object_id (NOT pdata1, which is a separate column used
|
|
@@ -574,7 +921,7 @@ module Ea
|
|
|
574
921
|
@context.id_allocator.allocate(
|
|
575
922
|
prefix: IdAllocator::SLOT,
|
|
576
923
|
seed: "slot-#{instance.ea_object_id}-#{binding.variable}",
|
|
577
|
-
|
|
924
|
+
owner_id: @context.xmi_id_for(instance),
|
|
578
925
|
)
|
|
579
926
|
end
|
|
580
927
|
|
|
@@ -582,7 +929,7 @@ module Ea
|
|
|
582
929
|
@context.id_allocator.allocate(
|
|
583
930
|
prefix: IdAllocator::OPAQUE_EXPRESSION,
|
|
584
931
|
seed: "oe-#{instance.ea_object_id}-#{binding.variable}",
|
|
585
|
-
|
|
932
|
+
owner_id: @context.xmi_id_for(instance),
|
|
586
933
|
)
|
|
587
934
|
end
|
|
588
935
|
|