ea 0.2.4 → 0.3.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.refactor/01-ea-model-domain-layer.md +64 -0
- data/TODO.refactor/02-ea-model-qea-source-adapter.md +58 -0
- data/TODO.refactor/03-ea-model-xmi-source-adapter.md +33 -0
- data/TODO.refactor/04-ea-model-annotations-tagged-values.md +34 -0
- data/TODO.refactor/05-ea-model-diagrams-coordinates.md +37 -0
- data/TODO.refactor/06-spa-projection-layer.md +45 -0
- data/TODO.refactor/07-spa-sharded-output-strategy.md +28 -0
- data/TODO.refactor/08-spa-build-time-search-index.md +24 -0
- data/TODO.refactor/09-spa-frontend-data-contract.md +25 -0
- data/TODO.refactor/10-spa-cli-command-rework.md +34 -0
- data/TODO.refactor/11-spa-perf-benchmark.md +23 -0
- data/TODO.refactor/12-lutaml-uml-static-site-deprecation.md +28 -0
- data/lib/ea/cli/app.rb +13 -7
- data/lib/ea/cli/command/spa.rb +33 -48
- data/lib/ea/cli/command/svg.rb +68 -0
- data/lib/ea/cli/command.rb +1 -0
- data/lib/ea/model/annotation.rb +29 -0
- data/lib/ea/model/association.rb +40 -0
- data/lib/ea/model/base.rb +27 -0
- data/lib/ea/model/bounds.rb +22 -0
- data/lib/ea/model/classifier.rb +41 -0
- data/lib/ea/model/data_type.rb +9 -0
- data/lib/ea/model/dependency.rb +19 -0
- data/lib/ea/model/diagram.rb +28 -0
- data/lib/ea/model/diagram_connector.rb +30 -0
- data/lib/ea/model/diagram_element.rb +23 -0
- data/lib/ea/model/document.rb +107 -0
- data/lib/ea/model/enumeration.rb +15 -0
- data/lib/ea/model/enumeration_literal.rb +16 -0
- data/lib/ea/model/generalization.rb +19 -0
- data/lib/ea/model/interface.rb +9 -0
- data/lib/ea/model/klass.rb +16 -0
- data/lib/ea/model/metadata.rb +31 -0
- data/lib/ea/model/operation.rb +33 -0
- data/lib/ea/model/package.rb +36 -0
- data/lib/ea/model/parameter.rb +24 -0
- data/lib/ea/model/point.rb +16 -0
- data/lib/ea/model/polymorphic_maps.rb +30 -0
- data/lib/ea/model/primitive_type.rb +9 -0
- data/lib/ea/model/property.rb +49 -0
- data/lib/ea/model/realization.rb +19 -0
- data/lib/ea/model/relationship.rb +21 -0
- data/lib/ea/model/stereotype.rb +24 -0
- data/lib/ea/model/tagged_value.rb +20 -0
- data/lib/ea/model/waypoint.rb +19 -0
- data/lib/ea/model.rb +48 -0
- data/lib/ea/sources/qea/adapter.rb +63 -0
- data/lib/ea/sources/qea/annotation_builder.rb +36 -0
- data/lib/ea/sources/qea/classifier_builder.rb +119 -0
- data/lib/ea/sources/qea/connector_relationship_map.rb +42 -0
- data/lib/ea/sources/qea/diagram_builder.rb +130 -0
- data/lib/ea/sources/qea/diagram_style_parser.rb +43 -0
- data/lib/ea/sources/qea/id_normalizer.rb +24 -0
- data/lib/ea/sources/qea/metadata_builder.rb +42 -0
- data/lib/ea/sources/qea/object_classifier_map.rb +36 -0
- data/lib/ea/sources/qea/operation_builder.rb +79 -0
- data/lib/ea/sources/qea/package_builder.rb +44 -0
- data/lib/ea/sources/qea/property_builder.rb +86 -0
- data/lib/ea/sources/qea/relationship_builder.rb +113 -0
- data/lib/ea/sources/qea/stereotype_builder.rb +44 -0
- data/lib/ea/sources/qea/tagged_value_builder.rb +39 -0
- data/lib/ea/sources/qea.rb +31 -0
- data/lib/ea/sources/xmi/adapter.rb +62 -0
- data/lib/ea/sources/xmi/annotation_builder.rb +49 -0
- data/lib/ea/sources/xmi/classifier_builder.rb +89 -0
- data/lib/ea/sources/xmi/diagram_builder.rb +82 -0
- data/lib/ea/sources/xmi/id_normalizer.rb +27 -0
- data/lib/ea/sources/xmi/metadata_builder.rb +32 -0
- data/lib/ea/sources/xmi/operation_builder.rb +59 -0
- data/lib/ea/sources/xmi/package_builder.rb +73 -0
- data/lib/ea/sources/xmi/property_builder.rb +77 -0
- data/lib/ea/sources/xmi/relationship_builder.rb +142 -0
- data/lib/ea/sources/xmi/relationship_type_map.rb +34 -0
- data/lib/ea/sources/xmi/type_classifier_map.rb +33 -0
- data/lib/ea/sources/xmi.rb +29 -0
- data/lib/ea/sources.rb +15 -0
- data/lib/ea/spa/configuration.rb +96 -0
- data/lib/ea/spa/generator.rb +45 -0
- data/lib/ea/spa/lazy_ref.rb +18 -0
- data/lib/ea/spa/output/sharded_multi_file_strategy.rb +71 -0
- data/lib/ea/spa/output/single_file_strategy.rb +56 -0
- data/lib/ea/spa/output/strategy.rb +36 -0
- data/lib/ea/spa/output.rb +11 -0
- data/lib/ea/spa/package_tree.rb +15 -0
- data/lib/ea/spa/package_tree_node.rb +26 -0
- data/lib/ea/spa/projector.rb +194 -0
- data/lib/ea/spa/search_entry.rb +28 -0
- data/lib/ea/spa/search_index.rb +18 -0
- data/lib/ea/spa/shard.rb +19 -0
- data/lib/ea/spa/skeleton.rb +22 -0
- data/lib/ea/spa/skeleton_entry.rb +27 -0
- data/lib/ea/spa.rb +31 -0
- data/lib/ea/svg/bounds_calculator.rb +74 -0
- data/lib/ea/svg/connector_path.rb +46 -0
- data/lib/ea/svg/element_box.rb +71 -0
- data/lib/ea/svg/renderer.rb +70 -0
- data/lib/ea/svg/style_resolver.rb +72 -0
- data/lib/ea/svg.rb +21 -0
- data/lib/ea/version.rb +1 -1
- data/lib/ea.rb +4 -0
- metadata +105 -4
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Sources
|
|
5
|
+
module Qea
|
|
6
|
+
# Parses EA's packed `objectstyle` / `style` / `styleex`
|
|
7
|
+
# strings into a Hash with typed keys. EA packs visual style
|
|
8
|
+
# as `DUID=...;BCol=...;FCol=...;` etc. — semicolon-separated
|
|
9
|
+
# key=value pairs with non-standard escaping.
|
|
10
|
+
module DiagramStyleParser
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def parse(style_string)
|
|
14
|
+
return {} if style_string.nil? || style_string.empty?
|
|
15
|
+
|
|
16
|
+
style_string.to_s
|
|
17
|
+
.split(";")
|
|
18
|
+
.filter_map { |pair| parse_pair(pair) }
|
|
19
|
+
.to_h
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def parse_pair(pair)
|
|
23
|
+
return nil unless pair.include?("=")
|
|
24
|
+
|
|
25
|
+
key, value = pair.split("=", 2)
|
|
26
|
+
return nil if key.nil? || key.empty?
|
|
27
|
+
|
|
28
|
+
[normalize_key(key), normalize_value(value)]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def normalize_key(key)
|
|
32
|
+
key.strip.downcase.to_sym
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def normalize_value(value)
|
|
36
|
+
return "" if value.nil?
|
|
37
|
+
|
|
38
|
+
value.strip
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Sources
|
|
5
|
+
module Qea
|
|
6
|
+
# Normalizes EA's identifier formats into stable strings used
|
|
7
|
+
# as Ea::Model element ids. EA represents identity as
|
|
8
|
+
# `{GUID}` strings. We strip the braces to give the bare GUID.
|
|
9
|
+
module IdNormalizer
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def from_guid(ea_guid)
|
|
13
|
+
return nil if ea_guid.nil? || ea_guid.empty?
|
|
14
|
+
|
|
15
|
+
ea_guid.to_s.gsub(/[{}]/, "")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def synthetic(prefix, *parts)
|
|
19
|
+
"#{prefix}:#{parts.join(":")}"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Sources
|
|
5
|
+
module Qea
|
|
6
|
+
# Builds an Ea::Model::Metadata from the QEA database. Pulls
|
|
7
|
+
# project-level info (model name, version, tool, source path)
|
|
8
|
+
# from the root package and the QEA file path.
|
|
9
|
+
class MetadataBuilder
|
|
10
|
+
attr_reader :database, :qea_path
|
|
11
|
+
|
|
12
|
+
def initialize(database, qea_path)
|
|
13
|
+
@database = database
|
|
14
|
+
@qea_path = qea_path
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def build
|
|
18
|
+
root_pkg = root_package
|
|
19
|
+
Ea::Model::Metadata.new(
|
|
20
|
+
id: "metadata",
|
|
21
|
+
title: root_pkg&.name,
|
|
22
|
+
version: root_pkg&.version,
|
|
23
|
+
created_date: root_pkg&.createddate,
|
|
24
|
+
modified_date: root_pkg&.modifieddate,
|
|
25
|
+
author: root_pkg&.pkgowner,
|
|
26
|
+
source_format: "qea",
|
|
27
|
+
source_tool: "Sparx Enterprise Architect",
|
|
28
|
+
source_path: qea_path
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def root_package
|
|
35
|
+
@root_package ||= database.collections[:packages]&.find do |pkg|
|
|
36
|
+
pkg.parent_id.nil? || pkg.parent_id.zero?
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Sources
|
|
5
|
+
module Qea
|
|
6
|
+
# Maps EA's `t_object.Object_Type` discriminator to the
|
|
7
|
+
# concrete Ea::Model::Classifier subclass that should be
|
|
8
|
+
# produced. New classifier kinds = one entry here, nothing
|
|
9
|
+
# else changes (OCP).
|
|
10
|
+
module ObjectClassifierMap
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
# EA Object_Type → Model class. Unknown types default to
|
|
14
|
+
# Klass — the most generic classifier.
|
|
15
|
+
LOOKUP = {
|
|
16
|
+
"Class" => "Ea::Model::Klass",
|
|
17
|
+
"DataType" => "Ea::Model::DataType",
|
|
18
|
+
"Enumeration" => "Ea::Model::Enumeration",
|
|
19
|
+
"Interface" => "Ea::Model::Interface",
|
|
20
|
+
"PrimitiveType" => "Ea::Model::PrimitiveType",
|
|
21
|
+
"Primitive" => "Ea::Model::PrimitiveType"
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
def class_for(object_type)
|
|
25
|
+
name = LOOKUP[object_type] || "Ea::Model::Klass"
|
|
26
|
+
Object.const_get(name)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def model_kind_for(object_type)
|
|
30
|
+
cls = class_for(object_type)
|
|
31
|
+
cls.new.model_kind
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Sources
|
|
5
|
+
module Qea
|
|
6
|
+
# Translates EA t_operation + t_operationparams rows into
|
|
7
|
+
# Ea::Model::Operation instances with owned Parameters.
|
|
8
|
+
class OperationBuilder
|
|
9
|
+
attr_reader :database
|
|
10
|
+
|
|
11
|
+
def initialize(database)
|
|
12
|
+
@database = database
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def build_all_for(owner_object)
|
|
16
|
+
ops = database.operations_for_object(owner_object.ea_object_id) || []
|
|
17
|
+
ops.map { |row| build_one(row, owner_object) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def build_one(row, owner_object)
|
|
21
|
+
Ea::Model::Operation.new(
|
|
22
|
+
id: IdNormalizer.from_guid(row.ea_guid),
|
|
23
|
+
name: row.name,
|
|
24
|
+
owner_id: IdNormalizer.from_guid(owner_object.ea_guid),
|
|
25
|
+
qualified_name: "#{owner_object.name}::#{row.name}",
|
|
26
|
+
return_type_name: row.type,
|
|
27
|
+
visibility: visibility_from_scope(row.scope),
|
|
28
|
+
is_static: boolean(row.isstatic),
|
|
29
|
+
is_abstract: boolean(row.abstract),
|
|
30
|
+
parameters: build_parameters(row),
|
|
31
|
+
annotations: AnnotationBuilder.from_note(row.notes, row.ea_guid,
|
|
32
|
+
kind: "documentation")
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def build_parameters(operation_row)
|
|
39
|
+
params = database.operation_params_for(operation_row.operationid) || []
|
|
40
|
+
params.map.with_index do |p, idx|
|
|
41
|
+
Ea::Model::Parameter.new(
|
|
42
|
+
id: IdNormalizer.from_guid(p.ea_guid),
|
|
43
|
+
name: p.name,
|
|
44
|
+
ordinal: p.pos || idx,
|
|
45
|
+
direction: direction_from_kind(p.kind),
|
|
46
|
+
type_name: p.type,
|
|
47
|
+
default_value: p.default
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def direction_from_kind(kind)
|
|
53
|
+
case kind.to_s
|
|
54
|
+
when "1", "out" then "out"
|
|
55
|
+
when "2", "inout" then "inout"
|
|
56
|
+
when "3", "return" then "return"
|
|
57
|
+
else "in"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def visibility_from_scope(scope)
|
|
62
|
+
case scope.to_s
|
|
63
|
+
when "Public" then "public"
|
|
64
|
+
when "Protected" then "protected"
|
|
65
|
+
when "Private" then "private"
|
|
66
|
+
else "public"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def boolean(value)
|
|
71
|
+
case value.to_s
|
|
72
|
+
when "1", "true", "TRUE" then true
|
|
73
|
+
else false
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Sources
|
|
5
|
+
module Qea
|
|
6
|
+
# Translates EA t_package rows into Ea::Model::Package
|
|
7
|
+
# instances. References to parent and child packages are by
|
|
8
|
+
# model id (normalized GUID), not by EA's integer Package_ID.
|
|
9
|
+
class PackageBuilder
|
|
10
|
+
attr_reader :database
|
|
11
|
+
|
|
12
|
+
def initialize(database)
|
|
13
|
+
@database = database
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def build_all
|
|
17
|
+
packages = database.collections[:packages] || []
|
|
18
|
+
packages.map { |pkg| build_one(pkg) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def build_one(pkg)
|
|
22
|
+
Ea::Model::Package.new(
|
|
23
|
+
id: IdNormalizer.from_guid(pkg.ea_guid),
|
|
24
|
+
name: pkg.name,
|
|
25
|
+
parent_id: parent_id_for(pkg),
|
|
26
|
+
qualified_name: pkg.xmlpath,
|
|
27
|
+
annotations: AnnotationBuilder.from_note(pkg.notes, pkg.ea_guid)
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def parent_id_for(pkg)
|
|
34
|
+
return nil if pkg.parent_id.nil? || pkg.parent_id.zero?
|
|
35
|
+
|
|
36
|
+
parent = database.find_package(pkg.parent_id)
|
|
37
|
+
return nil unless parent
|
|
38
|
+
|
|
39
|
+
IdNormalizer.from_guid(parent.ea_guid)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Sources
|
|
5
|
+
module Qea
|
|
6
|
+
# Translates EA t_attribute rows into Ea::Model::Property
|
|
7
|
+
# instances. Multiplicity is parsed from EA's `LowerBound` /
|
|
8
|
+
# `UpperBound` columns (with `*` represented as -1 in the
|
|
9
|
+
# model).
|
|
10
|
+
class PropertyBuilder
|
|
11
|
+
attr_reader :database
|
|
12
|
+
|
|
13
|
+
def initialize(database)
|
|
14
|
+
@database = database
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def build_all_for(owner_object)
|
|
18
|
+
attrs = database.attributes_for_object(owner_object.ea_object_id) || []
|
|
19
|
+
attrs.map { |row| build_one(row, owner_object) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def build_one(row, owner_object)
|
|
23
|
+
Ea::Model::Property.new(
|
|
24
|
+
id: IdNormalizer.from_guid(row.ea_guid),
|
|
25
|
+
name: row.name,
|
|
26
|
+
owner_id: IdNormalizer.from_guid(owner_object.ea_guid),
|
|
27
|
+
type_name: row.type,
|
|
28
|
+
qualified_name: "#{owner_object.name}::#{row.name}",
|
|
29
|
+
multiplicity_lower: parse_lower(row),
|
|
30
|
+
multiplicity_upper: parse_upper(row),
|
|
31
|
+
default_value: row.default,
|
|
32
|
+
is_ordered: boolean(row.isordered),
|
|
33
|
+
is_unique: !boolean(row.allowduplicates),
|
|
34
|
+
visibility: visibility_from_scope(row.scope),
|
|
35
|
+
aggregation: "none",
|
|
36
|
+
stereotype_refs: stereotype_refs(row),
|
|
37
|
+
tagged_values: TaggedValueBuilder.new(database).for_attribute(row),
|
|
38
|
+
annotations: AnnotationBuilder.from_note(row.notes, row.ea_guid,
|
|
39
|
+
kind: "documentation")
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def parse_lower(row)
|
|
46
|
+
Integer(row.lowerbound || 1)
|
|
47
|
+
rescue StandardError
|
|
48
|
+
1
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def parse_upper(row)
|
|
52
|
+
return -1 if row.upperbound.to_s == "*"
|
|
53
|
+
|
|
54
|
+
begin
|
|
55
|
+
Integer(row.upperbound || 1)
|
|
56
|
+
rescue StandardError
|
|
57
|
+
1
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def boolean(value)
|
|
62
|
+
case value.to_s
|
|
63
|
+
when "1", "true", "TRUE" then true
|
|
64
|
+
else false
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def visibility_from_scope(scope)
|
|
69
|
+
case scope.to_s
|
|
70
|
+
when "Public" then "public"
|
|
71
|
+
when "Protected" then "protected"
|
|
72
|
+
when "Private" then "private"
|
|
73
|
+
when "Package" then "package"
|
|
74
|
+
else "public"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def stereotype_refs(row)
|
|
79
|
+
refs = []
|
|
80
|
+
refs << row.stereotype if row.stereotype && !row.stereotype.empty?
|
|
81
|
+
refs
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Sources
|
|
5
|
+
module Qea
|
|
6
|
+
# Translates EA t_connector rows into the appropriate
|
|
7
|
+
# Ea::Model::Relationship subclass. Dispatches on
|
|
8
|
+
# `Connector_Type` via ConnectorRelationshipMap.
|
|
9
|
+
class RelationshipBuilder
|
|
10
|
+
attr_reader :database
|
|
11
|
+
|
|
12
|
+
def initialize(database)
|
|
13
|
+
@database = database
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def build_all
|
|
17
|
+
connectors = database.collections[:connectors] || []
|
|
18
|
+
connectors.filter_map { |row| build_one(row) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def build_one(row)
|
|
22
|
+
klass = ConnectorRelationshipMap.class_for(row.connector_type)
|
|
23
|
+
args = common_args(row)
|
|
24
|
+
args.merge!(association_args(row)) if klass == Ea::Model::Association
|
|
25
|
+
klass.new(**args)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def common_args(row)
|
|
31
|
+
{
|
|
32
|
+
id: IdNormalizer.from_guid(row.ea_guid),
|
|
33
|
+
name: row.name,
|
|
34
|
+
qualified_name: row.name,
|
|
35
|
+
annotations: AnnotationBuilder.from_note(row.notes, row.ea_guid,
|
|
36
|
+
kind: "documentation")
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def association_args(row)
|
|
41
|
+
{
|
|
42
|
+
source_id: id_for_object_id(row.start_object_id),
|
|
43
|
+
target_id: id_for_object_id(row.end_object_id),
|
|
44
|
+
source_role_name: row.sourcerole,
|
|
45
|
+
target_role_name: row.destrole,
|
|
46
|
+
source_multiplicity_lower: parse_lower(row.sourcecard),
|
|
47
|
+
source_multiplicity_upper: parse_upper(row.sourcecard),
|
|
48
|
+
target_multiplicity_lower: parse_lower(row.destcard),
|
|
49
|
+
target_multiplicity_upper: parse_upper(row.destcard),
|
|
50
|
+
source_aggregation: ConnectorRelationshipMap
|
|
51
|
+
.source_aggregation_for(row.connector_type),
|
|
52
|
+
target_aggregation: aggregation_from(row.destaccess),
|
|
53
|
+
source_navigable: navigable?(row.sourceaccess, row.direction,
|
|
54
|
+
:source),
|
|
55
|
+
target_navigable: navigable?(row.destaccess, row.direction, :target)
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def id_for_object_id(ea_object_id)
|
|
60
|
+
obj = database.find_object(ea_object_id)
|
|
61
|
+
return nil unless obj
|
|
62
|
+
|
|
63
|
+
IdNormalizer.from_guid(obj.ea_guid)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def parse_lower(cardinality)
|
|
67
|
+
return 1 if cardinality.nil? || cardinality.empty?
|
|
68
|
+
|
|
69
|
+
# EA format: "1", "0..*", "1..1", etc. Take the first number.
|
|
70
|
+
begin
|
|
71
|
+
Integer(cardinality.split("..").first)
|
|
72
|
+
rescue StandardError
|
|
73
|
+
1
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def parse_upper(cardinality)
|
|
78
|
+
return 1 if cardinality.nil? || cardinality.empty?
|
|
79
|
+
|
|
80
|
+
upper = cardinality.split("..").last
|
|
81
|
+
return -1 if upper == "*"
|
|
82
|
+
|
|
83
|
+
begin
|
|
84
|
+
Integer(upper)
|
|
85
|
+
rescue StandardError
|
|
86
|
+
1
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def aggregation_from(access_value)
|
|
91
|
+
case access_value.to_s
|
|
92
|
+
when "0" then "none"
|
|
93
|
+
when "1" then "shared"
|
|
94
|
+
when "2" then "composite"
|
|
95
|
+
else "none"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def navigable?(_access, direction, end_label)
|
|
100
|
+
# EA's navigability is messy. Treat unspecified direction
|
|
101
|
+
# as bidirectional, "Source -> Destination" as target-only
|
|
102
|
+
# navigable, etc. Conservative default: both ends navigable.
|
|
103
|
+
case direction.to_s
|
|
104
|
+
when "<->", "" then true
|
|
105
|
+
when "->" then end_label == :target
|
|
106
|
+
when "<-" then end_label == :source
|
|
107
|
+
else true
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Sources
|
|
5
|
+
module Qea
|
|
6
|
+
# Builds Ea::Model::Stereotype instances from EA's
|
|
7
|
+
# t_stereotypes table and from the `stereotype` column on
|
|
8
|
+
# individual elements. EA's stereotype storage is messy — a
|
|
9
|
+
# stereotype can be declared globally and applied per-element
|
|
10
|
+
# by name. We treat each distinct applied stereotype name as
|
|
11
|
+
# an instance and reference it from the applying element.
|
|
12
|
+
class StereotypeBuilder
|
|
13
|
+
attr_reader :database
|
|
14
|
+
|
|
15
|
+
def initialize(database)
|
|
16
|
+
@database = database
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def build_all
|
|
20
|
+
stereotypes = database.collections[:stereotypes] || []
|
|
21
|
+
stereotypes.map { |st| build_one(st) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def build_one(stereotype_row)
|
|
25
|
+
name = stereotype_row.stereotype
|
|
26
|
+
Ea::Model::Stereotype.new(
|
|
27
|
+
id: IdNormalizer.from_guid(stereotype_row.ea_guid) ||
|
|
28
|
+
IdNormalizer.synthetic("stereotype", name),
|
|
29
|
+
name: name,
|
|
30
|
+
qualified_name: name,
|
|
31
|
+
profile: stereotype_row.appliesto
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Apply references: which stereotypes apply to a given EA object.
|
|
36
|
+
def refs_for_object(object)
|
|
37
|
+
refs = []
|
|
38
|
+
refs << object.stereotype if object.stereotype && !object.stereotype.empty?
|
|
39
|
+
refs.uniq
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Sources
|
|
5
|
+
module Qea
|
|
6
|
+
# Builds Ea::Model::TaggedValue instances from EA's
|
|
7
|
+
# t_objectproperties (and t_attributetags for attribute-level
|
|
8
|
+
# tagged values). Each row is one key/value pair; we keep the
|
|
9
|
+
# optional stereotype scope if EA recorded one.
|
|
10
|
+
class TaggedValueBuilder
|
|
11
|
+
attr_reader :database
|
|
12
|
+
|
|
13
|
+
def initialize(database)
|
|
14
|
+
@database = database
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def for_object(ea_guid)
|
|
18
|
+
rows = database.tagged_values_for_element(ea_guid) || []
|
|
19
|
+
rows.map { |row| build_from_row(row, ea_guid) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def for_attribute(attribute_row)
|
|
23
|
+
return [] unless attribute_row.respond_to?(:ea_guid)
|
|
24
|
+
|
|
25
|
+
rows = database.tagged_values_for_element(attribute_row.ea_guid) || []
|
|
26
|
+
rows.map { |row| build_from_row(row, attribute_row.ea_guid) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def build_from_row(row, owner_guid)
|
|
30
|
+
Ea::Model::TaggedValue.new(
|
|
31
|
+
id: IdNormalizer.synthetic("tv", owner_guid, row.property&.gsub(/\W+/, "_")),
|
|
32
|
+
key: row.property,
|
|
33
|
+
value: row.value
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Sources
|
|
5
|
+
# QEA source adapter. Produces an Ea::Model::Document from a
|
|
6
|
+
# parsed Ea::Qea::Database by walking the SQLite-derived tables
|
|
7
|
+
# once and translating each row into a model instance.
|
|
8
|
+
#
|
|
9
|
+
# Structure: Adapter coordinates per-domain Builders. Each
|
|
10
|
+
# Builder owns one source table → model type translation, so
|
|
11
|
+
# new model types or new source columns only touch one Builder
|
|
12
|
+
# (OCP/MECE).
|
|
13
|
+
module Qea
|
|
14
|
+
autoload :IdNormalizer, "ea/sources/qea/id_normalizer"
|
|
15
|
+
autoload :ObjectClassifierMap, "ea/sources/qea/object_classifier_map"
|
|
16
|
+
autoload :ConnectorRelationshipMap, "ea/sources/qea/connector_relationship_map"
|
|
17
|
+
autoload :MetadataBuilder, "ea/sources/qea/metadata_builder"
|
|
18
|
+
autoload :PackageBuilder, "ea/sources/qea/package_builder"
|
|
19
|
+
autoload :ClassifierBuilder, "ea/sources/qea/classifier_builder"
|
|
20
|
+
autoload :PropertyBuilder, "ea/sources/qea/property_builder"
|
|
21
|
+
autoload :OperationBuilder, "ea/sources/qea/operation_builder"
|
|
22
|
+
autoload :RelationshipBuilder, "ea/sources/qea/relationship_builder"
|
|
23
|
+
autoload :StereotypeBuilder, "ea/sources/qea/stereotype_builder"
|
|
24
|
+
autoload :TaggedValueBuilder, "ea/sources/qea/tagged_value_builder"
|
|
25
|
+
autoload :AnnotationBuilder, "ea/sources/qea/annotation_builder"
|
|
26
|
+
autoload :DiagramBuilder, "ea/sources/qea/diagram_builder"
|
|
27
|
+
autoload :DiagramStyleParser, "ea/sources/qea/diagram_style_parser"
|
|
28
|
+
autoload :Adapter, "ea/sources/qea/adapter"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Sources
|
|
5
|
+
module Xmi
|
|
6
|
+
# Driving port: produces an Ea::Model::Document from a parsed
|
|
7
|
+
# Xmi::Sparx::Root. Walks the UML model tree once and
|
|
8
|
+
# delegates to per-domain builders. The result is structurally
|
|
9
|
+
# identical to what the QEA adapter produces for the same
|
|
10
|
+
# conceptual source — that's the "single way" promise.
|
|
11
|
+
class Adapter
|
|
12
|
+
attr_reader :root, :xmi_path
|
|
13
|
+
|
|
14
|
+
def initialize(root, xmi_path = nil)
|
|
15
|
+
@root = root
|
|
16
|
+
@xmi_path = xmi_path
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Convenience: build a document directly from an .xmi file
|
|
20
|
+
# path. Parses via Xmi::Sparx::Root.parse_xml.
|
|
21
|
+
def self.from_path(xmi_path)
|
|
22
|
+
require "xmi"
|
|
23
|
+
root = ::Xmi::Sparx::Root.parse_xml(File.read(xmi_path))
|
|
24
|
+
new(root, xmi_path).to_document
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def to_document
|
|
28
|
+
Ea::Model::Document.new(
|
|
29
|
+
metadata: metadata,
|
|
30
|
+
packages: packages,
|
|
31
|
+
classifiers: classifiers,
|
|
32
|
+
relationships: relationships,
|
|
33
|
+
stereotypes: [],
|
|
34
|
+
diagrams: diagrams
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def metadata
|
|
41
|
+
@metadata ||= MetadataBuilder.new(root, xmi_path).build
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def packages
|
|
45
|
+
@packages ||= PackageBuilder.new(root).build_all
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def classifiers
|
|
49
|
+
@classifiers ||= ClassifierBuilder.new(root).build_all
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def relationships
|
|
53
|
+
@relationships ||= RelationshipBuilder.new(root).build_all
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def diagrams
|
|
57
|
+
@diagrams ||= DiagramBuilder.new(root).build_all
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|