ea 0.1.0 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CLAUDE.md +125 -0
- data/Rakefile +12 -4
- data/TODO.next/00-publish-blocking-bugs.md +74 -0
- data/TODO.next/01-standalone-ea-gem-identity.md +76 -0
- data/TODO.next/02-optional-lutaml-uml-dependency.md +47 -0
- data/TODO.next/03-slim-lutaml-uml.md +79 -0
- data/TODO.next/04-loader-registry-for-uml-repository.md +49 -0
- data/TODO.next/05-extract-shared-transformer-methods.md +14 -0
- data/TODO.next/06-deduplicate-stereotype-loading.md +17 -0
- data/TODO.next/07-transformer-registry-in-factory.md +20 -0
- data/TODO.next/08-connector-type-registry.md +27 -0
- data/TODO.next/09-element-renderer-registry.md +29 -0
- data/TODO.next/10-connector-renderer-lsp.md +18 -0
- data/TODO.next/11-consolidate-style-knowledge.md +33 -0
- data/TODO.next/12-data-driven-from-db-row.md +24 -0
- data/TODO.next/13-extract-duplicated-methods.md +17 -0
- data/TODO.next/14-remove-dead-code.md +10 -0
- data/TODO.next/15-narrow-exception-handling.md +39 -0
- data/TODO.next/16-repository-indexes.md +28 -0
- data/TODO.next/17-fix-spec-quality-and-coverage.md +32 -0
- data/TODO.next/18-xmi-tool-specific-parser-architecture.md +172 -0
- data/TODO.next/19-fix-ea-gemspec-dependency-declarations.md +56 -0
- data/TODO.next/20-ci-requires-unreleased-lutaml-uml.md +63 -0
- data/TODO.next/21-qeatoxmi-via-xmi-gem.md +340 -0
- data/TODO.next/22-strip-respond-to-from-qeatoxmi-specs.md +32 -0
- data/TODO.next/23-cleanup-idallocator.md +41 -0
- data/TODO.next/24-tighten-parity-specs.md +42 -0
- data/TODO.next/25-sparx-eaid-format-for-synthesized-ids.md +62 -0
- data/TODO.next/26-fix-uppervalue-lowervalue-count-gap.md +51 -0
- data/TODO.next/27-extract-cardinality-module.md +68 -0
- data/TODO.next/28-extract-xml-sanitizer.md +51 -0
- data/TODO.next/29-ocp-registry-for-classifier-builders.md +58 -0
- data/TODO.next/30-struct-return-for-association-end.md +37 -0
- data/TODO.next/31-idallocator-specs.md +27 -0
- data/TODO.next/32-phase2-gap-sentinel-specs.md +53 -0
- data/TODO.next/33-normalize-lower-cleanup.md +30 -0
- data/TODO.next/34-document-member-end-order-rt-prefix.md +29 -0
- data/TODO.next/35-walk-runstate-for-instance-slots.md +76 -0
- data/TODO.next/36-wire-interface-realization.md +50 -0
- data/TODO.next/37-visibility-returns-real-booleans.md +36 -0
- data/config/diagram_styles.yml +200 -0
- data/config/model_transformations.yml +266 -0
- data/config/qea_schema.yml +1024 -0
- data/docs/ea_to_uml_type_mapping.md +89 -0
- data/docs/xmi_qea_conversion_capabilities.md +99 -0
- data/examples/lur/20251010_current_plateau_v5.1.lur +0 -0
- data/examples/lur/basic.lur +0 -0
- data/examples/lur/test-output.lur +0 -0
- data/examples/lur/test.lur +0 -0
- data/examples/lur_basic_usage.rb +221 -0
- data/examples/lur_cli_workflow.rb +263 -0
- data/examples/lur_statistics.rb +326 -0
- data/examples/qea/20251010_current_plateau_v5.1.qea +0 -0
- data/examples/qea/ArcGISWorkspace_template.qea +0 -0
- data/examples/qea/README_qea_parser.adoc +230 -0
- data/examples/qea/UmlModel_template.qea +0 -0
- data/examples/qea/basic.qea +0 -0
- data/examples/qea/simple.qea +0 -0
- data/examples/qea/simple_example.qea +0 -0
- data/examples/qea/test.qea +0 -0
- data/examples/qea_standalone_query.rb +73 -0
- data/examples/qea_to_repository.rb +51 -0
- data/examples/smoke_test_real_qea.rb +81 -0
- data/exe/ea +7 -0
- data/lib/ea/cli/app.rb +72 -0
- data/lib/ea/cli/command/base.rb +80 -0
- data/lib/ea/cli/command/convert.rb +62 -0
- data/lib/ea/cli/command/diagrams.rb +81 -0
- data/lib/ea/cli/command/list.rb +61 -0
- data/lib/ea/cli/command/parse.rb +29 -0
- data/lib/ea/cli/command/stats.rb +20 -0
- data/lib/ea/cli/command/validate.rb +41 -0
- data/lib/ea/cli/command.rb +15 -0
- data/lib/ea/cli/error.rb +34 -0
- data/lib/ea/cli/output/formatter.rb +34 -0
- data/lib/ea/cli/output/json_formatter.rb +20 -0
- data/lib/ea/cli/output/table_formatter.rb +42 -0
- data/lib/ea/cli/output/yaml_formatter.rb +20 -0
- data/lib/ea/cli/output.rb +56 -0
- data/lib/ea/cli.rb +17 -0
- data/lib/ea/diagram/configuration.rb +379 -0
- data/lib/ea/diagram/element_renderers/base_renderer.rb +77 -0
- data/lib/ea/diagram/element_renderers/class_renderer.rb +323 -0
- data/lib/ea/diagram/element_renderers/connector_renderer.rb +41 -0
- data/lib/ea/diagram/element_renderers/package_renderer.rb +61 -0
- data/lib/ea/diagram/element_renderers.rb +43 -0
- data/lib/ea/diagram/extractor.rb +560 -0
- data/lib/ea/diagram/layout_engine.rb +170 -0
- data/lib/ea/diagram/path_builder.rb +202 -0
- data/lib/ea/diagram/style_parser.rb +42 -0
- data/lib/ea/diagram/style_resolver.rb +276 -0
- data/lib/ea/diagram/svg_renderer.rb +274 -0
- data/lib/ea/diagram/util.rb +73 -0
- data/lib/ea/diagram.rb +47 -0
- data/lib/ea/qea/benchmark.rb +210 -0
- data/lib/ea/qea/database.rb +308 -0
- data/lib/ea/qea/factory/association_builder.rb +203 -0
- data/lib/ea/qea/factory/association_transformer.rb +91 -0
- data/lib/ea/qea/factory/attribute_tag_transformer.rb +57 -0
- data/lib/ea/qea/factory/attribute_transformer.rb +93 -0
- data/lib/ea/qea/factory/base_transformer.rb +177 -0
- data/lib/ea/qea/factory/class_transformer.rb +116 -0
- data/lib/ea/qea/factory/constraint_transformer.rb +75 -0
- data/lib/ea/qea/factory/data_type_transformer.rb +77 -0
- data/lib/ea/qea/factory/diagram_transformer.rb +157 -0
- data/lib/ea/qea/factory/document_builder.rb +283 -0
- data/lib/ea/qea/factory/ea_to_uml_factory.rb +229 -0
- data/lib/ea/qea/factory/enum_transformer.rb +74 -0
- data/lib/ea/qea/factory/generalization_builder.rb +227 -0
- data/lib/ea/qea/factory/generalization_transformer.rb +98 -0
- data/lib/ea/qea/factory/instance_transformer.rb +68 -0
- data/lib/ea/qea/factory/object_property_transformer.rb +58 -0
- data/lib/ea/qea/factory/operation_transformer.rb +66 -0
- data/lib/ea/qea/factory/package_transformer.rb +145 -0
- data/lib/ea/qea/factory/reference_resolver.rb +99 -0
- data/lib/ea/qea/factory/stereotype_loader.rb +39 -0
- data/lib/ea/qea/factory/tagged_value_transformer.rb +38 -0
- data/lib/ea/qea/factory/transformer_registry.rb +80 -0
- data/lib/ea/qea/factory.rb +37 -0
- data/lib/ea/qea/file_detector.rb +178 -0
- data/lib/ea/qea/infrastructure/database_connection.rb +100 -0
- data/lib/ea/qea/infrastructure/schema_reader.rb +136 -0
- data/lib/ea/qea/infrastructure/table_reader.rb +224 -0
- data/lib/ea/qea/infrastructure.rb +12 -0
- data/lib/ea/qea/models/base_model.rb +59 -0
- data/lib/ea/qea/models/ea_attribute.rb +109 -0
- data/lib/ea/qea/models/ea_attribute_tag.rb +100 -0
- data/lib/ea/qea/models/ea_complexity_type.rb +79 -0
- data/lib/ea/qea/models/ea_connector.rb +160 -0
- data/lib/ea/qea/models/ea_connector_type.rb +60 -0
- data/lib/ea/qea/models/ea_constraint_type.rb +63 -0
- data/lib/ea/qea/models/ea_datatype.rb +104 -0
- data/lib/ea/qea/models/ea_diagram.rb +115 -0
- data/lib/ea/qea/models/ea_diagram_link.rb +78 -0
- data/lib/ea/qea/models/ea_diagram_object.rb +73 -0
- data/lib/ea/qea/models/ea_diagram_type.rb +56 -0
- data/lib/ea/qea/models/ea_document.rb +63 -0
- data/lib/ea/qea/models/ea_object.rb +223 -0
- data/lib/ea/qea/models/ea_object_constraint.rb +53 -0
- data/lib/ea/qea/models/ea_object_property.rb +87 -0
- data/lib/ea/qea/models/ea_object_type.rb +73 -0
- data/lib/ea/qea/models/ea_operation.rb +127 -0
- data/lib/ea/qea/models/ea_operation_param.rb +76 -0
- data/lib/ea/qea/models/ea_package.rb +78 -0
- data/lib/ea/qea/models/ea_script.rb +62 -0
- data/lib/ea/qea/models/ea_status_type.rb +66 -0
- data/lib/ea/qea/models/ea_stereotype.rb +57 -0
- data/lib/ea/qea/models/ea_tagged_value.rb +99 -0
- data/lib/ea/qea/models/ea_xref.rb +165 -0
- data/lib/ea/qea/models.rb +35 -0
- data/lib/ea/qea/repositories/base_repository.rb +225 -0
- data/lib/ea/qea/repositories/object_repository.rb +219 -0
- data/lib/ea/qea/repositories.rb +10 -0
- data/lib/ea/qea/services/configuration.rb +211 -0
- data/lib/ea/qea/services/database_loader.rb +191 -0
- data/lib/ea/qea/services.rb +10 -0
- data/lib/ea/qea/validation/association_validator.rb +73 -0
- data/lib/ea/qea/validation/attribute_validator.rb +91 -0
- data/lib/ea/qea/validation/base_validator.rb +331 -0
- data/lib/ea/qea/validation/class_validator.rb +121 -0
- data/lib/ea/qea/validation/database/circular_reference_validator.rb +109 -0
- data/lib/ea/qea/validation/database/orphan_validator.rb +153 -0
- data/lib/ea/qea/validation/database/referential_integrity_validator.rb +128 -0
- data/lib/ea/qea/validation/database.rb +16 -0
- data/lib/ea/qea/validation/diagram_validator.rb +112 -0
- data/lib/ea/qea/validation/formatters/json_formatter.rb +137 -0
- data/lib/ea/qea/validation/formatters/text_formatter.rb +235 -0
- data/lib/ea/qea/validation/formatters.rb +12 -0
- data/lib/ea/qea/validation/operation_validator.rb +71 -0
- data/lib/ea/qea/validation/package_validator.rb +111 -0
- data/lib/ea/qea/validation/validation_engine.rb +387 -0
- data/lib/ea/qea/validation/validation_message.rb +144 -0
- data/lib/ea/qea/validation/validation_result.rb +210 -0
- data/lib/ea/qea/validation/validator_registry.rb +134 -0
- data/lib/ea/qea/validation.rb +28 -0
- data/lib/ea/qea/verification/comparison_result.rb +264 -0
- data/lib/ea/qea/verification/document_normalizer.rb +169 -0
- data/lib/ea/qea/verification/document_verifier.rb +322 -0
- data/lib/ea/qea/verification/element_comparator.rb +277 -0
- data/lib/ea/qea/verification/structure_matcher.rb +287 -0
- data/lib/ea/qea/verification.rb +14 -0
- data/lib/ea/qea.rb +185 -0
- data/lib/ea/transformations/configuration.rb +333 -0
- data/lib/ea/transformations/format_registry.rb +366 -0
- data/lib/ea/transformations/parsers/base_parser.rb +482 -0
- data/lib/ea/transformations/parsers/qea_parser.rb +401 -0
- data/lib/ea/transformations/parsers/xmi_parser.rb +243 -0
- data/lib/ea/transformations/transformation_engine.rb +390 -0
- data/lib/ea/transformations.rb +85 -0
- data/lib/ea/transformers/qea_to_xmi/association_end.rb +19 -0
- data/lib/ea/transformers/qea_to_xmi/cardinality.rb +96 -0
- data/lib/ea/transformers/qea_to_xmi/context.rb +106 -0
- data/lib/ea/transformers/qea_to_xmi/guid_format.rb +56 -0
- data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +92 -0
- data/lib/ea/transformers/qea_to_xmi/run_state.rb +107 -0
- data/lib/ea/transformers/qea_to_xmi/transformer.rb +607 -0
- data/lib/ea/transformers/qea_to_xmi/visibility.rb +73 -0
- data/lib/ea/transformers/qea_to_xmi.rb +29 -0
- data/lib/ea/transformers/uml_to_xmi/id_generator.rb +54 -0
- data/lib/ea/transformers/uml_to_xmi/transformer.rb +152 -0
- data/lib/ea/transformers/uml_to_xmi/writer.rb +96 -0
- data/lib/ea/transformers/uml_to_xmi.rb +16 -0
- data/lib/ea/transformers.rb +34 -0
- data/lib/ea/version.rb +1 -1
- data/lib/ea/xmi/liquid_drops/association_drop.rb +56 -0
- data/lib/ea/xmi/liquid_drops/attribute_drop.rb +72 -0
- data/lib/ea/xmi/liquid_drops/cardinality_drop.rb +35 -0
- data/lib/ea/xmi/liquid_drops/connector_drop.rb +54 -0
- data/lib/ea/xmi/liquid_drops/constraint_drop.rb +29 -0
- data/lib/ea/xmi/liquid_drops/data_type_drop.rb +63 -0
- data/lib/ea/xmi/liquid_drops/dependency_drop.rb +36 -0
- data/lib/ea/xmi/liquid_drops/diagram_drop.rb +34 -0
- data/lib/ea/xmi/liquid_drops/enum_drop.rb +49 -0
- data/lib/ea/xmi/liquid_drops/enum_owned_literal_drop.rb +25 -0
- data/lib/ea/xmi/liquid_drops/generalization_attribute_drop.rb +87 -0
- data/lib/ea/xmi/liquid_drops/generalization_drop.rb +127 -0
- data/lib/ea/xmi/liquid_drops/klass_drop.rb +191 -0
- data/lib/ea/xmi/liquid_drops/operation_drop.rb +29 -0
- data/lib/ea/xmi/liquid_drops/package_drop.rb +108 -0
- data/lib/ea/xmi/liquid_drops/root_drop.rb +34 -0
- data/lib/ea/xmi/liquid_drops/source_target_drop.rb +43 -0
- data/lib/ea/xmi/lookup_service.rb +89 -0
- data/lib/ea/xmi/parser.rb +919 -0
- data/lib/ea/xmi.rb +35 -0
- data/lib/ea.rb +10 -1
- metadata +382 -9
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Factory
|
|
6
|
+
# Resolves references between EA and UML elements
|
|
7
|
+
# Maps EA GUIDs to UML xmi_ids and maintains object relationships
|
|
8
|
+
class ReferenceResolver
|
|
9
|
+
# Initialize empty resolver
|
|
10
|
+
def initialize
|
|
11
|
+
@guid_to_element = {}
|
|
12
|
+
@object_id_to_name = {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Register EA GUID to UML element mapping
|
|
16
|
+
# @param ea_guid [String] EA GUID
|
|
17
|
+
# @param uml_element [Object] UML element with xmi_id
|
|
18
|
+
# @return [void]
|
|
19
|
+
def register(ea_guid, uml_element)
|
|
20
|
+
return if ea_guid.nil? || uml_element.nil?
|
|
21
|
+
|
|
22
|
+
@guid_to_element[normalize_guid(ea_guid)] = uml_element
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Register object ID to name mapping
|
|
26
|
+
# @param object_id [Integer] EA object ID
|
|
27
|
+
# @param name [String] Object name
|
|
28
|
+
# @return [void]
|
|
29
|
+
def register_object_name(object_id, name)
|
|
30
|
+
return if object_id.nil?
|
|
31
|
+
|
|
32
|
+
@object_id_to_name[object_id] = name
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Resolve EA GUID to UML element
|
|
36
|
+
# @param ea_guid [String] EA GUID
|
|
37
|
+
# @return [Object, nil] UML element or nil if not found
|
|
38
|
+
def resolve(ea_guid)
|
|
39
|
+
return nil if ea_guid.nil?
|
|
40
|
+
|
|
41
|
+
@guid_to_element[normalize_guid(ea_guid)]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Get object name by object ID
|
|
45
|
+
# @param object_id [Integer] EA object ID
|
|
46
|
+
# @return [String, nil] Object name or nil if not found
|
|
47
|
+
def resolve_object_name(object_id)
|
|
48
|
+
return nil if object_id.nil?
|
|
49
|
+
|
|
50
|
+
@object_id_to_name[object_id]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Get UML xmi_id by EA GUID
|
|
54
|
+
# @param ea_guid [String] EA GUID
|
|
55
|
+
# @return [String, nil] xmi_id or nil if not found
|
|
56
|
+
def resolve_xmi_id(ea_guid)
|
|
57
|
+
element = resolve(ea_guid)
|
|
58
|
+
element&.xmi_id
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Check if GUID is registered
|
|
62
|
+
# @param ea_guid [String] EA GUID
|
|
63
|
+
# @return [Boolean] True if registered
|
|
64
|
+
def registered?(ea_guid)
|
|
65
|
+
return false if ea_guid.nil?
|
|
66
|
+
|
|
67
|
+
@guid_to_element.key?(normalize_guid(ea_guid))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Clear all mappings
|
|
71
|
+
# @return [void]
|
|
72
|
+
def clear
|
|
73
|
+
@guid_to_element.clear
|
|
74
|
+
@object_id_to_name.clear
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Get statistics
|
|
78
|
+
# @return [Hash] Statistics about registered elements
|
|
79
|
+
def stats
|
|
80
|
+
{
|
|
81
|
+
total_elements: @guid_to_element.size,
|
|
82
|
+
total_objects: @object_id_to_name.size,
|
|
83
|
+
}
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
# Normalize GUID format (remove braces, upcase)
|
|
89
|
+
# @param guid [String] GUID string
|
|
90
|
+
# @return [String] Normalized GUID
|
|
91
|
+
def normalize_guid(guid)
|
|
92
|
+
return guid if guid.nil?
|
|
93
|
+
|
|
94
|
+
guid.to_s.gsub(/[{}]/, "").upcase
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Factory
|
|
6
|
+
class StereotypeLoader
|
|
7
|
+
def initialize(database)
|
|
8
|
+
@database = database
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def load_from_xref(ea_guid)
|
|
12
|
+
return nil if ea_guid.nil?
|
|
13
|
+
return nil unless @database.xrefs
|
|
14
|
+
|
|
15
|
+
xref = find_stereotype_xref(ea_guid)
|
|
16
|
+
return nil unless xref
|
|
17
|
+
|
|
18
|
+
extract_stereotype_name(xref.description)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def find_stereotype_xref(ea_guid)
|
|
24
|
+
@database.xrefs_for_client(ea_guid).find do |x|
|
|
25
|
+
x.name == "Stereotypes" && x.type == "element property"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def extract_stereotype_name(description)
|
|
30
|
+
return nil if description.nil? || description.empty?
|
|
31
|
+
|
|
32
|
+
if description =~ /@STEREO;Name=([^;]+);/
|
|
33
|
+
Regexp.last_match(1)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Factory
|
|
6
|
+
# Transforms EA TaggedValue to UML TaggedValue
|
|
7
|
+
#
|
|
8
|
+
# This transformer converts Enterprise Architect tagged value
|
|
9
|
+
# definitions (custom metadata) to standard UML TaggedValue objects.
|
|
10
|
+
#
|
|
11
|
+
# @example Transform a tagged value
|
|
12
|
+
# ea_tag = Models::EaTaggedValue.new(
|
|
13
|
+
# property_id: "{GUID}",
|
|
14
|
+
# element_id: "{ELEMENT-GUID}",
|
|
15
|
+
# base_class: "ASSOCIATION_SOURCE",
|
|
16
|
+
# tag_value: "sequenceNumber|15$ea_notes=Unique integer..."
|
|
17
|
+
# )
|
|
18
|
+
# transformer = TaggedValueTransformer.new(database)
|
|
19
|
+
# uml_tag = transformer.transform(ea_tag)
|
|
20
|
+
class TaggedValueTransformer < BaseTransformer
|
|
21
|
+
# Transform EA tagged value to UML TaggedValue
|
|
22
|
+
#
|
|
23
|
+
# @param ea_tag [Models::EaTaggedValue] EA tagged value model
|
|
24
|
+
# @return [Lutaml::Uml::TaggedValue, nil] UML tagged value or nil
|
|
25
|
+
def transform(ea_tag)
|
|
26
|
+
return nil unless ea_tag
|
|
27
|
+
return nil unless ea_tag.tag_name
|
|
28
|
+
|
|
29
|
+
Lutaml::Uml::TaggedValue.new.tap do |tag|
|
|
30
|
+
tag.name = ea_tag.tag_name
|
|
31
|
+
tag.value = ea_tag.parsed_value || ""
|
|
32
|
+
tag.notes = ea_tag.parsed_notes
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Factory
|
|
6
|
+
# Registry for EA to UML transformers
|
|
7
|
+
# Implements the Registry pattern for transformer lookup
|
|
8
|
+
class TransformerRegistry
|
|
9
|
+
class << self
|
|
10
|
+
# Get or initialize the registry
|
|
11
|
+
# @return [Hash] Registry hash
|
|
12
|
+
def registry
|
|
13
|
+
@registry ||= {}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Register a transformer for an EA type
|
|
17
|
+
# @param ea_type [Symbol, String] EA model type
|
|
18
|
+
# @param transformer_class [Class] Transformer class
|
|
19
|
+
def register(ea_type, transformer_class)
|
|
20
|
+
registry[ea_type.to_sym] = transformer_class
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Get transformer for an EA type
|
|
24
|
+
# @param ea_type [Symbol, String] EA model type
|
|
25
|
+
# @return [Class, nil] Transformer class or nil if not found
|
|
26
|
+
def transformer_for(ea_type)
|
|
27
|
+
registry[ea_type.to_sym]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Get all registered transformers
|
|
31
|
+
# @return [Hash] All registered transformers
|
|
32
|
+
def all_transformers
|
|
33
|
+
registry.dup
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Check if a transformer is registered for a type
|
|
37
|
+
# @param ea_type [Symbol, String] EA model type
|
|
38
|
+
# @return [Boolean] True if registered
|
|
39
|
+
def registered?(ea_type)
|
|
40
|
+
registry.key?(ea_type.to_sym)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Clear all registrations (mainly for testing)
|
|
44
|
+
def clear
|
|
45
|
+
@registry = {}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Reset to default registrations
|
|
49
|
+
def reset_defaults
|
|
50
|
+
clear
|
|
51
|
+
register_defaults
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Register default transformers
|
|
55
|
+
def register_defaults
|
|
56
|
+
# Object types
|
|
57
|
+
register(:class, ClassTransformer)
|
|
58
|
+
register(:interface, ClassTransformer)
|
|
59
|
+
register(:enumeration, EnumTransformer)
|
|
60
|
+
register(:data_type, DataTypeTransformer)
|
|
61
|
+
register(:instance, InstanceTransformer)
|
|
62
|
+
register(:package, PackageTransformer)
|
|
63
|
+
|
|
64
|
+
# Connector types
|
|
65
|
+
register(:association, AssociationTransformer)
|
|
66
|
+
register(:generalization, GeneralizationTransformer)
|
|
67
|
+
|
|
68
|
+
# Feature types
|
|
69
|
+
register(:attribute, AttributeTransformer)
|
|
70
|
+
register(:operation, OperationTransformer)
|
|
71
|
+
register(:diagram, DiagramTransformer)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Initialize registry with default transformers
|
|
76
|
+
register_defaults
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Qea
|
|
5
|
+
module Factory
|
|
6
|
+
autoload :BaseTransformer, "ea/qea/factory/base_transformer"
|
|
7
|
+
autoload :AssociationBuilder, "ea/qea/factory/association_builder"
|
|
8
|
+
autoload :AssociationTransformer,
|
|
9
|
+
"ea/qea/factory/association_transformer"
|
|
10
|
+
autoload :AttributeTagTransformer,
|
|
11
|
+
"ea/qea/factory/attribute_tag_transformer"
|
|
12
|
+
autoload :AttributeTransformer, "ea/qea/factory/attribute_transformer"
|
|
13
|
+
autoload :ClassTransformer, "ea/qea/factory/class_transformer"
|
|
14
|
+
autoload :ConstraintTransformer,
|
|
15
|
+
"ea/qea/factory/constraint_transformer"
|
|
16
|
+
autoload :DataTypeTransformer, "ea/qea/factory/data_type_transformer"
|
|
17
|
+
autoload :DiagramTransformer, "ea/qea/factory/diagram_transformer"
|
|
18
|
+
autoload :DocumentBuilder, "ea/qea/factory/document_builder"
|
|
19
|
+
autoload :EnumTransformer, "ea/qea/factory/enum_transformer"
|
|
20
|
+
autoload :GeneralizationBuilder,
|
|
21
|
+
"ea/qea/factory/generalization_builder"
|
|
22
|
+
autoload :GeneralizationTransformer,
|
|
23
|
+
"ea/qea/factory/generalization_transformer"
|
|
24
|
+
autoload :InstanceTransformer, "ea/qea/factory/instance_transformer"
|
|
25
|
+
autoload :ObjectPropertyTransformer,
|
|
26
|
+
"ea/qea/factory/object_property_transformer"
|
|
27
|
+
autoload :OperationTransformer, "ea/qea/factory/operation_transformer"
|
|
28
|
+
autoload :PackageTransformer, "ea/qea/factory/package_transformer"
|
|
29
|
+
autoload :ReferenceResolver, "ea/qea/factory/reference_resolver"
|
|
30
|
+
autoload :StereotypeLoader, "ea/qea/factory/stereotype_loader"
|
|
31
|
+
autoload :TaggedValueTransformer,
|
|
32
|
+
"ea/qea/factory/tagged_value_transformer"
|
|
33
|
+
autoload :TransformerRegistry, "ea/qea/factory/transformer_registry"
|
|
34
|
+
autoload :EaToUmlFactory, "ea/qea/factory/ea_to_uml_factory"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sqlite3"
|
|
4
|
+
|
|
5
|
+
module Ea
|
|
6
|
+
module Qea
|
|
7
|
+
# Utility for detecting and validating QEA files
|
|
8
|
+
class FileDetector
|
|
9
|
+
# SQLite magic bytes
|
|
10
|
+
SQLITE_MAGIC = "SQLite format 3\x00"
|
|
11
|
+
|
|
12
|
+
# Required EA tables for valid QEA file
|
|
13
|
+
REQUIRED_EA_TABLES = %w[
|
|
14
|
+
t_object
|
|
15
|
+
t_attribute
|
|
16
|
+
t_connector
|
|
17
|
+
t_package
|
|
18
|
+
].freeze
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
# Check if file is a QEA file
|
|
22
|
+
#
|
|
23
|
+
# @param path [String] File path
|
|
24
|
+
# @return [Boolean] True if file appears to be QEA
|
|
25
|
+
def qea_file?(path)
|
|
26
|
+
return false unless File.exist?(path)
|
|
27
|
+
return false unless File.file?(path)
|
|
28
|
+
return false unless path.end_with?(".qea")
|
|
29
|
+
|
|
30
|
+
# Quick check: is it SQLite?
|
|
31
|
+
sqlite_file?(path)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Validate QEA file structure
|
|
35
|
+
#
|
|
36
|
+
# @param path [String] File path
|
|
37
|
+
# @return [Hash] Validation result with :valid, :errors, :warnings
|
|
38
|
+
#
|
|
39
|
+
# @example
|
|
40
|
+
# result = FileDetector.validate_qea("model.qea")
|
|
41
|
+
# if result[:valid]
|
|
42
|
+
# puts "Valid QEA file"
|
|
43
|
+
# else
|
|
44
|
+
# puts "Errors: #{result[:errors]}"
|
|
45
|
+
# end
|
|
46
|
+
def validate_qea(path) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
47
|
+
errors = []
|
|
48
|
+
warnings = []
|
|
49
|
+
|
|
50
|
+
# Check file exists
|
|
51
|
+
unless File.exist?(path)
|
|
52
|
+
return {
|
|
53
|
+
valid: false,
|
|
54
|
+
errors: ["File not found: #{path}"],
|
|
55
|
+
warnings: [],
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Check file extension
|
|
60
|
+
unless path.end_with?(".qea")
|
|
61
|
+
warnings << "File does not have .qea extension"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Check SQLite format
|
|
65
|
+
unless sqlite_file?(path)
|
|
66
|
+
errors << "File is not a valid SQLite database"
|
|
67
|
+
return { valid: false, errors: errors, warnings: warnings }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Check for EA tables
|
|
71
|
+
begin
|
|
72
|
+
db = SQLite3::Database.new(path, readonly: true)
|
|
73
|
+
tables = get_table_names(db)
|
|
74
|
+
|
|
75
|
+
REQUIRED_EA_TABLES.each do |required_table|
|
|
76
|
+
unless tables.include?(required_table)
|
|
77
|
+
errors << "Missing required EA table: #{required_table}"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Check for data
|
|
82
|
+
if tables.include?("t_object")
|
|
83
|
+
count = db.execute("SELECT COUNT(*) FROM t_object").first.first
|
|
84
|
+
if count.zero?
|
|
85
|
+
warnings << "No objects found in t_object table"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
rescue SQLite3::Exception => e
|
|
89
|
+
errors << "Failed to open database: #{e.message}"
|
|
90
|
+
ensure
|
|
91
|
+
db&.close
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
{
|
|
95
|
+
valid: errors.empty?,
|
|
96
|
+
errors: errors,
|
|
97
|
+
warnings: warnings,
|
|
98
|
+
}
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Get file information
|
|
102
|
+
#
|
|
103
|
+
# @param path [String] File path
|
|
104
|
+
# @return [Hash] File information
|
|
105
|
+
#
|
|
106
|
+
# @example
|
|
107
|
+
# info = FileDetector.file_info("model.qea")
|
|
108
|
+
# puts "Size: #{info[:size_mb]} MB"
|
|
109
|
+
# puts "Tables: #{info[:table_count]}"
|
|
110
|
+
def file_info(path) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
111
|
+
return { error: "File not found" } unless File.exist?(path)
|
|
112
|
+
|
|
113
|
+
info = {
|
|
114
|
+
path: path,
|
|
115
|
+
size_bytes: File.size(path),
|
|
116
|
+
size_mb: (File.size(path) / 1024.0 / 1024.0).round(2),
|
|
117
|
+
modified: File.mtime(path),
|
|
118
|
+
is_qea: qea_file?(path),
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if sqlite_file?(path)
|
|
122
|
+
begin
|
|
123
|
+
db = SQLite3::Database.new(path, readonly: true)
|
|
124
|
+
tables = get_table_names(db)
|
|
125
|
+
info[:is_sqlite] = true
|
|
126
|
+
info[:table_count] = tables.size
|
|
127
|
+
info[:has_ea_tables] = REQUIRED_EA_TABLES.all? do |t|
|
|
128
|
+
tables.include?(t)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Get record counts for key tables
|
|
132
|
+
if tables.include?("t_object")
|
|
133
|
+
info[:object_count] =
|
|
134
|
+
db.execute("SELECT COUNT(*) FROM t_object").first.first
|
|
135
|
+
end
|
|
136
|
+
if tables.include?("t_package")
|
|
137
|
+
info[:package_count] =
|
|
138
|
+
db.execute("SELECT COUNT(*) FROM t_package").first.first
|
|
139
|
+
end
|
|
140
|
+
rescue SQLite3::Exception => e
|
|
141
|
+
info[:error] = e.message
|
|
142
|
+
ensure
|
|
143
|
+
db&.close
|
|
144
|
+
end
|
|
145
|
+
else
|
|
146
|
+
info[:is_sqlite] = false
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
info
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
private
|
|
153
|
+
|
|
154
|
+
# Check if file is SQLite database
|
|
155
|
+
#
|
|
156
|
+
# @param path [String] File path
|
|
157
|
+
# @return [Boolean] True if SQLite database
|
|
158
|
+
def sqlite_file?(path)
|
|
159
|
+
File.open(path, "rb") do |f|
|
|
160
|
+
magic = f.read(16)
|
|
161
|
+
magic == SQLITE_MAGIC
|
|
162
|
+
end
|
|
163
|
+
rescue StandardError
|
|
164
|
+
false
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Get table names from database
|
|
168
|
+
#
|
|
169
|
+
# @param db [SQLite3::Database] Database connection
|
|
170
|
+
# @return [Array<String>] Table names
|
|
171
|
+
def get_table_names(db)
|
|
172
|
+
db.execute("SELECT name FROM sqlite_master WHERE type='table'")
|
|
173
|
+
.flatten
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sqlite3"
|
|
4
|
+
|
|
5
|
+
module Ea
|
|
6
|
+
module Qea
|
|
7
|
+
module Infrastructure
|
|
8
|
+
# DatabaseConnection manages the SQLite database connection lifecycle
|
|
9
|
+
# for QEA files (Enterprise Architect SQLite databases).
|
|
10
|
+
#
|
|
11
|
+
# @example Connect to a QEA file
|
|
12
|
+
# conn = DatabaseConnection.new("model.qea")
|
|
13
|
+
# conn.connect
|
|
14
|
+
# # ... use connection
|
|
15
|
+
# conn.close
|
|
16
|
+
#
|
|
17
|
+
# @example Using with_connection block
|
|
18
|
+
# conn = DatabaseConnection.new("model.qea")
|
|
19
|
+
# conn.with_connection do |db|
|
|
20
|
+
# # ... use db
|
|
21
|
+
# end
|
|
22
|
+
class DatabaseConnection
|
|
23
|
+
attr_reader :file_path, :connection
|
|
24
|
+
|
|
25
|
+
# Initialize a new database connection
|
|
26
|
+
#
|
|
27
|
+
# @param file_path [String] Path to the .qea file
|
|
28
|
+
# @raise [ArgumentError] if file_path is nil or empty
|
|
29
|
+
def initialize(file_path)
|
|
30
|
+
if file_path.nil? || file_path.empty?
|
|
31
|
+
raise ArgumentError,
|
|
32
|
+
"file_path cannot be nil or empty"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
@file_path = file_path
|
|
36
|
+
@connection = nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Connect to the database
|
|
40
|
+
#
|
|
41
|
+
# @return [SQLite3::Database] The database connection
|
|
42
|
+
# @raise [Errno::ENOENT] if the file does not exist
|
|
43
|
+
# @raise [SQLite3::Exception] if connection fails
|
|
44
|
+
def connect
|
|
45
|
+
unless File.exist?(@file_path)
|
|
46
|
+
raise Errno::ENOENT, "QEA file not found: #{@file_path}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
@connection = SQLite3::Database.new(@file_path, readonly: true)
|
|
50
|
+
@connection.results_as_hash = true
|
|
51
|
+
@connection
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Close the database connection
|
|
55
|
+
#
|
|
56
|
+
# @return [void]
|
|
57
|
+
def close
|
|
58
|
+
return unless @connection
|
|
59
|
+
|
|
60
|
+
@connection.close
|
|
61
|
+
@connection = nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Check if the connection is open
|
|
65
|
+
#
|
|
66
|
+
# @return [Boolean] true if connection is open
|
|
67
|
+
def connected?
|
|
68
|
+
!@connection.nil? && !@connection.closed?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Execute a block with an active connection
|
|
72
|
+
#
|
|
73
|
+
# This method ensures the connection is properly opened and closed.
|
|
74
|
+
# If a connection already exists, it reuses it. Otherwise, it creates
|
|
75
|
+
# a new connection and closes it after the block executes.
|
|
76
|
+
#
|
|
77
|
+
# @yield [SQLite3::Database] The database connection
|
|
78
|
+
# @return [Object] The result of the block
|
|
79
|
+
# @raise [Errno::ENOENT] if the file does not exist
|
|
80
|
+
# @raise [SQLite3::Exception] if connection fails
|
|
81
|
+
#
|
|
82
|
+
# @example
|
|
83
|
+
# conn = DatabaseConnection.new("model.qea")
|
|
84
|
+
# result = conn.with_connection do |db|
|
|
85
|
+
# db.execute("SELECT COUNT(*) FROM t_object")
|
|
86
|
+
# end
|
|
87
|
+
def with_connection
|
|
88
|
+
should_close = !connected?
|
|
89
|
+
|
|
90
|
+
begin
|
|
91
|
+
connect unless connected?
|
|
92
|
+
yield @connection
|
|
93
|
+
ensure
|
|
94
|
+
close if should_close
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|