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.
Files changed (227) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +125 -0
  3. data/Rakefile +12 -4
  4. data/TODO.next/00-publish-blocking-bugs.md +74 -0
  5. data/TODO.next/01-standalone-ea-gem-identity.md +76 -0
  6. data/TODO.next/02-optional-lutaml-uml-dependency.md +47 -0
  7. data/TODO.next/03-slim-lutaml-uml.md +79 -0
  8. data/TODO.next/04-loader-registry-for-uml-repository.md +49 -0
  9. data/TODO.next/05-extract-shared-transformer-methods.md +14 -0
  10. data/TODO.next/06-deduplicate-stereotype-loading.md +17 -0
  11. data/TODO.next/07-transformer-registry-in-factory.md +20 -0
  12. data/TODO.next/08-connector-type-registry.md +27 -0
  13. data/TODO.next/09-element-renderer-registry.md +29 -0
  14. data/TODO.next/10-connector-renderer-lsp.md +18 -0
  15. data/TODO.next/11-consolidate-style-knowledge.md +33 -0
  16. data/TODO.next/12-data-driven-from-db-row.md +24 -0
  17. data/TODO.next/13-extract-duplicated-methods.md +17 -0
  18. data/TODO.next/14-remove-dead-code.md +10 -0
  19. data/TODO.next/15-narrow-exception-handling.md +39 -0
  20. data/TODO.next/16-repository-indexes.md +28 -0
  21. data/TODO.next/17-fix-spec-quality-and-coverage.md +32 -0
  22. data/TODO.next/18-xmi-tool-specific-parser-architecture.md +172 -0
  23. data/TODO.next/19-fix-ea-gemspec-dependency-declarations.md +56 -0
  24. data/TODO.next/20-ci-requires-unreleased-lutaml-uml.md +63 -0
  25. data/TODO.next/21-qeatoxmi-via-xmi-gem.md +340 -0
  26. data/TODO.next/22-strip-respond-to-from-qeatoxmi-specs.md +32 -0
  27. data/TODO.next/23-cleanup-idallocator.md +41 -0
  28. data/TODO.next/24-tighten-parity-specs.md +42 -0
  29. data/TODO.next/25-sparx-eaid-format-for-synthesized-ids.md +62 -0
  30. data/TODO.next/26-fix-uppervalue-lowervalue-count-gap.md +51 -0
  31. data/TODO.next/27-extract-cardinality-module.md +68 -0
  32. data/TODO.next/28-extract-xml-sanitizer.md +51 -0
  33. data/TODO.next/29-ocp-registry-for-classifier-builders.md +58 -0
  34. data/TODO.next/30-struct-return-for-association-end.md +37 -0
  35. data/TODO.next/31-idallocator-specs.md +27 -0
  36. data/TODO.next/32-phase2-gap-sentinel-specs.md +53 -0
  37. data/TODO.next/33-normalize-lower-cleanup.md +30 -0
  38. data/TODO.next/34-document-member-end-order-rt-prefix.md +29 -0
  39. data/TODO.next/35-walk-runstate-for-instance-slots.md +76 -0
  40. data/TODO.next/36-wire-interface-realization.md +50 -0
  41. data/TODO.next/37-visibility-returns-real-booleans.md +36 -0
  42. data/config/diagram_styles.yml +200 -0
  43. data/config/model_transformations.yml +266 -0
  44. data/config/qea_schema.yml +1024 -0
  45. data/docs/ea_to_uml_type_mapping.md +89 -0
  46. data/docs/xmi_qea_conversion_capabilities.md +99 -0
  47. data/examples/lur/20251010_current_plateau_v5.1.lur +0 -0
  48. data/examples/lur/basic.lur +0 -0
  49. data/examples/lur/test-output.lur +0 -0
  50. data/examples/lur/test.lur +0 -0
  51. data/examples/lur_basic_usage.rb +221 -0
  52. data/examples/lur_cli_workflow.rb +263 -0
  53. data/examples/lur_statistics.rb +326 -0
  54. data/examples/qea/20251010_current_plateau_v5.1.qea +0 -0
  55. data/examples/qea/ArcGISWorkspace_template.qea +0 -0
  56. data/examples/qea/README_qea_parser.adoc +230 -0
  57. data/examples/qea/UmlModel_template.qea +0 -0
  58. data/examples/qea/basic.qea +0 -0
  59. data/examples/qea/simple.qea +0 -0
  60. data/examples/qea/simple_example.qea +0 -0
  61. data/examples/qea/test.qea +0 -0
  62. data/examples/qea_standalone_query.rb +73 -0
  63. data/examples/qea_to_repository.rb +51 -0
  64. data/examples/smoke_test_real_qea.rb +81 -0
  65. data/exe/ea +7 -0
  66. data/lib/ea/cli/app.rb +72 -0
  67. data/lib/ea/cli/command/base.rb +80 -0
  68. data/lib/ea/cli/command/convert.rb +62 -0
  69. data/lib/ea/cli/command/diagrams.rb +81 -0
  70. data/lib/ea/cli/command/list.rb +61 -0
  71. data/lib/ea/cli/command/parse.rb +29 -0
  72. data/lib/ea/cli/command/stats.rb +20 -0
  73. data/lib/ea/cli/command/validate.rb +41 -0
  74. data/lib/ea/cli/command.rb +15 -0
  75. data/lib/ea/cli/error.rb +34 -0
  76. data/lib/ea/cli/output/formatter.rb +34 -0
  77. data/lib/ea/cli/output/json_formatter.rb +20 -0
  78. data/lib/ea/cli/output/table_formatter.rb +42 -0
  79. data/lib/ea/cli/output/yaml_formatter.rb +20 -0
  80. data/lib/ea/cli/output.rb +56 -0
  81. data/lib/ea/cli.rb +17 -0
  82. data/lib/ea/diagram/configuration.rb +379 -0
  83. data/lib/ea/diagram/element_renderers/base_renderer.rb +77 -0
  84. data/lib/ea/diagram/element_renderers/class_renderer.rb +323 -0
  85. data/lib/ea/diagram/element_renderers/connector_renderer.rb +41 -0
  86. data/lib/ea/diagram/element_renderers/package_renderer.rb +61 -0
  87. data/lib/ea/diagram/element_renderers.rb +43 -0
  88. data/lib/ea/diagram/extractor.rb +560 -0
  89. data/lib/ea/diagram/layout_engine.rb +170 -0
  90. data/lib/ea/diagram/path_builder.rb +202 -0
  91. data/lib/ea/diagram/style_parser.rb +42 -0
  92. data/lib/ea/diagram/style_resolver.rb +276 -0
  93. data/lib/ea/diagram/svg_renderer.rb +274 -0
  94. data/lib/ea/diagram/util.rb +73 -0
  95. data/lib/ea/diagram.rb +47 -0
  96. data/lib/ea/qea/benchmark.rb +210 -0
  97. data/lib/ea/qea/database.rb +308 -0
  98. data/lib/ea/qea/factory/association_builder.rb +203 -0
  99. data/lib/ea/qea/factory/association_transformer.rb +91 -0
  100. data/lib/ea/qea/factory/attribute_tag_transformer.rb +57 -0
  101. data/lib/ea/qea/factory/attribute_transformer.rb +93 -0
  102. data/lib/ea/qea/factory/base_transformer.rb +177 -0
  103. data/lib/ea/qea/factory/class_transformer.rb +116 -0
  104. data/lib/ea/qea/factory/constraint_transformer.rb +75 -0
  105. data/lib/ea/qea/factory/data_type_transformer.rb +77 -0
  106. data/lib/ea/qea/factory/diagram_transformer.rb +157 -0
  107. data/lib/ea/qea/factory/document_builder.rb +283 -0
  108. data/lib/ea/qea/factory/ea_to_uml_factory.rb +229 -0
  109. data/lib/ea/qea/factory/enum_transformer.rb +74 -0
  110. data/lib/ea/qea/factory/generalization_builder.rb +227 -0
  111. data/lib/ea/qea/factory/generalization_transformer.rb +98 -0
  112. data/lib/ea/qea/factory/instance_transformer.rb +68 -0
  113. data/lib/ea/qea/factory/object_property_transformer.rb +58 -0
  114. data/lib/ea/qea/factory/operation_transformer.rb +66 -0
  115. data/lib/ea/qea/factory/package_transformer.rb +145 -0
  116. data/lib/ea/qea/factory/reference_resolver.rb +99 -0
  117. data/lib/ea/qea/factory/stereotype_loader.rb +39 -0
  118. data/lib/ea/qea/factory/tagged_value_transformer.rb +38 -0
  119. data/lib/ea/qea/factory/transformer_registry.rb +80 -0
  120. data/lib/ea/qea/factory.rb +37 -0
  121. data/lib/ea/qea/file_detector.rb +178 -0
  122. data/lib/ea/qea/infrastructure/database_connection.rb +100 -0
  123. data/lib/ea/qea/infrastructure/schema_reader.rb +136 -0
  124. data/lib/ea/qea/infrastructure/table_reader.rb +224 -0
  125. data/lib/ea/qea/infrastructure.rb +12 -0
  126. data/lib/ea/qea/models/base_model.rb +59 -0
  127. data/lib/ea/qea/models/ea_attribute.rb +109 -0
  128. data/lib/ea/qea/models/ea_attribute_tag.rb +100 -0
  129. data/lib/ea/qea/models/ea_complexity_type.rb +79 -0
  130. data/lib/ea/qea/models/ea_connector.rb +160 -0
  131. data/lib/ea/qea/models/ea_connector_type.rb +60 -0
  132. data/lib/ea/qea/models/ea_constraint_type.rb +63 -0
  133. data/lib/ea/qea/models/ea_datatype.rb +104 -0
  134. data/lib/ea/qea/models/ea_diagram.rb +115 -0
  135. data/lib/ea/qea/models/ea_diagram_link.rb +78 -0
  136. data/lib/ea/qea/models/ea_diagram_object.rb +73 -0
  137. data/lib/ea/qea/models/ea_diagram_type.rb +56 -0
  138. data/lib/ea/qea/models/ea_document.rb +63 -0
  139. data/lib/ea/qea/models/ea_object.rb +223 -0
  140. data/lib/ea/qea/models/ea_object_constraint.rb +53 -0
  141. data/lib/ea/qea/models/ea_object_property.rb +87 -0
  142. data/lib/ea/qea/models/ea_object_type.rb +73 -0
  143. data/lib/ea/qea/models/ea_operation.rb +127 -0
  144. data/lib/ea/qea/models/ea_operation_param.rb +76 -0
  145. data/lib/ea/qea/models/ea_package.rb +78 -0
  146. data/lib/ea/qea/models/ea_script.rb +62 -0
  147. data/lib/ea/qea/models/ea_status_type.rb +66 -0
  148. data/lib/ea/qea/models/ea_stereotype.rb +57 -0
  149. data/lib/ea/qea/models/ea_tagged_value.rb +99 -0
  150. data/lib/ea/qea/models/ea_xref.rb +165 -0
  151. data/lib/ea/qea/models.rb +35 -0
  152. data/lib/ea/qea/repositories/base_repository.rb +225 -0
  153. data/lib/ea/qea/repositories/object_repository.rb +219 -0
  154. data/lib/ea/qea/repositories.rb +10 -0
  155. data/lib/ea/qea/services/configuration.rb +211 -0
  156. data/lib/ea/qea/services/database_loader.rb +191 -0
  157. data/lib/ea/qea/services.rb +10 -0
  158. data/lib/ea/qea/validation/association_validator.rb +73 -0
  159. data/lib/ea/qea/validation/attribute_validator.rb +91 -0
  160. data/lib/ea/qea/validation/base_validator.rb +331 -0
  161. data/lib/ea/qea/validation/class_validator.rb +121 -0
  162. data/lib/ea/qea/validation/database/circular_reference_validator.rb +109 -0
  163. data/lib/ea/qea/validation/database/orphan_validator.rb +153 -0
  164. data/lib/ea/qea/validation/database/referential_integrity_validator.rb +128 -0
  165. data/lib/ea/qea/validation/database.rb +16 -0
  166. data/lib/ea/qea/validation/diagram_validator.rb +112 -0
  167. data/lib/ea/qea/validation/formatters/json_formatter.rb +137 -0
  168. data/lib/ea/qea/validation/formatters/text_formatter.rb +235 -0
  169. data/lib/ea/qea/validation/formatters.rb +12 -0
  170. data/lib/ea/qea/validation/operation_validator.rb +71 -0
  171. data/lib/ea/qea/validation/package_validator.rb +111 -0
  172. data/lib/ea/qea/validation/validation_engine.rb +387 -0
  173. data/lib/ea/qea/validation/validation_message.rb +144 -0
  174. data/lib/ea/qea/validation/validation_result.rb +210 -0
  175. data/lib/ea/qea/validation/validator_registry.rb +134 -0
  176. data/lib/ea/qea/validation.rb +28 -0
  177. data/lib/ea/qea/verification/comparison_result.rb +264 -0
  178. data/lib/ea/qea/verification/document_normalizer.rb +169 -0
  179. data/lib/ea/qea/verification/document_verifier.rb +322 -0
  180. data/lib/ea/qea/verification/element_comparator.rb +277 -0
  181. data/lib/ea/qea/verification/structure_matcher.rb +287 -0
  182. data/lib/ea/qea/verification.rb +14 -0
  183. data/lib/ea/qea.rb +185 -0
  184. data/lib/ea/transformations/configuration.rb +333 -0
  185. data/lib/ea/transformations/format_registry.rb +366 -0
  186. data/lib/ea/transformations/parsers/base_parser.rb +482 -0
  187. data/lib/ea/transformations/parsers/qea_parser.rb +401 -0
  188. data/lib/ea/transformations/parsers/xmi_parser.rb +243 -0
  189. data/lib/ea/transformations/transformation_engine.rb +390 -0
  190. data/lib/ea/transformations.rb +85 -0
  191. data/lib/ea/transformers/qea_to_xmi/association_end.rb +19 -0
  192. data/lib/ea/transformers/qea_to_xmi/cardinality.rb +96 -0
  193. data/lib/ea/transformers/qea_to_xmi/context.rb +106 -0
  194. data/lib/ea/transformers/qea_to_xmi/guid_format.rb +56 -0
  195. data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +92 -0
  196. data/lib/ea/transformers/qea_to_xmi/run_state.rb +107 -0
  197. data/lib/ea/transformers/qea_to_xmi/transformer.rb +607 -0
  198. data/lib/ea/transformers/qea_to_xmi/visibility.rb +73 -0
  199. data/lib/ea/transformers/qea_to_xmi.rb +29 -0
  200. data/lib/ea/transformers/uml_to_xmi/id_generator.rb +54 -0
  201. data/lib/ea/transformers/uml_to_xmi/transformer.rb +152 -0
  202. data/lib/ea/transformers/uml_to_xmi/writer.rb +96 -0
  203. data/lib/ea/transformers/uml_to_xmi.rb +16 -0
  204. data/lib/ea/transformers.rb +34 -0
  205. data/lib/ea/version.rb +1 -1
  206. data/lib/ea/xmi/liquid_drops/association_drop.rb +56 -0
  207. data/lib/ea/xmi/liquid_drops/attribute_drop.rb +72 -0
  208. data/lib/ea/xmi/liquid_drops/cardinality_drop.rb +35 -0
  209. data/lib/ea/xmi/liquid_drops/connector_drop.rb +54 -0
  210. data/lib/ea/xmi/liquid_drops/constraint_drop.rb +29 -0
  211. data/lib/ea/xmi/liquid_drops/data_type_drop.rb +63 -0
  212. data/lib/ea/xmi/liquid_drops/dependency_drop.rb +36 -0
  213. data/lib/ea/xmi/liquid_drops/diagram_drop.rb +34 -0
  214. data/lib/ea/xmi/liquid_drops/enum_drop.rb +49 -0
  215. data/lib/ea/xmi/liquid_drops/enum_owned_literal_drop.rb +25 -0
  216. data/lib/ea/xmi/liquid_drops/generalization_attribute_drop.rb +87 -0
  217. data/lib/ea/xmi/liquid_drops/generalization_drop.rb +127 -0
  218. data/lib/ea/xmi/liquid_drops/klass_drop.rb +191 -0
  219. data/lib/ea/xmi/liquid_drops/operation_drop.rb +29 -0
  220. data/lib/ea/xmi/liquid_drops/package_drop.rb +108 -0
  221. data/lib/ea/xmi/liquid_drops/root_drop.rb +34 -0
  222. data/lib/ea/xmi/liquid_drops/source_target_drop.rb +43 -0
  223. data/lib/ea/xmi/lookup_service.rb +89 -0
  224. data/lib/ea/xmi/parser.rb +919 -0
  225. data/lib/ea/xmi.rb +35 -0
  226. data/lib/ea.rb +10 -1
  227. metadata +382 -9
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Factory
6
+ # Transforms EA attributes to UML attributes
7
+ class AttributeTransformer < BaseTransformer
8
+ # Transform EA attribute to UML attribute
9
+ # @param ea_attribute [EaAttribute] EA attribute model
10
+ # @return [Lutaml::Uml::TopElementAttribute] UML attribute
11
+ def transform(ea_attribute) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
12
+ return nil if ea_attribute.nil?
13
+
14
+ Lutaml::Uml::TopElementAttribute.new.tap do |attr|
15
+ attr.name = ea_attribute.name
16
+ attr.type = ea_attribute.type
17
+ attr.visibility = map_visibility(ea_attribute.scope)
18
+
19
+ # XMI uses the TYPE's XMI ID, not the attribute's ID
20
+ type_xmi_id = lookup_type_xmi_id(ea_attribute.classifier)
21
+ attr.xmi_id = type_xmi_id || normalize_guid_to_xmi_format(
22
+ ea_attribute.ea_guid, "EAID"
23
+ )
24
+
25
+ attr.id = normalize_guid_to_xmi_format(ea_attribute.ea_guid, "EAID")
26
+ attr.static = ea_attribute.static? ? "true" : nil
27
+ attr.is_derived = ea_attribute.derived == "1"
28
+
29
+ # Map cardinality if bounds are present
30
+ if ea_attribute.lowerbound || ea_attribute.upperbound
31
+ attr.cardinality = build_cardinality(
32
+ ea_attribute.lowerbound,
33
+ ea_attribute.upperbound,
34
+ )
35
+ end
36
+
37
+ # Map definition/notes
38
+ attr.definition = normalize_line_endings(ea_attribute.notes) unless
39
+ ea_attribute.notes.nil? || ea_attribute.notes.empty?
40
+
41
+ # Load and transform attribute tags
42
+ attr.tagged_values = load_attribute_tags(ea_attribute.id)
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ # Look up the type object's XMI ID from classifier
49
+ # @param classifier_id [Integer] Classifier ID
50
+ # @return [String, nil] Type's XMI ID
51
+ def lookup_type_xmi_id(classifier_id) # rubocop:disable Metrics/AbcSize
52
+ return nil if classifier_id.nil? || classifier_id.to_i.zero?
53
+
54
+ obj = database.find_object(classifier_id.to_i)
55
+ return nil unless obj
56
+
57
+ normalize_guid_to_xmi_format(obj.ea_guid, "EAID")
58
+ end
59
+
60
+ # Build cardinality from lower and upper bounds
61
+ # @param lower [String] Lower bound
62
+ # @param upper [String] Upper bound
63
+ # @return [Lutaml::Uml::Cardinality] Cardinality object
64
+ def build_cardinality(lower, upper)
65
+ return nil if lower.nil? && upper.nil?
66
+
67
+ Lutaml::Uml::Cardinality.new.tap do |card|
68
+ card.min = lower || "0"
69
+ card.max = upper || "*"
70
+ end
71
+ end
72
+
73
+ # Load and transform attribute tags for an attribute
74
+ # @param attribute_id [Integer] Attribute ID
75
+ # @return [Array<Lutaml::Uml::TaggedValue>] UML tagged values
76
+ def load_attribute_tags(attribute_id)
77
+ return [] if attribute_id.nil?
78
+ return [] unless database.attribute_tags
79
+
80
+ # Filter attribute tags for this attribute from the in-memory
81
+ # collection
82
+ ea_tags = database.attribute_tags.select do |tag|
83
+ tag.element_id == attribute_id
84
+ end
85
+
86
+ # Transform to UML tagged values
87
+ tag_transformer = AttributeTagTransformer.new(database)
88
+ tag_transformer.transform_collection(ea_tags)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Factory
6
+ # Abstract base class for all EA to UML transformers
7
+ # Implements the Strategy pattern for model transformation
8
+ class BaseTransformer
9
+ attr_reader :database
10
+
11
+ # Initialize transformer with database reference
12
+ # @param database [Ea::Qea::Database] QEA database instance
13
+ def initialize(database)
14
+ @database = database
15
+ end
16
+
17
+ # Transform a single EA model to UML model
18
+ # @param ea_model [BaseModel] EA model instance
19
+ # @return [Object] UML model instance
20
+ # @raise [NotImplementedError] Must be implemented by subclasses
21
+ def transform(ea_model)
22
+ raise NotImplementedError,
23
+ "#{self.class} must implement #transform"
24
+ end
25
+
26
+ # Transform a collection of EA models to UML models
27
+ # @param collection [Array<BaseModel>] Collection of EA models
28
+ # @return [Array<Object>] Collection of UML models
29
+ def transform_collection(collection)
30
+ return [] if collection.nil? || collection.empty?
31
+
32
+ collection.filter_map { |item| transform(item) }
33
+ end
34
+
35
+ # Map EA visibility to UML visibility
36
+ # @param ea_visibility [String] EA visibility value
37
+ # @return [String] UML visibility value
38
+ def map_visibility(ea_visibility) # rubocop:disable Metrics/CyclomaticComplexity
39
+ return "public" if ea_visibility.nil? || ea_visibility.empty?
40
+
41
+ case ea_visibility.downcase
42
+ when "private" then "private"
43
+ when "protected" then "protected"
44
+ when "package" then "package"
45
+ else "public"
46
+ end
47
+ end
48
+
49
+ # Parse cardinality string to min/max values
50
+ # @param cardinality_str [String] Cardinality string (e.g., "0..1",
51
+ # "1..*")
52
+ # @return [Hash] Hash with :min and :max keys
53
+ def parse_cardinality(cardinality_str)
54
+ return { min: nil, max: nil } if cardinality_str.nil? ||
55
+ cardinality_str.empty?
56
+
57
+ parts = cardinality_str.split("..")
58
+ if parts.size == 2
59
+ { min: parts[0], max: parts[1] }
60
+ elsif parts.size == 1
61
+ { min: parts[0], max: parts[0] }
62
+ else
63
+ { min: nil, max: nil }
64
+ end
65
+ end
66
+
67
+ # Convert boolean-like values to actual boolean
68
+ # @param value [Object] Value to convert
69
+ # @return [Boolean] Boolean value
70
+ def to_boolean(value)
71
+ return false if value.nil?
72
+ return value if [true, false].include?(value)
73
+
74
+ value.to_s == "1" || value.to_s.downcase == "true"
75
+ end
76
+
77
+ # Normalize EA GUID to XMI ID format
78
+ # Converts {GUID-WITH-HYPHENS} to PREFIX_GUID_WITH_UNDERSCORES
79
+ # @param ea_guid [String] EA GUID in format
80
+ # "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"
81
+ # @param prefix [String] Prefix to add (e.g., "EAID", "EAPK")
82
+ # @return [String, nil] XMI ID in format
83
+ # "PREFIX_XXXXXXXX_XXXX_XXXX_XXXX_XXXXXXXXXXXX"
84
+ def normalize_guid_to_xmi_format(ea_guid, prefix = "EAID")
85
+ return nil if ea_guid.nil? || ea_guid.empty?
86
+
87
+ # Remove braces and replace hyphens with underscores
88
+ clean = ea_guid.tr("{}", "").tr("-", "_")
89
+ "#{prefix}_#{clean}"
90
+ end
91
+
92
+ # Convert ea_guid to XMI SRC ID format
93
+ def normalize_guid_to_xmi_src_dst_format(
94
+ ea_guid, prefix = "EAID", is_src = true # rubocop:disable Style/OptionalBooleanParameter
95
+ )
96
+ xmi_id = normalize_guid_to_xmi_format(ea_guid, prefix)
97
+
98
+ # Trim prefix and add _src or _dst
99
+ src_dst = is_src ? "src" : "dst"
100
+ clean = xmi_id[(prefix.length + 3), xmi_id.length]
101
+ "#{prefix}_#{src_dst}#{clean}"
102
+ end
103
+
104
+ # Normalize line endings from Windows (\r\n) to Unix (\n)
105
+ # EA database stores text with Windows line endings, but XMI uses Unix
106
+ # @param text [String, nil] Text to normalize
107
+ # @return [String, nil] Text with normalized line endings
108
+ def normalize_line_endings(text)
109
+ return nil if text.nil?
110
+
111
+ text.gsub("\r\n", "\n")
112
+ end
113
+
114
+ # Find object by ID
115
+ # @param object_id [Integer] Object ID
116
+ # @return [Models::EaObject, nil] EA object or nil
117
+ def find_object_by_id(object_id)
118
+ return nil if object_id.nil?
119
+
120
+ database.find_object(object_id)
121
+ end
122
+
123
+ # Load and transform tagged values for an element
124
+ # @param ea_guid [String] Element GUID
125
+ # @return [Array<Lutaml::Uml::TaggedValue>] UML tagged values
126
+ def load_tagged_values(ea_guid)
127
+ return [] if ea_guid.nil?
128
+
129
+ ea_tags = database.tagged_values_for_element(ea_guid)
130
+ TaggedValueTransformer.new(database).transform_collection(ea_tags)
131
+ end
132
+
133
+ # Load and transform attributes for an object
134
+ # @param object_id [Integer] Object ID
135
+ # @return [Array<Lutaml::Uml::TopElementAttribute>] UML attributes
136
+ def load_attributes(object_id)
137
+ return [] if object_id.nil?
138
+
139
+ ea_attributes = database.attributes_for_object(object_id)
140
+ .sort_by { |a| a.pos || 0 }
141
+ AttributeTransformer.new(database).transform_collection(ea_attributes)
142
+ end
143
+
144
+ # Load and transform operations for an object
145
+ # @param object_id [Integer] Object ID
146
+ # @return [Array<Lutaml::Uml::Operation>] UML operations
147
+ def load_operations(object_id)
148
+ return [] if object_id.nil?
149
+
150
+ ea_operations = database.operations_for_object(object_id)
151
+ .sort_by { |op| op.pos || 0 }
152
+ OperationTransformer.new(database).transform_collection(ea_operations)
153
+ end
154
+
155
+ # Load and transform constraints for an object
156
+ # @param object_id [Integer] Object ID
157
+ # @return [Array<Lutaml::Uml::Constraint>] UML constraints
158
+ def load_constraints(object_id)
159
+ return [] if object_id.nil?
160
+
161
+ ea_constraints = database.constraints_for_object(object_id)
162
+ ConstraintTransformer.new(database).transform_collection(ea_constraints)
163
+ end
164
+
165
+ # Load and transform object properties for an object
166
+ # @param object_id [Integer] Object ID
167
+ # @return [Array<Lutaml::Uml::TaggedValue>] UML tagged values
168
+ def load_object_properties(object_id)
169
+ return [] if object_id.nil?
170
+
171
+ ea_props = database.properties_for_object(object_id)
172
+ ObjectPropertyTransformer.new(database).transform_collection(ea_props)
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Factory
6
+ class ClassTransformer < BaseTransformer
7
+ def transform(ea_object)
8
+ return nil if ea_object.nil?
9
+ return nil unless transformable?(ea_object)
10
+
11
+ Lutaml::Uml::UmlClass.new.tap do |klass|
12
+ assign_basic_properties(klass, ea_object)
13
+ assign_features(klass, ea_object)
14
+ assign_relationships(klass, ea_object)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def transformable?(ea_object)
21
+ ea_object.uml_class? || ea_object.interface?
22
+ end
23
+
24
+ def assign_basic_properties(klass, ea_object)
25
+ klass.name = ea_object.name
26
+ klass.xmi_id = normalize_guid_to_xmi_format(ea_object.ea_guid, "EAID")
27
+ klass.is_abstract = ea_object.abstract?
28
+ klass.type = "Class"
29
+ klass.visibility = map_visibility(ea_object.visibility)
30
+ assign_stereotypes(klass, ea_object)
31
+ assign_definition(klass, ea_object)
32
+ end
33
+
34
+ def assign_stereotypes(klass, ea_object)
35
+ stereotypes = build_stereotypes(ea_object)
36
+ klass.stereotype = stereotypes unless stereotypes.empty?
37
+ end
38
+
39
+ def assign_definition(klass, ea_object)
40
+ return if ea_object.note.nil? || ea_object.note.empty?
41
+
42
+ klass.definition = normalize_line_endings(ea_object.note)
43
+ end
44
+
45
+ def assign_features(klass, ea_object)
46
+ klass.attributes = load_all_attributes(ea_object)
47
+ assign_feature_collections(klass, ea_object)
48
+ end
49
+
50
+ def assign_feature_collections(klass, ea_object)
51
+ klass.operations = load_operations(ea_object.ea_object_id)
52
+ klass.constraints = load_constraints(ea_object.ea_object_id)
53
+ klass.tagged_values = load_tagged_values(ea_object.ea_guid)
54
+ klass.tagged_values.concat(
55
+ load_object_properties(ea_object.ea_object_id),
56
+ )
57
+ end
58
+
59
+ def assign_relationships(klass, ea_object)
60
+ gen_builder = GeneralizationBuilder.new(database)
61
+ assoc_builder = AssociationBuilder.new(database)
62
+
63
+ klass.generalization = gen_builder.load_generalization(
64
+ ea_object.ea_object_id,
65
+ )
66
+ klass.association_generalization = gen_builder
67
+ .load_association_generalizations(ea_object.ea_object_id)
68
+
69
+ klass.associations = assoc_builder.load_class_associations(
70
+ ea_object.ea_object_id, ea_object.ea_guid
71
+ )
72
+ end
73
+
74
+ def load_all_attributes(ea_object)
75
+ gen_builder = GeneralizationBuilder.new(database)
76
+ assoc_builder = AssociationBuilder.new(database)
77
+
78
+ attrs = load_attributes(ea_object.ea_object_id)
79
+ assoc_attrs = gen_builder.convert_to_top_element_attributes(
80
+ assoc_builder.load_association_attributes(ea_object.ea_object_id),
81
+ )
82
+ attrs + assoc_attrs
83
+ end
84
+
85
+ def build_stereotypes(ea_object)
86
+ stereotypes = []
87
+ add_direct_stereotype(stereotypes, ea_object)
88
+ add_xref_stereotype(stereotypes, ea_object)
89
+ add_interface_stereotype(stereotypes, ea_object)
90
+
91
+ stereotypes
92
+ end
93
+
94
+ def add_direct_stereotype(stereotypes, ea_object)
95
+ return unless ea_object.stereotype && !ea_object.stereotype.empty?
96
+
97
+ stereotypes << ea_object.stereotype
98
+ end
99
+
100
+ def add_xref_stereotype(stereotypes, ea_object)
101
+ xref_stereotype = StereotypeLoader.new(database)
102
+ .load_from_xref(ea_object.ea_guid)
103
+ return unless xref_stereotype && !stereotypes.include?(xref_stereotype)
104
+
105
+ stereotypes << xref_stereotype
106
+ end
107
+
108
+ def add_interface_stereotype(stereotypes, ea_object)
109
+ return unless ea_object.interface? && !stereotypes.include?("interface")
110
+
111
+ stereotypes << "interface"
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Factory
6
+ # Transforms EA ObjectConstraint to UML Constraint
7
+ #
8
+ # This transformer converts Enterprise Architect constraint definitions
9
+ # (typically OCL constraints) to standard UML Constraint objects.
10
+ #
11
+ # @example Transform a constraint
12
+ # ea_constraint = Models::EaObjectConstraint.new(
13
+ # constraint_id: 1,
14
+ # object_id: 4,
15
+ # constraint: "count(self.legalConstraints) >= 1",
16
+ # constraint_type: "Invariant",
17
+ # weight: 0.0,
18
+ # status: "Approved"
19
+ # )
20
+ # transformer = ConstraintTransformer.new(database)
21
+ # uml_constraint = transformer.transform(ea_constraint)
22
+ class ConstraintTransformer < BaseTransformer
23
+ # Transform EA constraint to UML Constraint
24
+ #
25
+ # @param ea_constraint [Models::EaObjectConstraint] EA constraint model
26
+ # @return [Lutaml::Uml::Constraint, nil] UML constraint or nil
27
+ def transform(ea_constraint) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
28
+ return nil unless ea_constraint
29
+
30
+ Lutaml::Uml::Constraint.new.tap do |constraint|
31
+ # Generate unique ID for the constraint
32
+ constraint.xmi_id = "constraint_#{ea_constraint.constraint_id}" if
33
+ ea_constraint.constraint_id
34
+
35
+ # Generate descriptive name from constraint body or use ID
36
+ constraint.name = constraint_name(ea_constraint)
37
+
38
+ # Map constraint properties
39
+ constraint.body = ea_constraint.constraint
40
+ constraint.type = ea_constraint.constraint_type
41
+ constraint.weight = ea_constraint.weight&.to_s
42
+ constraint.status = ea_constraint.status
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ # Generate descriptive name from constraint body
49
+ #
50
+ # Extracts the first meaningful part of the constraint body to use
51
+ # as a descriptive name. Falls back to a generic name if extraction
52
+ # fails.
53
+ #
54
+ # @param ea_constraint [Models::EaObjectConstraint] EA constraint
55
+ # @return [String] Constraint name
56
+ def constraint_name(ea_constraint) # rubocop:disable Metrics/MethodLength
57
+ return "constraint_#{ea_constraint.constraint_id}" unless
58
+ ea_constraint.constraint
59
+
60
+ body = ea_constraint.constraint.to_s.strip
61
+
62
+ # Try to extract a meaningful name from the constraint body
63
+ # Take first 50 chars or until special chars
64
+ name_part = body[0..50].split(/[()<>=\s]/).first&.strip
65
+
66
+ if name_part && !name_part.empty?
67
+ name_part
68
+ else
69
+ "constraint_#{ea_constraint.constraint_id}"
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Factory
6
+ # Transforms EA objects (DataType type) to UML data types
7
+ class DataTypeTransformer < BaseTransformer
8
+ # Transform EA object to UML data type
9
+ # @param ea_object [EaObject] EA object model
10
+ # @return [Lutaml::Uml::DataType] UML data type
11
+ def transform(ea_object) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
12
+ return nil if ea_object.nil?
13
+ return nil unless ea_object.data_type?
14
+
15
+ Lutaml::Uml::DataType.new.tap do |data_type| # rubocop:disable Metrics/BlockLength
16
+ # Map basic properties
17
+ data_type.name = ea_object.name
18
+ data_type.xmi_id = normalize_guid_to_xmi_format(ea_object.ea_guid,
19
+ "EAID")
20
+ data_type.is_abstract = ea_object.abstract?
21
+ data_type.type = "DataType"
22
+ data_type.visibility = map_visibility(ea_object.visibility)
23
+
24
+ # Map stereotype
25
+ if ea_object.stereotype && !ea_object.stereotype.empty?
26
+ data_type.stereotype = [ea_object.stereotype]
27
+ end
28
+
29
+ # Map definition/notes
30
+ data_type.definition = ea_object.note unless
31
+ ea_object.note.nil? || ea_object.note.empty?
32
+
33
+ # Load and transform attributes
34
+ data_type.attributes = load_attributes(ea_object.ea_object_id)
35
+
36
+ # Load and transform operations
37
+ data_type.operations = load_operations(ea_object.ea_object_id)
38
+
39
+ # Load and transform constraints
40
+ data_type.constraints = load_constraints(ea_object.ea_object_id)
41
+
42
+ # Load and transform tagged values
43
+ data_type.tagged_values = load_tagged_values(ea_object.ea_guid)
44
+
45
+ # Load associations for this data type
46
+ data_type.associations = load_associations(ea_object.ea_object_id,
47
+ ea_object.ea_guid)
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ # Load associations for a data type
54
+ # @param object_id [Integer] Object ID
55
+ # @param object_guid [String] Object GUID
56
+ # @return [Array<Lutaml::Uml::Association>] UML associations
57
+ def load_associations(object_id, object_guid) # rubocop:disable Metrics/MethodLength
58
+ return [] if object_id.nil?
59
+
60
+ assoc_connectors = database.connectors_for_object(object_id)
61
+ .select { |c| c.connector_type == "Association" }
62
+
63
+ assoc_transformer = AssociationTransformer.new(database)
64
+ normalized_xmi_id = normalize_guid_to_xmi_format(object_guid, "EAID")
65
+
66
+ assoc_connectors.filter_map do |ea_connector|
67
+ assoc = assoc_transformer.transform(ea_connector)
68
+
69
+ next unless assoc && assoc.owner_end_xmi_id == normalized_xmi_id
70
+
71
+ assoc
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Factory
6
+ # Transforms EA diagrams to UML diagrams
7
+ #
8
+ # This transformer loads diagram data along with diagram objects
9
+ # (visual placement) and diagram links (visual connector routing)
10
+ # to create a complete UML diagram representation.
11
+ class DiagramTransformer < BaseTransformer
12
+ # Transform EA diagram to UML diagram
13
+ # @param ea_diagram [EaDiagram] EA diagram model
14
+ # @return [Lutaml::Uml::Diagram] UML diagram
15
+ def transform(ea_diagram) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/MethodLength
16
+ return nil if ea_diagram.nil?
17
+
18
+ Lutaml::Uml::Diagram.new.tap do |diagram| # rubocop:disable Metrics/BlockLength
19
+ # Map basic properties
20
+ diagram.name = ea_diagram.name
21
+ diagram.xmi_id = normalize_guid_to_xmi_format(ea_diagram.ea_guid,
22
+ "EAID")
23
+
24
+ # Map package relationship - use GUID not numeric ID
25
+ if ea_diagram.package_id
26
+ package = find_package(ea_diagram.package_id)
27
+ if package
28
+ diagram.package_id = normalize_guid_to_xmi_format(
29
+ package.ea_guid, "EAPK"
30
+ )
31
+ diagram.package_name = package.name
32
+ end
33
+ end
34
+
35
+ # Map definition/notes
36
+ diagram.definition = ea_diagram.notes unless
37
+ ea_diagram.notes.nil? || ea_diagram.notes.empty?
38
+
39
+ # Map stereotype
40
+ if ea_diagram.stereotype && !ea_diagram.stereotype.empty?
41
+ diagram.stereotype = [ea_diagram.stereotype]
42
+ end
43
+
44
+ # Load and transform diagram objects (visual placement)
45
+ diagram_objects = load_diagram_objects(ea_diagram.diagram_id)
46
+ if diagram_objects.any?
47
+ diagram.diagram_objects.concat(diagram_objects)
48
+ end
49
+
50
+ # Load and transform diagram links (visual routing)
51
+ diagram_links = load_diagram_links(ea_diagram.diagram_id)
52
+ diagram.diagram_links.concat(diagram_links) if diagram_links.any?
53
+
54
+ # Load diagram type
55
+ diagram.diagram_type = ea_diagram.diagram_type
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ # Find package by ID
62
+ # @param package_id [Integer] Package ID
63
+ # @return [EaPackage, nil] EA package or nil if not found
64
+ def find_package(package_id)
65
+ return nil if package_id.nil?
66
+
67
+ database.find_package(package_id)
68
+ end
69
+
70
+ # Load diagram objects for a diagram
71
+ # @param diagram_id [Integer] Diagram ID
72
+ # @return [Array<Lutaml::Uml::DiagramObject>] UML diagram objects
73
+ def load_diagram_objects(diagram_id)
74
+ return [] if diagram_id.nil?
75
+
76
+ ea_objects = database.diagram_objects_for(diagram_id)
77
+ ea_objects.filter_map do |ea_obj|
78
+ transform_diagram_object(ea_obj)
79
+ end
80
+ end
81
+
82
+ # Transform EA diagram object to UML diagram object
83
+ # @param ea_obj [Models::EaDiagramObject] EA diagram object
84
+ # @return [Lutaml::Uml::DiagramObject] UML diagram object
85
+ def transform_diagram_object(ea_obj) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
86
+ return nil if ea_obj.nil?
87
+
88
+ Lutaml::Uml::DiagramObject.new.tap do |obj|
89
+ obj.diagram_object_id = ea_obj.ea_object_id.to_s
90
+ obj.left = ea_obj.rectleft
91
+ obj.top = ea_obj.recttop
92
+ obj.right = ea_obj.rectright
93
+ obj.bottom = ea_obj.rectbottom
94
+ obj.sequence = ea_obj.sequence
95
+ obj.style = ea_obj.objectstyle
96
+
97
+ # Try to find and set xmi_id from the referenced object
98
+ if ea_obj.ea_object_id
99
+ uml_object = find_object_by_id(ea_obj.ea_object_id)
100
+ if uml_object
101
+ obj.object_xmi_id = normalize_guid_to_xmi_format(
102
+ uml_object.ea_guid, "EAID"
103
+ )
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ # Load diagram links for a diagram
110
+ # @param diagram_id [Integer] Diagram ID
111
+ # @return [Array<Lutaml::Uml::DiagramLink>] UML diagram links
112
+ def load_diagram_links(diagram_id)
113
+ return [] if diagram_id.nil?
114
+
115
+ ea_links = database.diagram_links_for(diagram_id)
116
+ ea_links.filter_map do |ea_link|
117
+ transform_diagram_link(ea_link)
118
+ end
119
+ end
120
+
121
+ # Transform EA diagram link to UML diagram link
122
+ # @param ea_link [Models::EaDiagramLink] EA diagram link
123
+ # @return [Lutaml::Uml::DiagramLink] UML diagram link
124
+ def transform_diagram_link(ea_link) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
125
+ return nil if ea_link.nil?
126
+
127
+ Lutaml::Uml::DiagramLink.new.tap do |link|
128
+ link.connector_id = ea_link.connectorid.to_s
129
+ link.geometry = ea_link.geometry
130
+ link.style = ea_link.style
131
+ link.hidden = ea_link.hidden?
132
+ link.path = ea_link.path
133
+
134
+ # Try to find and set xmi_id from the referenced connector
135
+ if ea_link.connectorid
136
+ connector = find_connector_by_id(ea_link.connectorid)
137
+ if connector
138
+ link.connector_xmi_id = normalize_guid_to_xmi_format(
139
+ connector.ea_guid, "EAID"
140
+ )
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ # Find connector by ID
147
+ # @param connector_id [Integer] Connector ID
148
+ # @return [Models::EaConnector, nil] EA connector or nil if not found
149
+ def find_connector_by_id(connector_id)
150
+ return nil if connector_id.nil?
151
+
152
+ database.find_connector(connector_id)
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end