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,390 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Transformations
|
|
5
|
+
# Transformation Engine orchestrates the entire model transformation
|
|
6
|
+
# process.
|
|
7
|
+
#
|
|
8
|
+
# This class implements the Facade pattern to provide a simple interface
|
|
9
|
+
# for complex model transformation operations. It coordinates between
|
|
10
|
+
# configuration, format detection, parser selection, and transformation.
|
|
11
|
+
#
|
|
12
|
+
# The engine follows the Dependency Inversion Principle by depending on
|
|
13
|
+
# abstractions (BaseParser interface) rather than concrete implementations.
|
|
14
|
+
#
|
|
15
|
+
# @example Basic usage
|
|
16
|
+
# engine = TransformationEngine.new
|
|
17
|
+
# document = engine.parse("model.xmi")
|
|
18
|
+
#
|
|
19
|
+
# @example With custom configuration
|
|
20
|
+
# config = Configuration.load("my_config.yml")
|
|
21
|
+
# engine = TransformationEngine.new(config)
|
|
22
|
+
# document = engine.parse("model.qea")
|
|
23
|
+
class TransformationEngine
|
|
24
|
+
# @return [Configuration] Current configuration
|
|
25
|
+
attr_reader :configuration
|
|
26
|
+
|
|
27
|
+
# @return [FormatRegistry] Format registry
|
|
28
|
+
attr_reader :format_registry
|
|
29
|
+
|
|
30
|
+
# @return [Array<Hash>] Transformation history
|
|
31
|
+
attr_reader :transformation_history
|
|
32
|
+
|
|
33
|
+
# @return [Parser] Parser instance
|
|
34
|
+
attr_reader :current_parser
|
|
35
|
+
|
|
36
|
+
# Initialize transformation engine
|
|
37
|
+
#
|
|
38
|
+
# @param configuration [Configuration, nil] Configuration to use
|
|
39
|
+
# (defaults to auto-loaded configuration)
|
|
40
|
+
def initialize(configuration = nil)
|
|
41
|
+
@configuration = configuration || Configuration.load
|
|
42
|
+
@format_registry = FormatRegistry.new
|
|
43
|
+
@transformation_history = []
|
|
44
|
+
@parser_cache = {}
|
|
45
|
+
|
|
46
|
+
# Load parsers from configuration
|
|
47
|
+
setup_parsers
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Parse a model file into a UML document
|
|
51
|
+
#
|
|
52
|
+
# This is the main entry point for model transformation. It auto-detects
|
|
53
|
+
# the file format and uses the appropriate parser.
|
|
54
|
+
#
|
|
55
|
+
# @param file_path [String] Path to the model file
|
|
56
|
+
# @param options [Hash] Parsing options (merged with configuration)
|
|
57
|
+
# @return [Lutaml::Uml::Document] Parsed UML document
|
|
58
|
+
# @raise [UnsupportedFormatError] if file format is not supported
|
|
59
|
+
# @raise [ParseError] if parsing fails
|
|
60
|
+
def parse(file_path, options = {}) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
61
|
+
validate_file_path!(file_path)
|
|
62
|
+
|
|
63
|
+
# Detect format and get parser
|
|
64
|
+
parser_class = detect_parser(file_path)
|
|
65
|
+
raise UnsupportedFormatError.new(file_path) unless parser_class
|
|
66
|
+
|
|
67
|
+
# Create parser instance with merged options
|
|
68
|
+
merged_options = merge_options(options)
|
|
69
|
+
@current_parser = get_parser_instance(parser_class, merged_options)
|
|
70
|
+
|
|
71
|
+
# Record transformation start
|
|
72
|
+
transformation_start = Time.now
|
|
73
|
+
|
|
74
|
+
begin
|
|
75
|
+
# Perform parsing
|
|
76
|
+
document = @current_parser.parse(file_path)
|
|
77
|
+
|
|
78
|
+
# Record successful transformation
|
|
79
|
+
record_transformation(
|
|
80
|
+
file_path: file_path,
|
|
81
|
+
parser: @current_parser,
|
|
82
|
+
duration: Time.now - transformation_start,
|
|
83
|
+
success: true,
|
|
84
|
+
document: document,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
document
|
|
88
|
+
rescue StandardError => e
|
|
89
|
+
# Record failed transformation
|
|
90
|
+
record_transformation(
|
|
91
|
+
file_path: file_path,
|
|
92
|
+
parser: @current_parser,
|
|
93
|
+
duration: Time.now - transformation_start,
|
|
94
|
+
success: false,
|
|
95
|
+
error: e,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Re-raise the error
|
|
99
|
+
raise
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Auto-detect file format and return appropriate parser class
|
|
104
|
+
#
|
|
105
|
+
# Uses multiple detection strategies:
|
|
106
|
+
# 1. File extension
|
|
107
|
+
# 2. Content detection (magic bytes)
|
|
108
|
+
# 3. Fallback parser from configuration
|
|
109
|
+
#
|
|
110
|
+
# @param file_path [String] Path to the model file
|
|
111
|
+
# @return [Class, nil] Parser class, or nil if format cannot be detected
|
|
112
|
+
def detect_parser(file_path) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
|
|
113
|
+
# Strategy 1: File extension detection
|
|
114
|
+
if @configuration.file_extension_detection_enabled?
|
|
115
|
+
parser_class = @format_registry.parser_for_file(file_path)
|
|
116
|
+
return parser_class if parser_class
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Strategy 2: Content detection
|
|
120
|
+
if @configuration.content_sniffing_enabled?
|
|
121
|
+
parser_class = @format_registry.detect_by_content(file_path)
|
|
122
|
+
return parser_class if parser_class
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Strategy 3: Fallback parser
|
|
126
|
+
fallback_parser_name = @configuration.fallback_parser
|
|
127
|
+
if fallback_parser_name
|
|
128
|
+
return Transformations.constantize(fallback_parser_name)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
nil
|
|
132
|
+
rescue StandardError
|
|
133
|
+
nil
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Get list of supported file extensions
|
|
137
|
+
#
|
|
138
|
+
# @return [Array<String>] List of supported extensions
|
|
139
|
+
def supported_extensions
|
|
140
|
+
@format_registry.supported_extensions
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Check if a file format is supported
|
|
144
|
+
#
|
|
145
|
+
# @param file_path [String] Path to check
|
|
146
|
+
# @return [Boolean] true if format is supported
|
|
147
|
+
def supports_file?(file_path)
|
|
148
|
+
detect_parser(file_path) != nil
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Register a custom parser for a file extension
|
|
152
|
+
#
|
|
153
|
+
# @param extension [String] File extension (e.g., ".custom")
|
|
154
|
+
# @param parser_class [Class] Parser class implementing BaseParser
|
|
155
|
+
# interface
|
|
156
|
+
# @return [void]
|
|
157
|
+
def register_parser(extension, parser_class)
|
|
158
|
+
@format_registry.register(extension, parser_class)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Unregister a parser for a file extension
|
|
162
|
+
#
|
|
163
|
+
# @param extension [String] File extension to unregister
|
|
164
|
+
# @return [Class, nil] The unregistered parser class
|
|
165
|
+
def unregister_parser(extension)
|
|
166
|
+
@format_registry.unregister(extension)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Set configuration and reload parsers
|
|
170
|
+
#
|
|
171
|
+
# @param config [Configuration] New configuration
|
|
172
|
+
# @return [void]
|
|
173
|
+
def configuration=(config)
|
|
174
|
+
@configuration = config
|
|
175
|
+
@parser_cache.clear
|
|
176
|
+
setup_parsers
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Get comprehensive transformation statistics
|
|
180
|
+
#
|
|
181
|
+
# @return [Hash] Statistics about transformations
|
|
182
|
+
def statistics # rubocop:disable Metrics/MethodLength
|
|
183
|
+
successful_transformations = @transformation_history.count do |t|
|
|
184
|
+
t[:success]
|
|
185
|
+
end
|
|
186
|
+
failed_transformations = @transformation_history.count do |t|
|
|
187
|
+
!t[:success]
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
{
|
|
191
|
+
total_transformations: @transformation_history.size,
|
|
192
|
+
successful_transformations: successful_transformations,
|
|
193
|
+
failed_transformations: failed_transformations,
|
|
194
|
+
success_rate: calculate_success_rate,
|
|
195
|
+
average_duration: calculate_average_duration,
|
|
196
|
+
supported_extensions: supported_extensions,
|
|
197
|
+
registered_parsers: @format_registry.all_parsers.keys,
|
|
198
|
+
configuration_version: @configuration.version,
|
|
199
|
+
}
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Clear transformation history
|
|
203
|
+
#
|
|
204
|
+
# @return [void]
|
|
205
|
+
def clear_history
|
|
206
|
+
@transformation_history.clear
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Get transformation history for a specific file
|
|
210
|
+
#
|
|
211
|
+
# @param file_path [String] Path to the file
|
|
212
|
+
# @return [Array<Hash>] Transformation history entries for the file
|
|
213
|
+
def history_for_file(file_path)
|
|
214
|
+
@transformation_history.select do |entry|
|
|
215
|
+
entry[:file_path] == file_path
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Get recent transformation failures
|
|
220
|
+
#
|
|
221
|
+
# @param limit [Integer] Maximum number of failures to return
|
|
222
|
+
# @return [Array<Hash>] Recent failure entries
|
|
223
|
+
def recent_failures(limit = 10)
|
|
224
|
+
@transformation_history
|
|
225
|
+
.reject { |entry| entry[:success] }
|
|
226
|
+
.last(limit)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Validate configuration and parsers
|
|
230
|
+
#
|
|
231
|
+
# @return [Hash] Validation results
|
|
232
|
+
def validate_setup # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
233
|
+
results = {
|
|
234
|
+
configuration_valid: false,
|
|
235
|
+
parsers_loaded: 0,
|
|
236
|
+
parser_errors: [],
|
|
237
|
+
warnings: [],
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# Validate configuration
|
|
241
|
+
begin
|
|
242
|
+
if @configuration&.enabled_parsers&.any?
|
|
243
|
+
results[:configuration_valid] = true
|
|
244
|
+
else
|
|
245
|
+
results[:warnings] << "No enabled parsers in configuration"
|
|
246
|
+
end
|
|
247
|
+
rescue StandardError => e
|
|
248
|
+
results[:parser_errors] << "Configuration error: #{e.message}"
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Validate each parser
|
|
252
|
+
@format_registry.all_parsers.each_value do |parser_class|
|
|
253
|
+
# Try to create instance to validate
|
|
254
|
+
parser = parser_class.new(configuration: @configuration)
|
|
255
|
+
if parser.is_a?(Parsers::BaseParser)
|
|
256
|
+
results[:parsers_loaded] += 1
|
|
257
|
+
else
|
|
258
|
+
results[:parser_errors] << "Parser #{parser_class} does not " \
|
|
259
|
+
"implement parse method"
|
|
260
|
+
end
|
|
261
|
+
rescue StandardError => e
|
|
262
|
+
results[:parser_errors] << "Failed to instantiate #{parser_class}: " \
|
|
263
|
+
"#{e.message}"
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
results
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
private
|
|
270
|
+
|
|
271
|
+
# Setup parsers from configuration
|
|
272
|
+
#
|
|
273
|
+
# @return [void]
|
|
274
|
+
def setup_parsers
|
|
275
|
+
# Clear existing parsers
|
|
276
|
+
@format_registry.clear
|
|
277
|
+
|
|
278
|
+
# Load parsers from configuration
|
|
279
|
+
@format_registry.load_from_configuration(@configuration)
|
|
280
|
+
|
|
281
|
+
# Load default parsers if none configured
|
|
282
|
+
if @format_registry.supported_extensions.empty?
|
|
283
|
+
@format_registry.load_default_parsers
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Get parser instance (with caching)
|
|
288
|
+
#
|
|
289
|
+
# @param parser_class [Class] Parser class
|
|
290
|
+
# @param options [Hash] Parser options
|
|
291
|
+
# @return [BaseParser] Parser instance
|
|
292
|
+
def get_parser_instance(parser_class, options)
|
|
293
|
+
cache_key = [parser_class, options.hash]
|
|
294
|
+
|
|
295
|
+
@parser_cache[cache_key] ||= parser_class.new(
|
|
296
|
+
configuration: @configuration,
|
|
297
|
+
options: options,
|
|
298
|
+
)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Merge options with configuration defaults
|
|
302
|
+
#
|
|
303
|
+
# @param options [Hash] User-provided options
|
|
304
|
+
# @return [Hash] Merged options
|
|
305
|
+
def merge_options(options) # rubocop:disable Metrics/MethodLength
|
|
306
|
+
default_options = {}
|
|
307
|
+
|
|
308
|
+
if @configuration.transformation_options
|
|
309
|
+
default_options = {
|
|
310
|
+
validate_output: @configuration
|
|
311
|
+
.transformation_options.validate_output,
|
|
312
|
+
include_diagrams: @configuration
|
|
313
|
+
.transformation_options.include_diagrams,
|
|
314
|
+
preserve_ids: @configuration.transformation_options.preserve_ids,
|
|
315
|
+
resolve_references: @configuration
|
|
316
|
+
.transformation_options.resolve_references,
|
|
317
|
+
strict_mode: @configuration.transformation_options.strict_mode,
|
|
318
|
+
}
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
default_options.merge(options)
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# Record transformation in history
|
|
325
|
+
#
|
|
326
|
+
# @param entry [Hash] Transformation entry
|
|
327
|
+
# @return [void]
|
|
328
|
+
def record_transformation(entry)
|
|
329
|
+
# Add timestamp and additional metadata
|
|
330
|
+
full_entry = entry.merge(
|
|
331
|
+
timestamp: Time.now,
|
|
332
|
+
engine_version: self.class.name,
|
|
333
|
+
configuration_version: @configuration.version,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
@transformation_history.push(full_entry)
|
|
337
|
+
@transformation_history.shift if @transformation_history.size > 1000
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Calculate success rate
|
|
341
|
+
#
|
|
342
|
+
# @return [Float] Success rate as percentage
|
|
343
|
+
def calculate_success_rate
|
|
344
|
+
return 0.0 if @transformation_history.empty?
|
|
345
|
+
|
|
346
|
+
successful = @transformation_history.count { |t| t[:success] }
|
|
347
|
+
(successful.to_f / @transformation_history.size * 100).round(2)
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# Calculate average transformation duration
|
|
351
|
+
#
|
|
352
|
+
# @return [Float] Average duration in seconds
|
|
353
|
+
def calculate_average_duration
|
|
354
|
+
return 0.0 if @transformation_history.empty?
|
|
355
|
+
|
|
356
|
+
total_duration = @transformation_history.sum { |t| t[:duration] || 0 }
|
|
357
|
+
(total_duration / @transformation_history.size).round(3)
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# Validate file path
|
|
361
|
+
#
|
|
362
|
+
# @param file_path [String] File path to validate
|
|
363
|
+
# @raise [ArgumentError] if path is invalid
|
|
364
|
+
def validate_file_path!(file_path)
|
|
365
|
+
raise ArgumentError, "File path cannot be nil" if file_path.nil?
|
|
366
|
+
raise ArgumentError, "File path cannot be empty" if file_path.empty?
|
|
367
|
+
|
|
368
|
+
unless File.exist?(file_path)
|
|
369
|
+
raise ArgumentError,
|
|
370
|
+
"File does not exist: #{file_path}"
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
# Error class for unsupported file formats
|
|
376
|
+
class UnsupportedFormatError < Ea::Error
|
|
377
|
+
# @return [String] Path to the unsupported file
|
|
378
|
+
attr_reader :file_path
|
|
379
|
+
|
|
380
|
+
# Initialize error
|
|
381
|
+
#
|
|
382
|
+
# @param file_path [String] Path to unsupported file
|
|
383
|
+
def initialize(file_path)
|
|
384
|
+
@file_path = file_path
|
|
385
|
+
extension = File.extname(file_path)
|
|
386
|
+
super("Unsupported file format: #{extension} (file: #{file_path})")
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Transformations
|
|
5
|
+
autoload :Configuration, "ea/transformations/configuration"
|
|
6
|
+
autoload :FormatRegistry, "ea/transformations/format_registry"
|
|
7
|
+
autoload :TransformationEngine, "ea/transformations/transformation_engine"
|
|
8
|
+
|
|
9
|
+
module Parsers
|
|
10
|
+
autoload :BaseParser, "ea/transformations/parsers/base_parser"
|
|
11
|
+
autoload :XmiParser, "ea/transformations/parsers/xmi_parser"
|
|
12
|
+
autoload :QeaParser, "ea/transformations/parsers/qea_parser"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Resolve a class name string to a constant
|
|
16
|
+
# @param class_name [String] Fully qualified class name (e.g. "Ea::Foo::Bar")
|
|
17
|
+
# @return [Class, nil] The resolved class constant, or nil if not found
|
|
18
|
+
def self.constantize(class_name)
|
|
19
|
+
parts = class_name.split("::")
|
|
20
|
+
constant = Object
|
|
21
|
+
parts.each { |part| constant = constant.const_get(part) }
|
|
22
|
+
constant
|
|
23
|
+
rescue NameError
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class << self
|
|
28
|
+
def engine
|
|
29
|
+
@engine ||= TransformationEngine.new
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def engine=(engine)
|
|
33
|
+
@engine = engine
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def parse(file_path, options = {})
|
|
37
|
+
engine.parse(file_path, options)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def detect_parser(file_path)
|
|
41
|
+
engine.detect_parser(file_path)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def supported_extensions
|
|
45
|
+
engine.supported_extensions
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def supports_file?(file_path)
|
|
49
|
+
engine.supports_file?(file_path)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def statistics
|
|
53
|
+
engine.statistics
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def reset_statistics
|
|
57
|
+
engine.clear_history
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def validate_setup
|
|
61
|
+
engine.validate_setup
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def register_parser(extension, parser_class)
|
|
65
|
+
engine.register_parser(extension, parser_class)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def load_configuration(config_path)
|
|
69
|
+
engine.configuration = Configuration.load(config_path)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def configuration
|
|
73
|
+
engine.configuration
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def configuration=(config)
|
|
77
|
+
engine.configuration = config
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def configure
|
|
81
|
+
yield(configuration) if block_given?
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Transformers
|
|
5
|
+
module QeaToXmi
|
|
6
|
+
# Lightweight value object returned by `Transformer#build_association_end`.
|
|
7
|
+
#
|
|
8
|
+
# Carries both the synthesised xmi:id of the `<ownedEnd>` element
|
|
9
|
+
# (used to populate `<memberEnd idref="...">` on the enclosing
|
|
10
|
+
# `<packagedElement xmi:type="uml:Association">`) and the model
|
|
11
|
+
# instance itself.
|
|
12
|
+
#
|
|
13
|
+
# Using a Struct (not a Hash) makes the contract visible at the
|
|
14
|
+
# call site: typos like `end.xmii_id` raise NoMethodError instead
|
|
15
|
+
# of silently returning nil.
|
|
16
|
+
AssociationEnd = Struct.new(:xmi_id, :model)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Transformers
|
|
5
|
+
module QeaToXmi
|
|
6
|
+
# Pure-function cardinality / multiplicity parser for EA's
|
|
7
|
+
# free-text bound fields (`t_attribute.upperbound`,
|
|
8
|
+
# `t_connector.sourcecard`, etc.).
|
|
9
|
+
#
|
|
10
|
+
# EA stores cardinality as opaque strings: `1`, `0..1`, `1..*`,
|
|
11
|
+
# `*`, occasionally `unbounded` or `*-1`. The XMI wire form needs
|
|
12
|
+
# two separate child elements (`<lowerValue value="N"/>` and
|
|
13
|
+
# `<upperValue value="M"/>`) — never a range string. This module
|
|
14
|
+
# translates the EA form to a `{ lower:, upper: }` pair.
|
|
15
|
+
#
|
|
16
|
+
# Default-when-empty: EA's "no bound specified" maps to UML's
|
|
17
|
+
# unspecified multiplicity, which Sparx renders as
|
|
18
|
+
# `<lowerValue value="0"/>` and `<upperValue value="-1"/>`.
|
|
19
|
+
# Always emitting both is required for round-trip parity with
|
|
20
|
+
# real Sparx XMI (see TODO 26).
|
|
21
|
+
module Cardinality
|
|
22
|
+
# Tokens EA uses for "unbounded". Matched case-insensitively.
|
|
23
|
+
UNLIMITED_TOKENS = %w[* *-1 unbounded].freeze
|
|
24
|
+
|
|
25
|
+
# UML defaults when EA carries no explicit bound.
|
|
26
|
+
DEFAULT_LOWER = "0"
|
|
27
|
+
DEFAULT_UPPER = "-1"
|
|
28
|
+
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
# @param raw [String, nil] e.g. "1..*", "0..1", "1", "*", nil
|
|
32
|
+
# @return [Hash{Symbol=>String}] `{ lower:, upper: }` always
|
|
33
|
+
# populated; never nil. Empty/nil input returns the UML default.
|
|
34
|
+
def parse(raw)
|
|
35
|
+
return defaults if raw.nil? || raw.to_s.strip.empty?
|
|
36
|
+
|
|
37
|
+
stripped = raw.to_s.strip
|
|
38
|
+
return parse_range(stripped) if stripped.include?("..")
|
|
39
|
+
|
|
40
|
+
# Bare unlimited token (e.g. "*") means "many" — lower bound
|
|
41
|
+
# is unspecified, which UML renders as 0..-1. Returning
|
|
42
|
+
# `{ lower: "-1", upper: "-1" }` here would be invalid:
|
|
43
|
+
# LiteralInteger cannot hold -1.
|
|
44
|
+
return defaults if UNLIMITED_TOKENS.include?(stripped.downcase)
|
|
45
|
+
|
|
46
|
+
single = normalize_bound(stripped)
|
|
47
|
+
{ lower: single, upper: single }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Normalise an upper-bound token: `*` / `unbounded` → `-1`
|
|
51
|
+
# (UML LiteralUnlimitedNatural wire form).
|
|
52
|
+
# @param raw [String, Integer, nil]
|
|
53
|
+
# @return [String]
|
|
54
|
+
def normalize_upper(raw)
|
|
55
|
+
return DEFAULT_UPPER if raw.nil?
|
|
56
|
+
|
|
57
|
+
stripped = raw.to_s.strip
|
|
58
|
+
return DEFAULT_UPPER if stripped.empty?
|
|
59
|
+
|
|
60
|
+
UNLIMITED_TOKENS.include?(stripped.downcase) ? "-1" : stripped
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Normalise a lower-bound token: empty/nil → "0" (UML default).
|
|
64
|
+
# @param raw [String, Integer, nil]
|
|
65
|
+
# @return [String]
|
|
66
|
+
def normalize_lower(raw)
|
|
67
|
+
return DEFAULT_LOWER if raw.nil?
|
|
68
|
+
|
|
69
|
+
stripped = raw.to_s.strip
|
|
70
|
+
stripped.empty? ? DEFAULT_LOWER : stripped
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# ---- Internal helpers ----
|
|
74
|
+
|
|
75
|
+
def defaults
|
|
76
|
+
{ lower: DEFAULT_LOWER, upper: DEFAULT_UPPER }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def parse_range(stripped)
|
|
80
|
+
lower, upper = stripped.split("..", 2)
|
|
81
|
+
{ lower: normalize_bound(lower), upper: normalize_bound(upper) }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# A single bound token (one side of `..` or a bare scalar).
|
|
85
|
+
# Empty / `*` → UML unlimited (`-1`).
|
|
86
|
+
def normalize_bound(token)
|
|
87
|
+
return "-1" if token.nil?
|
|
88
|
+
return "-1" if token.strip.empty?
|
|
89
|
+
|
|
90
|
+
stripped = token.strip
|
|
91
|
+
UNLIMITED_TOKENS.include?(stripped.downcase) ? "-1" : stripped
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Transformers
|
|
5
|
+
module QeaToXmi
|
|
6
|
+
# Shared state passed across the walk.
|
|
7
|
+
#
|
|
8
|
+
# Wraps the {Ea::Qea::Database} and provides:
|
|
9
|
+
# - ID-derivation helpers (`xmi_id_for`, `end_xmi_id_for`) backed by
|
|
10
|
+
# {GuidFormat}
|
|
11
|
+
# - delegated database lookups (objects, packages, attributes, etc.)
|
|
12
|
+
#
|
|
13
|
+
# The {Ea::Qea::Database} already maintains its own lookup indexes
|
|
14
|
+
# (object-by-id, connectors-by-object, attributes-by-object, etc.).
|
|
15
|
+
# This class delegates to those rather than re-indexing — single source
|
|
16
|
+
# of truth lives on the database.
|
|
17
|
+
class Context
|
|
18
|
+
attr_reader :database, :id_allocator
|
|
19
|
+
|
|
20
|
+
def initialize(database:)
|
|
21
|
+
@database = database
|
|
22
|
+
@id_allocator = IdAllocator.new
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# ---- ID helpers ---------------------------------------------------
|
|
26
|
+
|
|
27
|
+
# @param record [#ea_guid]
|
|
28
|
+
# @param prefix [String] "EAID" (default) or "EAPK" for top-level packages
|
|
29
|
+
# @return [String, nil]
|
|
30
|
+
def xmi_id_for(record, prefix: "EAID")
|
|
31
|
+
return nil unless record&.ea_guid
|
|
32
|
+
|
|
33
|
+
GuidFormat.ea_guid_to_xmi_id(record.ea_guid, prefix: prefix)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @param connector_xmi_id [String]
|
|
37
|
+
# @param side [Symbol] :source or :destination
|
|
38
|
+
# @return [String]
|
|
39
|
+
def end_xmi_id_for(connector_xmi_id, side:)
|
|
40
|
+
GuidFormat.connector_end_xmi_id(connector_xmi_id, side: side)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# ---- Database lookups --------------------------------------------
|
|
44
|
+
|
|
45
|
+
# @param ea_guid [String]
|
|
46
|
+
# @return [Ea::Qea::Models::EaObject, nil]
|
|
47
|
+
def object_by_guid(ea_guid)
|
|
48
|
+
database.find_object_by_guid(ea_guid)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @param object_id [Integer]
|
|
52
|
+
# @return [Ea::Qea::Models::EaObject, nil]
|
|
53
|
+
def object_by_id(object_id)
|
|
54
|
+
database.find_object(object_id)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @param package_id [Integer]
|
|
58
|
+
# @return [Array<Ea::Qea::Models::EaPackage>]
|
|
59
|
+
def child_packages(package_id)
|
|
60
|
+
database.child_packages_for(package_id)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @param package_id [Integer]
|
|
64
|
+
# @return [Array<Ea::Qea::Models::EaObject>]
|
|
65
|
+
def objects_in_package(package_id)
|
|
66
|
+
database.objects_in_package(package_id)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @param object_id [Integer]
|
|
70
|
+
# @return [Array<Ea::Qea::Models::EaAttribute>]
|
|
71
|
+
def attributes_for(object_id)
|
|
72
|
+
database.attributes_for_object(object_id)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @param object_id [Integer]
|
|
76
|
+
# @return [Array<Ea::Qea::Models::EaOperation>]
|
|
77
|
+
def operations_for(object_id)
|
|
78
|
+
database.operations_for_object(object_id)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @param operation_id [Integer]
|
|
82
|
+
# @return [Array<Ea::Qea::Models::EaOperationParam>]
|
|
83
|
+
def params_for_operation(operation_id)
|
|
84
|
+
database.operation_params_for(operation_id)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Connectors where the object is on either side.
|
|
88
|
+
# @param object_id [Integer]
|
|
89
|
+
# @return [Array<Ea::Qea::Models::EaConnector>]
|
|
90
|
+
def connectors_for(object_id)
|
|
91
|
+
database.connectors_for_object(object_id)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Connectors where this object is the start (source) — used to decide
|
|
95
|
+
# which package owns a relationship connector.
|
|
96
|
+
# @param object_id [Integer]
|
|
97
|
+
# @return [Array<Ea::Qea::Models::EaConnector>]
|
|
98
|
+
def connectors_starting_at(object_id)
|
|
99
|
+
database.connectors_for_object(object_id).select do |conn|
|
|
100
|
+
conn.start_object_id == object_id
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|