ea 0.1.0 → 0.1.5
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/TODO.next/38-xmi-parser-feature-parity.md +75 -0
- data/TODO.next/39-ea-spa-cli-command.md +53 -0
- data/TODO.next/40-drop-lur-only-check-in-diagrams-extract.md +56 -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 +81 -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 +75 -0
- data/lib/ea/cli/command/list.rb +61 -0
- data/lib/ea/cli/command/parse.rb +29 -0
- data/lib/ea/cli/command/repository_builder.rb +64 -0
- data/lib/ea/cli/command/spa.rb +79 -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 +17 -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 +1015 -0
- data/lib/ea/xmi.rb +35 -0
- data/lib/ea.rb +10 -1
- metadata +387 -9
|
@@ -0,0 +1,1015 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "lutaml/path"
|
|
5
|
+
|
|
6
|
+
module Ea
|
|
7
|
+
module Xmi
|
|
8
|
+
# Parses EA XMI files into Lutaml::Uml::Document objects.
|
|
9
|
+
#
|
|
10
|
+
# Consolidates what was previously 6 separate modules
|
|
11
|
+
# (Xml, XmiBase, XmiConnector, XmiClassMembers, XmiToUml,
|
|
12
|
+
# XmiToUmlGeneralization) into a single coherent class.
|
|
13
|
+
#
|
|
14
|
+
# The XMI format handled here is EA/Sparx-specific.
|
|
15
|
+
class Parser
|
|
16
|
+
class << self
|
|
17
|
+
# Parse an XMI file into a UML Document.
|
|
18
|
+
def parse(xml)
|
|
19
|
+
new.parse(get_xmi_model(xml))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Parse XMI and serialize to Liquid drops for template rendering.
|
|
23
|
+
def serialize_to_liquid(xml, guidance = nil)
|
|
24
|
+
new.serialize_to_liquid(get_xmi_model(xml), guidance)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def get_xmi_model(xml)
|
|
30
|
+
::Xmi::Sparx::Root.parse_xml(File.read(xml))
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Public instance methods
|
|
35
|
+
|
|
36
|
+
def parse(xmi_model)
|
|
37
|
+
setup_model(xmi_model)
|
|
38
|
+
build_document(xmi_model)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def serialize_to_liquid(xmi_model, guidance = nil)
|
|
42
|
+
setup_model(xmi_model)
|
|
43
|
+
document = build_document(xmi_model)
|
|
44
|
+
lookup = LookupService.new(self)
|
|
45
|
+
options = {
|
|
46
|
+
xmi_root_model: @xmi_root_model,
|
|
47
|
+
id_name_mapping: @id_name_mapping,
|
|
48
|
+
lookup: lookup,
|
|
49
|
+
with_gen: true,
|
|
50
|
+
with_absolute_path: true,
|
|
51
|
+
}
|
|
52
|
+
LiquidDrops::RootDrop.new(document, guidance, options)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Public read access for collaborators (LookupService)
|
|
56
|
+
attr_reader :xmi_root_model
|
|
57
|
+
|
|
58
|
+
def xmi_index
|
|
59
|
+
if @xmi_index.nil? && @xmi_root_model
|
|
60
|
+
@xmi_index = @xmi_root_model.index
|
|
61
|
+
@id_name_mapping ||= @xmi_index.id_name_map
|
|
62
|
+
end
|
|
63
|
+
@xmi_index
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def id_name_mapping
|
|
67
|
+
@id_name_mapping
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Public lookup methods used by LookupService and Liquid drops
|
|
71
|
+
|
|
72
|
+
def fetch_connector(link_id)
|
|
73
|
+
xmi_index.find_connector(link_id)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def fetch_definition_node_value(link_id, node_name)
|
|
77
|
+
connector_node = fetch_connector(link_id)
|
|
78
|
+
return nil unless connector_node
|
|
79
|
+
|
|
80
|
+
node = connector_node.public_send(node_name.to_sym)
|
|
81
|
+
return nil unless node
|
|
82
|
+
|
|
83
|
+
documentation = node.documentation
|
|
84
|
+
if documentation.is_a?(::Xmi::Sparx::Element::Documentation)
|
|
85
|
+
documentation&.value
|
|
86
|
+
else
|
|
87
|
+
documentation
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def get_package_name(package) # rubocop:disable Metrics/AbcSize
|
|
92
|
+
return package.name unless package.name.nil?
|
|
93
|
+
|
|
94
|
+
connector = fetch_connector(package.id)
|
|
95
|
+
if connector.target&.model&.name
|
|
96
|
+
return "#{connector.target.model.name} (#{package.type.split(':').last})"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
"unnamed"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def find_packaged_element_by_id(id)
|
|
103
|
+
xmi_index.find_packaged_element(id)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def find_upper_level_packaged_element(klass_id)
|
|
107
|
+
xmi_index.find_parent(klass_id)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def find_subtype_of_from_owned_attribute_type(id) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
111
|
+
@pkg_elements_owned_attributes ||= begin
|
|
112
|
+
cache = {}
|
|
113
|
+
all_packaged_elements.each do |e|
|
|
114
|
+
next unless e.owned_attribute
|
|
115
|
+
|
|
116
|
+
e.owned_attribute.each do |oa|
|
|
117
|
+
next unless oa.association && oa.uml_type && oa.uml_type.idref
|
|
118
|
+
cache[oa.uml_type.idref] = e.name
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
cache
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
@pkg_elements_owned_attributes[id]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def find_subtype_of_from_generalization(id) # rubocop:disable Metrics/AbcSize,Metrics:CyclomaticComplexity,Metrics:MethodLength,Metrics:PerceivedComplexity
|
|
128
|
+
matched_element = xmi_index.find_element(id)
|
|
129
|
+
return unless matched_element&.links&.any?
|
|
130
|
+
|
|
131
|
+
matched_generalization = nil
|
|
132
|
+
matched_element.links.each do |link|
|
|
133
|
+
matched_generalization = link&.generalization&.find { |g| g.start == id }
|
|
134
|
+
break if matched_generalization
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
return if matched_generalization&.end.nil?
|
|
138
|
+
lookup_entity_name(matched_generalization.end)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def find_klass_packaged_element(path)
|
|
142
|
+
lutaml_path = Lutaml::Path.parse(path)
|
|
143
|
+
if lutaml_path.segments.one?
|
|
144
|
+
return find_klass_packaged_element_by_name(path)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
find_klass_packaged_element_by_path(lutaml_path)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def find_klass_packaged_element_by_name(name)
|
|
151
|
+
xmi_index.find_packaged_by_name_and_types(name, ["uml:Class", "uml:AssociationClass"])
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def find_enum_packaged_element_by_name(name)
|
|
155
|
+
xmi_index.packaged_elements_of_type("uml:Enumeration").find { |e| e.name == name }
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def select_dependencies_by_supplier(supplier_id)
|
|
159
|
+
xmi_index.packaged_elements_of_type("uml:Dependency").select { |e| e.supplier == supplier_id }
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def select_dependencies_by_client(client_id)
|
|
163
|
+
xmi_index.packaged_elements_of_type("uml:Dependency").select { |e| e.client == client_id }
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def find_packaged_element_by_name(name)
|
|
167
|
+
xmi_index.packaged_elements.find { |e| e.name == name }
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def doc_node_attribute_value(node_id, attr_name)
|
|
171
|
+
doc_node = fetch_element(node_id)
|
|
172
|
+
return unless doc_node
|
|
173
|
+
|
|
174
|
+
doc_node.properties&.public_send(
|
|
175
|
+
Lutaml::Model::Utils.snake_case(attr_name).to_sym
|
|
176
|
+
)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def lookup_attribute_documentation(xmi_id)
|
|
180
|
+
attribute_node = fetch_attribute_node(xmi_id)
|
|
181
|
+
return unless attribute_node&.documentation
|
|
182
|
+
|
|
183
|
+
attribute_node.documentation.value
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def lookup_element_prop_documentation(xmi_id)
|
|
187
|
+
element_node = xmi_index.find_element(xmi_id)
|
|
188
|
+
return unless element_node&.properties
|
|
189
|
+
|
|
190
|
+
element_node.properties.documentation
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def lookup_entity_name(xmi_id)
|
|
194
|
+
@id_name_mapping[xmi_id]
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def lookup_assoc_def(association)
|
|
198
|
+
connector = fetch_connector(association)
|
|
199
|
+
connector&.documentation&.value
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def get_ns_by_xmi_id(xmi_id)
|
|
203
|
+
return unless xmi_id
|
|
204
|
+
|
|
205
|
+
p = find_packaged_element_by_id(xmi_id)
|
|
206
|
+
return unless p
|
|
207
|
+
|
|
208
|
+
find_upper_level_packaged_element(p.id)&.name
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def fetch_element(klass_id)
|
|
212
|
+
xmi_index.find_element(klass_id)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def fetch_attribute_node(xmi_id)
|
|
216
|
+
xmi_index.find_attribute(xmi_id)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def all_packaged_elements
|
|
220
|
+
xmi_index.packaged_elements
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def select_all_packaged_elements(all_elements, model, type)
|
|
224
|
+
select_all_items(all_elements, model, type, :packaged_element)
|
|
225
|
+
all_elements.delete_if { |e| !e.is_a?(::Xmi::Uml::PackagedElement) }
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
private
|
|
229
|
+
|
|
230
|
+
# --- Model setup ---
|
|
231
|
+
|
|
232
|
+
def setup_model(xmi_model)
|
|
233
|
+
@xmi_root_model ||= xmi_model
|
|
234
|
+
if @xmi_index.nil?
|
|
235
|
+
@xmi_index = @xmi_root_model.index
|
|
236
|
+
@id_name_mapping = @xmi_index.id_name_map
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# --- Document building ---
|
|
241
|
+
|
|
242
|
+
def build_document(xmi_model)
|
|
243
|
+
::Lutaml::Uml::Document.new.tap do |doc|
|
|
244
|
+
doc.name = xmi_model.model.name
|
|
245
|
+
doc.packages = build_packages(xmi_model.model)
|
|
246
|
+
aggregate_document_extensions(doc)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Walk every package in the tree and aggregate document-level
|
|
251
|
+
# collections from per-package state. This brings the XMI
|
|
252
|
+
# parser output to parity with the QEA parser, which populates
|
|
253
|
+
# `document.diagrams` and `document.associations` directly
|
|
254
|
+
# during transformation.
|
|
255
|
+
def aggregate_document_extensions(doc)
|
|
256
|
+
diagrams = []
|
|
257
|
+
class_associations = []
|
|
258
|
+
walk_packages(doc.packages) do |pkg|
|
|
259
|
+
diagrams.concat(pkg.diagrams || [])
|
|
260
|
+
pkg.classes&.each { |k| class_associations.concat(k.associations || []) }
|
|
261
|
+
end
|
|
262
|
+
package_associations = build_package_level_associations
|
|
263
|
+
doc.diagrams = diagrams
|
|
264
|
+
doc.associations = class_associations + package_associations
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def walk_packages(packages, &block)
|
|
268
|
+
return unless packages
|
|
269
|
+
|
|
270
|
+
packages.each do |pkg|
|
|
271
|
+
yield pkg
|
|
272
|
+
walk_packages(pkg.packages, &block)
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Walks the raw XMI tree and collects every `<packagedElement
|
|
277
|
+
# xmi:type="uml:Association">` at any depth. Each becomes a
|
|
278
|
+
# `Lutaml::Uml::Association` with owner_end / member_end resolved
|
|
279
|
+
# from the `<memberEnd idref="...">` children. These are
|
|
280
|
+
# document-level relationships — UML associations live at the
|
|
281
|
+
# package level, not inside the participating classes.
|
|
282
|
+
def build_package_level_associations
|
|
283
|
+
result = []
|
|
284
|
+
walk_xmi_packaged_elements(@xmi_root_model.model) do |element|
|
|
285
|
+
next unless element.type?("uml:Association")
|
|
286
|
+
|
|
287
|
+
assoc = build_package_association(element)
|
|
288
|
+
result << assoc if assoc
|
|
289
|
+
end
|
|
290
|
+
result
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def build_package_association(element)
|
|
294
|
+
member_ends = element.member_ends || []
|
|
295
|
+
return nil if member_ends.empty?
|
|
296
|
+
|
|
297
|
+
owner_ref = member_ends.first&.idref
|
|
298
|
+
member_ref = member_ends.last&.idref
|
|
299
|
+
|
|
300
|
+
::Lutaml::Uml::Association.new.tap do |a|
|
|
301
|
+
a.xmi_id = element.id
|
|
302
|
+
a.name = element.name
|
|
303
|
+
a.owner_end = lookup_entity_name(owner_ref) || owner_ref
|
|
304
|
+
a.member_end = lookup_entity_name(member_ref) || member_ref
|
|
305
|
+
a.owner_end_xmi_id = owner_ref
|
|
306
|
+
a.member_end_xmi_id = member_ref
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def walk_xmi_packaged_elements(node, &block)
|
|
311
|
+
return unless node.is_a?(::Xmi::Uml::PackagedElement) ||
|
|
312
|
+
node.is_a?(::Xmi::Uml::UmlModel)
|
|
313
|
+
return unless node.packaged_element
|
|
314
|
+
|
|
315
|
+
node.packaged_element.each do |element|
|
|
316
|
+
yield element
|
|
317
|
+
walk_xmi_packaged_elements(element, &block)
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def build_packages(model)
|
|
322
|
+
return [] if model.packaged_element.nil?
|
|
323
|
+
|
|
324
|
+
packages = model.packaged_element.select { |e| e.type?("uml:Package") }
|
|
325
|
+
packages.map { |package| build_package(package) }
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def build_package(package)
|
|
329
|
+
::Lutaml::Uml::Package.new.tap do |pkg|
|
|
330
|
+
pkg.xmi_id = package.id
|
|
331
|
+
pkg.name = get_package_name(package)
|
|
332
|
+
pkg.definition = doc_node_attribute_value(package.id, "documentation")
|
|
333
|
+
st = doc_node_attribute_value(package.id, "stereotype")
|
|
334
|
+
pkg.stereotype = [st] if st
|
|
335
|
+
|
|
336
|
+
pkg.packages = build_packages(package)
|
|
337
|
+
pkg.classes = build_classes(package)
|
|
338
|
+
pkg.enums = build_enums(package)
|
|
339
|
+
pkg.data_types = build_data_types(package)
|
|
340
|
+
pkg.instances = build_instances(package)
|
|
341
|
+
pkg.diagrams = build_diagrams(package.id)
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def build_classes(package)
|
|
346
|
+
return [] if package.packaged_element.nil?
|
|
347
|
+
|
|
348
|
+
klasses = package.packaged_element.select do |e|
|
|
349
|
+
e.type?("uml:Class") || e.type?("uml:AssociationClass") ||
|
|
350
|
+
e.type?("uml:Interface") || e.type?("uml:Signal") ||
|
|
351
|
+
e.type?("uml:Component")
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
klasses.map { |klass| build_class(klass) }
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# Walks `<packagedElement xmi:type="uml:InstanceSpecification">`
|
|
358
|
+
# children of a package. Each becomes a `Lutaml::Uml::Instance`
|
|
359
|
+
# with name, xmi_id, classifier (resolved via id_name_mapping),
|
|
360
|
+
# and definition. Mirrors the QEA factory's InstanceTransformer.
|
|
361
|
+
def build_instances(package)
|
|
362
|
+
return [] if package.packaged_element.nil?
|
|
363
|
+
|
|
364
|
+
package.packaged_element
|
|
365
|
+
.select { |e| e.type?("uml:InstanceSpecification") }
|
|
366
|
+
.map { |spec| build_instance(spec) }
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def build_instance(spec)
|
|
370
|
+
::Lutaml::Uml::Instance.new.tap do |instance|
|
|
371
|
+
instance.xmi_id = spec.id
|
|
372
|
+
instance.name = spec.name
|
|
373
|
+
instance.definition = doc_node_attribute_value(spec.id, "documentation")
|
|
374
|
+
classifier_ref = spec.classifier
|
|
375
|
+
instance.classifier = lookup_entity_name(classifier_ref) if classifier_ref
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def build_class(klass) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics:CyclomaticComplexity,Metrics:PerceivedComplexity
|
|
380
|
+
::Lutaml::Uml::UmlClass.new.tap do |k|
|
|
381
|
+
k.xmi_id = klass.id
|
|
382
|
+
k.name = klass.name
|
|
383
|
+
k.type = klass.type.split(":").last
|
|
384
|
+
k.is_abstract = doc_node_attribute_value(klass.id, "isAbstract")
|
|
385
|
+
k.definition = doc_node_attribute_value(klass.id, "documentation")
|
|
386
|
+
k_st = doc_node_attribute_value(klass.id, "stereotype")
|
|
387
|
+
k.stereotype = [k_st] if k_st
|
|
388
|
+
|
|
389
|
+
k.attributes = build_class_attributes(klass)
|
|
390
|
+
k.associations = build_associations(klass.id)
|
|
391
|
+
k.operations = build_operations(klass)
|
|
392
|
+
k.constraints = build_constraints(klass.id)
|
|
393
|
+
k.association_generalization = build_assoc_generalizations(klass)
|
|
394
|
+
|
|
395
|
+
if klass.type?("uml:Class")
|
|
396
|
+
k.generalization = build_generalization(klass)
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
def build_enums(package) # rubocop:disable Metrics:MethodLength,Metrics:AbcSize
|
|
402
|
+
return [] if package.packaged_element.nil?
|
|
403
|
+
|
|
404
|
+
package.packaged_element
|
|
405
|
+
.select { |e| e.type?("uml:Enumeration") }
|
|
406
|
+
.map do |enum|
|
|
407
|
+
::Lutaml::Uml::Enum.new.tap do |en|
|
|
408
|
+
en.xmi_id = enum.id
|
|
409
|
+
en.name = enum.name
|
|
410
|
+
en.values = build_values(enum)
|
|
411
|
+
en.definition = doc_node_attribute_value(enum.id, "documentation")
|
|
412
|
+
en_st = doc_node_attribute_value(enum.id, "stereotype")
|
|
413
|
+
en.stereotype = [en_st] if en_st
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def build_data_types(package) # rubocop:disable Metrics:AbcSize,Metrics:MethodLength
|
|
419
|
+
return [] if package.packaged_element.nil?
|
|
420
|
+
|
|
421
|
+
package.packaged_element
|
|
422
|
+
.select { |e| e.type?("uml:DataType") }
|
|
423
|
+
.map do |dt|
|
|
424
|
+
::Lutaml::Uml::DataType.new.tap do |data_type|
|
|
425
|
+
data_type.xmi_id = dt.id
|
|
426
|
+
data_type.name = dt.name
|
|
427
|
+
data_type.is_abstract = doc_node_attribute_value(dt.id, "isAbstract")
|
|
428
|
+
data_type.definition = doc_node_attribute_value(dt.id, "documentation")
|
|
429
|
+
dt_st = doc_node_attribute_value(dt.id, "stereotype")
|
|
430
|
+
data_type.stereotype = [dt_st] if dt_st
|
|
431
|
+
|
|
432
|
+
data_type.attributes = build_class_attributes(dt)
|
|
433
|
+
data_type.operations = build_operations(dt)
|
|
434
|
+
data_type.associations = build_associations(dt.id)
|
|
435
|
+
data_type.constraints = build_constraints(dt.id)
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def build_diagrams(node_id) # rubocop:disable Metrics:AbcSize,Metrics:MethodLength
|
|
441
|
+
return [] if @xmi_root_model.extension&.diagrams&.diagram.nil?
|
|
442
|
+
|
|
443
|
+
diagram_lookup[node_id].map do |diagram|
|
|
444
|
+
::Lutaml::Uml::Diagram.new.tap do |dia|
|
|
445
|
+
dia.xmi_id = diagram.id
|
|
446
|
+
dia.name = diagram&.properties&.name
|
|
447
|
+
dia.definition = diagram&.properties&.documentation
|
|
448
|
+
|
|
449
|
+
package_id = diagram&.model&.package
|
|
450
|
+
if package_id
|
|
451
|
+
dia.package_id = package_id
|
|
452
|
+
dia.package_name = find_packaged_element_by_id(package_id)&.name
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
def build_class_attributes(klass) # rubocop:disable Metrics:AbcSize,Metrics:CyclomaticComplexity,Metrics:MethodLength
|
|
459
|
+
return [] if klass.owned_attribute.nil?
|
|
460
|
+
|
|
461
|
+
all_props = klass.owned_attribute.select { |attr| attr.type?("uml:Property") }
|
|
462
|
+
all_props.filter_map { |oa| build_attribute(oa) }
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
def build_attribute(owned_attr) # rubocop:disable Metrics:AbcSize,Metrics:MethodLength
|
|
466
|
+
uml_type = owned_attr.uml_type
|
|
467
|
+
uml_type_idref = uml_type.idref if uml_type
|
|
468
|
+
|
|
469
|
+
::Lutaml::Uml::TopElementAttribute.new.tap do |attr|
|
|
470
|
+
attr.id = owned_attr.id
|
|
471
|
+
attr.name = owned_attr.name
|
|
472
|
+
attr.type = lookup_entity_name(uml_type_idref) || uml_type_idref
|
|
473
|
+
attr.xmi_id = uml_type_idref
|
|
474
|
+
attr.is_derived = !!owned_attr.is_derived
|
|
475
|
+
attr.cardinality = ::Lutaml::Uml::Cardinality.new.tap do |car|
|
|
476
|
+
car.min = owned_attr.lower_value&.value
|
|
477
|
+
car.max = owned_attr.upper_value&.value
|
|
478
|
+
end
|
|
479
|
+
attr.definition = lookup_attribute_documentation(owned_attr.id)
|
|
480
|
+
|
|
481
|
+
if owned_attr.association
|
|
482
|
+
attr.association = owned_attr.association
|
|
483
|
+
attr.definition = lookup_assoc_def(owned_attr.association)
|
|
484
|
+
attr.type_ns = get_ns_by_xmi_id(attr.xmi_id)
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
def build_associations(xmi_id) # rubocop:disable Metrics:AbcSize,Metrics:CyclomaticComplexity,Metrics:MethodLength,Metrics:PerceivedComplexity
|
|
490
|
+
matched_element = xmi_index&.find_element(xmi_id)
|
|
491
|
+
return if !matched_element || !matched_element.links
|
|
492
|
+
|
|
493
|
+
links = []
|
|
494
|
+
matched_element.links.each do |link|
|
|
495
|
+
links << link.association if link.association.any?
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
links.flatten.compact.filter_map do |assoc|
|
|
499
|
+
build_association(assoc, xmi_id)
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
def build_association(assoc, xmi_id) # rubocop:disable Metrics:AbcSize,Metrics:CyclomaticComplexity,Metrics:MethodLength
|
|
504
|
+
link_member = assoc.start == xmi_id ? "end" : "start"
|
|
505
|
+
link_owner = link_member == "start" ? "end" : "start"
|
|
506
|
+
|
|
507
|
+
member_end, member_end_type, member_end_cardinality,
|
|
508
|
+
member_end_attribute_name, member_end_xmi_id =
|
|
509
|
+
serialize_member_type(xmi_id, assoc, link_member)
|
|
510
|
+
|
|
511
|
+
owner_end = serialize_owned_type(xmi_id, assoc, link_owner)
|
|
512
|
+
doc_node = link_member == "start" ? "source" : "target"
|
|
513
|
+
definition = fetch_definition_node_value(assoc.id, doc_node)
|
|
514
|
+
|
|
515
|
+
owner_end_attribute_name = find_owner_attribute_name(xmi_id, assoc.id)
|
|
516
|
+
|
|
517
|
+
return nil unless member_end &&
|
|
518
|
+
((member_end_type != "aggregation") ||
|
|
519
|
+
(member_end_type == "aggregation" && member_end_attribute_name))
|
|
520
|
+
|
|
521
|
+
::Lutaml::Uml::Association.new.tap do |association|
|
|
522
|
+
association.xmi_id = assoc.id
|
|
523
|
+
association.member_end = member_end
|
|
524
|
+
association.member_end_type = member_end_type
|
|
525
|
+
association.member_end_cardinality = build_cardinality(member_end_cardinality)
|
|
526
|
+
association.member_end_attribute_name = member_end_attribute_name
|
|
527
|
+
association.member_end_xmi_id = member_end_xmi_id
|
|
528
|
+
association.owner_end = owner_end
|
|
529
|
+
association.owner_end_xmi_id = xmi_id
|
|
530
|
+
association.owner_end_attribute_name = owner_end_attribute_name
|
|
531
|
+
association.definition = definition
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
def build_cardinality(hash)
|
|
536
|
+
return nil unless hash
|
|
537
|
+
|
|
538
|
+
::Lutaml::Uml::Cardinality.new.tap do |cardinality|
|
|
539
|
+
cardinality.min = hash[:min]
|
|
540
|
+
cardinality.max = hash[:max]
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
def build_operations(klass) # rubocop:disable Metrics:MethodLength,Metrics:AbcSize
|
|
545
|
+
return [] if klass.owned_operation.nil?
|
|
546
|
+
|
|
547
|
+
klass.owned_operation.filter_map do |operation|
|
|
548
|
+
uml_type = operation.uml_type.first
|
|
549
|
+
uml_type_idref = uml_type.idref if uml_type
|
|
550
|
+
|
|
551
|
+
if !operation.class.attributes.key?(:association) || operation.association.nil?
|
|
552
|
+
::Lutaml::Uml::Operation.new.tap do |op|
|
|
553
|
+
op.id = operation.id
|
|
554
|
+
op.xmi_id = uml_type_idref
|
|
555
|
+
op.name = operation.name
|
|
556
|
+
op.definition = lookup_attribute_documentation(operation.id)
|
|
557
|
+
end
|
|
558
|
+
end
|
|
559
|
+
end
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def build_constraints(klass_id) # rubocop:disable Metrics:MethodLength,Metrics:AbcSize
|
|
563
|
+
connector_node = fetch_connector(klass_id)
|
|
564
|
+
return [] if connector_node.nil?
|
|
565
|
+
|
|
566
|
+
constraints = %i[source target].map do |st|
|
|
567
|
+
connector_node.public_send(st).constraints.constraint
|
|
568
|
+
end.flatten
|
|
569
|
+
|
|
570
|
+
constraints.map do |constraint|
|
|
571
|
+
::Lutaml::Uml::Constraint.new.tap do |con|
|
|
572
|
+
con.name = CGI.unescapeHTML(constraint.name)
|
|
573
|
+
con.type = constraint.type
|
|
574
|
+
con.weight = constraint.weight
|
|
575
|
+
con.status = constraint.status
|
|
576
|
+
end
|
|
577
|
+
end
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
def build_values(enum) # rubocop:disable Metrics:MethodLength,Metrics:CyclomaticComplexity,Metrics:AbcSize
|
|
581
|
+
return [] if enum.owned_literal.nil?
|
|
582
|
+
|
|
583
|
+
enum.owned_literal
|
|
584
|
+
.select { |lit| lit.type?("uml:EnumerationLiteral") }
|
|
585
|
+
.map do |owned_literal|
|
|
586
|
+
uml_type_id = owned_literal&.uml_type&.idref
|
|
587
|
+
|
|
588
|
+
::Lutaml::Uml::Value.new.tap do |value|
|
|
589
|
+
value.name = owned_literal.name
|
|
590
|
+
value.type = lookup_entity_name(uml_type_id) || uml_type_id
|
|
591
|
+
value.definition = lookup_attribute_documentation(owned_literal.id)
|
|
592
|
+
end
|
|
593
|
+
end
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
# --- Generalization building ---
|
|
597
|
+
|
|
598
|
+
def build_generalization(klass) # rubocop:disable Metrics:AbcSize,Metrics:MethodLength,Metrics:CyclomaticComplexity,Metrics:PerceivedComplexity
|
|
599
|
+
uml_general_obj, next_general_node_id = get_uml_general(klass.id)
|
|
600
|
+
return uml_general_obj unless next_general_node_id
|
|
601
|
+
|
|
602
|
+
if uml_general_obj.general
|
|
603
|
+
inherited_props = []
|
|
604
|
+
inherited_assoc_props = []
|
|
605
|
+
level = 0
|
|
606
|
+
|
|
607
|
+
loop_general_item(
|
|
608
|
+
uml_general_obj.general, level, inherited_props, inherited_assoc_props
|
|
609
|
+
)
|
|
610
|
+
uml_general_obj.inherited_props = inherited_props.reverse
|
|
611
|
+
uml_general_obj.inherited_assoc_props = inherited_assoc_props.reverse
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
uml_general_obj
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
def build_generalization_attributes(uml_general_obj) # rubocop:disable Metrics:AbcSize,Metrics:MethodLength
|
|
618
|
+
upper_klass = uml_general_obj.general_upper_klass
|
|
619
|
+
gen_attrs = uml_general_obj.general_attributes
|
|
620
|
+
gen_name = uml_general_obj.general_name
|
|
621
|
+
|
|
622
|
+
gen_attrs&.each do |i|
|
|
623
|
+
name_ns = case i.type_ns
|
|
624
|
+
when "core", "gml"
|
|
625
|
+
upper_klass
|
|
626
|
+
else
|
|
627
|
+
i.type_ns
|
|
628
|
+
end
|
|
629
|
+
name_ns = upper_klass if name_ns.nil?
|
|
630
|
+
|
|
631
|
+
i.name_ns = name_ns
|
|
632
|
+
i.gen_name = gen_name
|
|
633
|
+
i.name = "" if i.name.nil?
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
gen_attrs
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
def get_uml_general(general_id) # rubocop:disable Metrics:AbcSize,Metrics:MethodLength
|
|
640
|
+
general_node = find_packaged_element_by_id(general_id)
|
|
641
|
+
return [] unless general_node
|
|
642
|
+
|
|
643
|
+
general_node_attrs = get_uml_general_attributes(general_node)
|
|
644
|
+
general_upper_klass = find_upper_level_packaged_element(general_id)
|
|
645
|
+
next_general_node_id = general_node.generalization.first&.general
|
|
646
|
+
|
|
647
|
+
uml_general = build_uml_general_node(
|
|
648
|
+
general_id, general_node, general_node_attrs,
|
|
649
|
+
general_upper_klass, next_general_node_id
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
assign_general_properties(uml_general)
|
|
653
|
+
|
|
654
|
+
[uml_general, next_general_node_id]
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
def build_uml_general_node(general_id, general_node, attrs, upper_klass, next_id)
|
|
658
|
+
gen = ::Lutaml::Uml::Generalization.new
|
|
659
|
+
assign_general_basic_props(gen, general_id, general_node, attrs, upper_klass)
|
|
660
|
+
assign_stereotype(gen, general_id)
|
|
661
|
+
assign_parent_generalization(gen, general_node, next_id)
|
|
662
|
+
gen
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
def assign_general_basic_props(gen, general_id, general_node, attrs, upper_klass)
|
|
666
|
+
gen.general_id = general_id
|
|
667
|
+
gen.general_name = general_node.name
|
|
668
|
+
gen.general_attributes = attrs
|
|
669
|
+
gen.general_upper_klass = upper_klass ? get_package_name(upper_klass) : nil
|
|
670
|
+
gen.name = general_node.name
|
|
671
|
+
gen.type = general_node.type
|
|
672
|
+
gen.definition = lookup_element_prop_documentation(general_id)
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
def assign_stereotype(gen, general_id)
|
|
676
|
+
gen_st = doc_node_attribute_value(general_id, "stereotype")
|
|
677
|
+
gen.stereotype = [gen_st] if gen_st
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
def assign_parent_generalization(gen, general_node, next_id)
|
|
681
|
+
return unless next_id
|
|
682
|
+
|
|
683
|
+
gen.general = set_uml_generalization(next_id)
|
|
684
|
+
gen.has_general = true
|
|
685
|
+
gen.general_id = general_node.id
|
|
686
|
+
gen.general_name = general_node.name
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
def assign_general_properties(uml_general)
|
|
690
|
+
uml_general.attributes = build_generalization_attributes(uml_general)
|
|
691
|
+
uml_general.owned_props = uml_general.attributes.select { |a| a.association.nil? }
|
|
692
|
+
uml_general.assoc_props = uml_general.attributes.select(&:association)
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
def get_uml_general_attributes(general_node) # rubocop:disable Metrics:AbcSize,Metrics:MethodLength
|
|
696
|
+
attrs = build_class_attributes(general_node)
|
|
697
|
+
|
|
698
|
+
attrs.map do |attr|
|
|
699
|
+
::Lutaml::Uml::GeneralAttribute.new.tap do |gen_attr|
|
|
700
|
+
gen_attr.id = attr.id
|
|
701
|
+
gen_attr.name = attr.name
|
|
702
|
+
gen_attr.type = attr.type
|
|
703
|
+
gen_attr.xmi_id = attr.xmi_id
|
|
704
|
+
gen_attr.is_derived = !!attr.is_derived
|
|
705
|
+
gen_attr.cardinality = attr.cardinality
|
|
706
|
+
gen_attr.definition = attr.definition&.strip
|
|
707
|
+
gen_attr.association = attr.association
|
|
708
|
+
gen_attr.has_association = !!attr.association
|
|
709
|
+
gen_attr.type_ns = attr.type_ns
|
|
710
|
+
end
|
|
711
|
+
end
|
|
712
|
+
end
|
|
713
|
+
|
|
714
|
+
def set_uml_generalization(general_id)
|
|
715
|
+
uml_general_obj, next_general_node_id = get_uml_general(general_id)
|
|
716
|
+
|
|
717
|
+
if next_general_node_id
|
|
718
|
+
uml_general_obj.general = set_uml_generalization(next_general_node_id)
|
|
719
|
+
uml_general_obj.has_general = true
|
|
720
|
+
end
|
|
721
|
+
|
|
722
|
+
uml_general_obj
|
|
723
|
+
end
|
|
724
|
+
|
|
725
|
+
def loop_general_item( # rubocop:disable Metrics:MethodLength,Metrics:AbcSize,Metrics:PerceivedComplexity,Metrics:CyclomaticComplexity
|
|
726
|
+
general_item, level, inherited_props, inherited_assoc_props
|
|
727
|
+
)
|
|
728
|
+
gen_upper_klass = general_item.general_upper_klass
|
|
729
|
+
gen_name = general_item.general_name
|
|
730
|
+
|
|
731
|
+
general_item.attributes.reverse_each do |attr|
|
|
732
|
+
attr.upper_klass = gen_upper_klass
|
|
733
|
+
attr.gen_name = gen_name
|
|
734
|
+
attr.level = level
|
|
735
|
+
|
|
736
|
+
if attr.association
|
|
737
|
+
inherited_assoc_props << attr
|
|
738
|
+
else
|
|
739
|
+
inherited_props << attr
|
|
740
|
+
end
|
|
741
|
+
end
|
|
742
|
+
|
|
743
|
+
if general_item&.has_general && general_item.general
|
|
744
|
+
level += 1
|
|
745
|
+
loop_general_item(
|
|
746
|
+
general_item.general, level, inherited_props, inherited_assoc_props
|
|
747
|
+
)
|
|
748
|
+
end
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
def build_assoc_generalizations(klass) # rubocop:disable Metrics:AbcSize
|
|
752
|
+
return [] if klass.generalization.nil? || klass.generalization.empty?
|
|
753
|
+
|
|
754
|
+
klass.generalization.map do |gen|
|
|
755
|
+
::Lutaml::Uml::AssociationGeneralization.new.tap do |assoc_gen|
|
|
756
|
+
assoc_gen.id = gen.id
|
|
757
|
+
assoc_gen.type = gen.type
|
|
758
|
+
assoc_gen.general = gen.general
|
|
759
|
+
end
|
|
760
|
+
end
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
# --- Connector serialization ---
|
|
764
|
+
|
|
765
|
+
def serialize_owned_type(owner_xmi_id, link, link_owner_name)
|
|
766
|
+
case link
|
|
767
|
+
when ::Xmi::Sparx::Element::NoteLink
|
|
768
|
+
return
|
|
769
|
+
when ::Xmi::Sparx::Element::Generalization
|
|
770
|
+
owner_end, = generalization_association(owner_xmi_id, link)
|
|
771
|
+
return owner_end
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
xmi_id = link.public_send(link_owner_name.to_sym)
|
|
775
|
+
lookup_entity_name(xmi_id) || connector_source_name(xmi_id)
|
|
776
|
+
end
|
|
777
|
+
|
|
778
|
+
def serialize_member_end(owner_xmi_id, link) # rubocop:disable Metrics:MethodLength,Metrics:AbcSize,Metrics:CyclomaticComplexity
|
|
779
|
+
case link.name
|
|
780
|
+
when "NoteLink"
|
|
781
|
+
return
|
|
782
|
+
when "Generalization"
|
|
783
|
+
return generalization_association(owner_xmi_id, link)
|
|
784
|
+
end
|
|
785
|
+
|
|
786
|
+
xmi_id = link.start
|
|
787
|
+
source_or_target = :source
|
|
788
|
+
|
|
789
|
+
if link.start == owner_xmi_id
|
|
790
|
+
xmi_id = link.end
|
|
791
|
+
source_or_target = :target
|
|
792
|
+
end
|
|
793
|
+
|
|
794
|
+
connector = fetch_connector(link.id)
|
|
795
|
+
ea_type = connector&.properties&.ea_type
|
|
796
|
+
member_end_type = ea_type&.downcase
|
|
797
|
+
|
|
798
|
+
member_end = member_end_name(xmi_id, source_or_target, link)
|
|
799
|
+
[member_end, member_end_type, xmi_id]
|
|
800
|
+
end
|
|
801
|
+
|
|
802
|
+
def member_end_name(xmi_id, source_or_target, link) # rubocop:disable Metrics:MethodLength
|
|
803
|
+
connector_label = connector_labels(xmi_id, source_or_target)
|
|
804
|
+
entity_name = lookup_entity_name(xmi_id)
|
|
805
|
+
connector_name = connector_name_by_source_or_target(xmi_id, source_or_target)
|
|
806
|
+
|
|
807
|
+
case link
|
|
808
|
+
when ::Xmi::Sparx::Element::Aggregation
|
|
809
|
+
connector_label || entity_name || connector_name
|
|
810
|
+
else
|
|
811
|
+
entity_name || connector_name
|
|
812
|
+
end
|
|
813
|
+
end
|
|
814
|
+
|
|
815
|
+
def serialize_member_type(owner_xmi_id, link, link_member_name) # rubocop:disable Metrics:MethodLength,Metrics:AbcSize
|
|
816
|
+
member_end, member_end_type, xmi_id =
|
|
817
|
+
serialize_member_end(owner_xmi_id, link)
|
|
818
|
+
|
|
819
|
+
if link.is_a?(::Xmi::Sparx::Element::Association)
|
|
820
|
+
connector_type = link_member_name == "start" ? "source" : "target"
|
|
821
|
+
member_end_cardinality, member_end_attribute_name =
|
|
822
|
+
fetch_assoc_connector(link.id, connector_type)
|
|
823
|
+
else
|
|
824
|
+
member_end_cardinality, member_end_attribute_name =
|
|
825
|
+
fetch_owned_attribute_node(xmi_id)
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
if fetch_connector_name(link.id)
|
|
829
|
+
member_end = fetch_connector_name(link.id)
|
|
830
|
+
end
|
|
831
|
+
|
|
832
|
+
[member_end, member_end_type, member_end_cardinality,
|
|
833
|
+
member_end_attribute_name, xmi_id]
|
|
834
|
+
end
|
|
835
|
+
|
|
836
|
+
def fetch_connector_name(link_id)
|
|
837
|
+
connector = fetch_connector(link_id)
|
|
838
|
+
connector&.name
|
|
839
|
+
end
|
|
840
|
+
|
|
841
|
+
def fetch_assoc_connector(link_id, connector_type)
|
|
842
|
+
connector = fetch_connector(link_id)
|
|
843
|
+
return [nil, nil] unless connector
|
|
844
|
+
|
|
845
|
+
assoc_connector = connector.public_send(connector_type.to_sym)
|
|
846
|
+
return [nil, nil] unless assoc_connector
|
|
847
|
+
|
|
848
|
+
[extract_cardinality(assoc_connector), extract_attribute_name(assoc_connector)]
|
|
849
|
+
end
|
|
850
|
+
|
|
851
|
+
def extract_cardinality(assoc_connector)
|
|
852
|
+
assoc_connector_type = assoc_connector.type
|
|
853
|
+
min = nil
|
|
854
|
+
max = nil
|
|
855
|
+
if assoc_connector_type&.multiplicity
|
|
856
|
+
cardinality = assoc_connector_type.multiplicity.split("..")
|
|
857
|
+
cardinality.unshift("1") if cardinality.length == 1
|
|
858
|
+
min, max = cardinality
|
|
859
|
+
end
|
|
860
|
+
cardinality_min_max_value(min, max)
|
|
861
|
+
end
|
|
862
|
+
|
|
863
|
+
def extract_attribute_name(assoc_connector)
|
|
864
|
+
assoc_connector.role ? assoc_connector.model.name : nil
|
|
865
|
+
end
|
|
866
|
+
|
|
867
|
+
def generalization_association(owner_xmi_id, link) # rubocop:disable Metrics:MethodLength
|
|
868
|
+
member_end_type = "generalization"
|
|
869
|
+
xmi_id = link.start
|
|
870
|
+
source_or_target = :source
|
|
871
|
+
|
|
872
|
+
if link.start == owner_xmi_id
|
|
873
|
+
member_end_type = "inheritance"
|
|
874
|
+
xmi_id = link.end
|
|
875
|
+
source_or_target = :target
|
|
876
|
+
end
|
|
877
|
+
|
|
878
|
+
member_end = member_end_name(xmi_id, source_or_target, link)
|
|
879
|
+
[member_end, member_end_type, xmi_id]
|
|
880
|
+
end
|
|
881
|
+
|
|
882
|
+
def fetch_owned_attribute_node(xmi_id)
|
|
883
|
+
oa = xmi_index.find_owned_attrs_by_type(xmi_id)
|
|
884
|
+
.find { |a| !!a.association }
|
|
885
|
+
|
|
886
|
+
if oa
|
|
887
|
+
cardinality = cardinality_min_max_value(
|
|
888
|
+
oa.lower_value&.value, oa.upper_value&.value
|
|
889
|
+
)
|
|
890
|
+
oa_name = oa.name
|
|
891
|
+
end
|
|
892
|
+
|
|
893
|
+
[cardinality, oa_name]
|
|
894
|
+
end
|
|
895
|
+
|
|
896
|
+
def find_owner_attribute_name(owner_xmi_id, assoc_id)
|
|
897
|
+
owner_node = find_packaged_element_by_id(owner_xmi_id)
|
|
898
|
+
return nil unless owner_node&.owned_attribute
|
|
899
|
+
|
|
900
|
+
owned_attr = owner_node.owned_attribute.find { |oa| oa.association == assoc_id }
|
|
901
|
+
owned_attr&.name
|
|
902
|
+
end
|
|
903
|
+
|
|
904
|
+
# --- Connector lookup index ---
|
|
905
|
+
|
|
906
|
+
def diagram_lookup
|
|
907
|
+
@diagram_lookup ||= begin
|
|
908
|
+
idx = Hash.new { |h, k| h[k] = [] }
|
|
909
|
+
xmi_diagrams.each { |d| idx[d.model.package] << d if d.model&.package }
|
|
910
|
+
idx
|
|
911
|
+
end
|
|
912
|
+
end
|
|
913
|
+
|
|
914
|
+
def xmi_diagrams
|
|
915
|
+
@xmi_root_model.extension&.diagrams&.diagram || []
|
|
916
|
+
end
|
|
917
|
+
|
|
918
|
+
def connector_lookup
|
|
919
|
+
@connector_lookup ||= begin
|
|
920
|
+
lookup = {}
|
|
921
|
+
connectors = @xmi_root_model.extension&.connectors&.connector || []
|
|
922
|
+
connectors.each { |con| index_connector_directions(con, lookup) }
|
|
923
|
+
lookup
|
|
924
|
+
end
|
|
925
|
+
end
|
|
926
|
+
|
|
927
|
+
def index_connector_directions(con, lookup)
|
|
928
|
+
%i[source target].each do |dir|
|
|
929
|
+
idref = con.public_send(dir)&.idref
|
|
930
|
+
lookup[[dir, idref]] = con if idref
|
|
931
|
+
end
|
|
932
|
+
end
|
|
933
|
+
|
|
934
|
+
def connector_node_by_id(xmi_id, source_or_target)
|
|
935
|
+
connector_lookup[[source_or_target.to_sym, xmi_id]]
|
|
936
|
+
end
|
|
937
|
+
|
|
938
|
+
def connector_name_by_source_or_target(xmi_id, source_or_target) # rubocop:disable Metrics:AbcSize
|
|
939
|
+
node = connector_node_by_id(xmi_id, source_or_target)
|
|
940
|
+
return node.name if node&.name
|
|
941
|
+
|
|
942
|
+
return if node.nil? ||
|
|
943
|
+
node.public_send(source_or_target.to_sym).nil? ||
|
|
944
|
+
node.public_send(source_or_target.to_sym).model.nil?
|
|
945
|
+
|
|
946
|
+
node.public_send(source_or_target.to_sym).model.name
|
|
947
|
+
end
|
|
948
|
+
|
|
949
|
+
def connector_labels(xmi_id, source_or_target)
|
|
950
|
+
node = connector_node_by_id(xmi_id, source_or_target)
|
|
951
|
+
return if node.nil?
|
|
952
|
+
|
|
953
|
+
node.labels&.rt || node.labels&.lt
|
|
954
|
+
end
|
|
955
|
+
|
|
956
|
+
def connector_source_name(xmi_id)
|
|
957
|
+
connector_name_by_source_or_target(xmi_id, :source)
|
|
958
|
+
end
|
|
959
|
+
|
|
960
|
+
def cardinality_min_max_value(min, max)
|
|
961
|
+
{ min: min, max: max }
|
|
962
|
+
end
|
|
963
|
+
|
|
964
|
+
def select_all_items(items, model, type, method)
|
|
965
|
+
iterate_tree(items, model, type, method.to_sym)
|
|
966
|
+
end
|
|
967
|
+
|
|
968
|
+
def iterate_tree(result, node, type, children_method) # rubocop:disable Metrics:AbcSize,Metrics:CyclomaticComplexity,Metrics:PerceivedComplexity
|
|
969
|
+
result << node if type.nil? || node.type == type
|
|
970
|
+
return unless node.public_send(children_method)
|
|
971
|
+
|
|
972
|
+
node.public_send(children_method).each do |sub_node|
|
|
973
|
+
if sub_node.public_send(children_method)
|
|
974
|
+
iterate_tree(result, sub_node, type, children_method)
|
|
975
|
+
elsif type.nil? || sub_node.type == type
|
|
976
|
+
result << sub_node
|
|
977
|
+
end
|
|
978
|
+
end
|
|
979
|
+
end
|
|
980
|
+
|
|
981
|
+
def find_klass_packaged_element_by_path(path)
|
|
982
|
+
if path.absolute?
|
|
983
|
+
iterate_packaged_element(@xmi_root_model.model, path.segments.map(&:name))
|
|
984
|
+
else
|
|
985
|
+
iterate_relative_packaged_element(path.segments.map(&:name))
|
|
986
|
+
end
|
|
987
|
+
end
|
|
988
|
+
|
|
989
|
+
def iterate_relative_packaged_element(name_array)
|
|
990
|
+
matched_elements = xmi_index.packaged_elements_of_type("uml:Package")
|
|
991
|
+
.select { |e| e.name == name_array[0] }
|
|
992
|
+
|
|
993
|
+
result = matched_elements.map do |e|
|
|
994
|
+
iterate_packaged_element(e, name_array, type: "uml:Class")
|
|
995
|
+
end
|
|
996
|
+
|
|
997
|
+
result.compact.first
|
|
998
|
+
end
|
|
999
|
+
|
|
1000
|
+
def iterate_packaged_element(model, name_array, index: 1, type: "uml:Package")
|
|
1001
|
+
return model if index == name_array.count
|
|
1002
|
+
|
|
1003
|
+
model = model.packaged_element.find do |p|
|
|
1004
|
+
p.name == name_array[index] && p.type?(type)
|
|
1005
|
+
end
|
|
1006
|
+
|
|
1007
|
+
return nil if model.nil?
|
|
1008
|
+
|
|
1009
|
+
index += 1
|
|
1010
|
+
type = index == name_array.count - 1 ? "uml:Class" : "uml:Package"
|
|
1011
|
+
iterate_packaged_element(model, name_array, index: index, type: type)
|
|
1012
|
+
end
|
|
1013
|
+
end
|
|
1014
|
+
end
|
|
1015
|
+
end
|