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,308 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ # Database container for all loaded EA models
6
+ #
7
+ # This class provides a unified container for all EA table collections
8
+ # loaded from a QEA database. It stores collections by name and provides
9
+ # accessor methods, statistics, and lookup functionality.
10
+ #
11
+ # @example Load and access database
12
+ # database = Ea::Qea::Services::DatabaseLoader.new("file.qea").load
13
+ # puts database.stats
14
+ # # => {"objects" => 693, "attributes" => 1910, ...}
15
+ #
16
+ # classes = database.objects.find_by_type("Class")
17
+ # obj = database.find_object(123)
18
+ class Database
19
+ # @return [Hash<Symbol, Array>] Collections of records by name
20
+ attr_reader :collections
21
+
22
+ # @return [String] Path to the QEA file
23
+ attr_reader :qea_path
24
+
25
+ def initialize(qea_path, connection = nil)
26
+ @qea_path = qea_path
27
+ @connection = connection
28
+ @collections = {}
29
+ @mutex = Mutex.new
30
+ end
31
+
32
+ # Set database connection
33
+ #
34
+ # @param connection [SQLite3::Database] Database connection
35
+ # @return [void]
36
+ def connection=(connection)
37
+ @connection = connection
38
+ end
39
+
40
+ # Close the database connection if open
41
+ # @return [void]
42
+ def close_connection
43
+ return unless @connection && !@connection.closed?
44
+
45
+ @connection.close
46
+ end
47
+
48
+ # Add a collection to the database
49
+ #
50
+ # @param name [Symbol, String] Collection name (e.g., :objects)
51
+ # @param records [Array] Array of model instances
52
+ # @return [void]
53
+ def add_collection(name, records)
54
+ @mutex.synchronize do
55
+ @collections[name.to_sym] = records.freeze
56
+ end
57
+ end
58
+
59
+ COLLECTION_ACCESSORS = %i[
60
+ attributes operations operation_params connectors packages
61
+ diagrams diagram_objects diagram_links object_constraints
62
+ tagged_values object_properties attribute_tags xrefs
63
+ stereotypes datatypes constraint_types connector_types
64
+ diagram_types object_types status_types complexity_types
65
+ documents scripts
66
+ ].freeze
67
+
68
+ COLLECTION_ACCESSORS.each do |name|
69
+ define_method(name) do
70
+ @collections[name] || []
71
+ end
72
+ end
73
+
74
+ # Get objects collection (special: wrapped in ObjectRepository)
75
+ #
76
+ # @return [Repositories::ObjectRepository] Repository for objects
77
+ def objects
78
+ return @objects if defined?(@objects)
79
+
80
+ @objects = Repositories::ObjectRepository.new(
81
+ @collections[:objects] || [],
82
+ )
83
+ end
84
+
85
+ # Get statistics for all collections
86
+ #
87
+ # @return [Hash<String, Integer>] Record counts by collection name
88
+ #
89
+ # @example
90
+ # database.stats
91
+ # # => {
92
+ # # "objects" => 693,
93
+ # # "attributes" => 1910,
94
+ # # "connectors" => 908,
95
+ # # ...
96
+ # # }
97
+ def stats
98
+ @collections.each_with_object({}) do |(name, records), hash|
99
+ hash[name.to_s] = records.size
100
+ end
101
+ end
102
+
103
+ # Get total number of records across all collections
104
+ #
105
+ # @return [Integer] Total record count
106
+ def total_records
107
+ @collections.values.sum(&:size)
108
+ end
109
+
110
+ def find_package(id)
111
+ ensure_lookup_indexes
112
+ @packages_by_id[id]
113
+ end
114
+
115
+ def find_attribute(id)
116
+ ensure_lookup_indexes
117
+ @attributes_by_id[id]
118
+ end
119
+
120
+ def find_connector(id)
121
+ ensure_lookup_indexes
122
+ @connectors_by_id[id]
123
+ end
124
+
125
+ def find_diagram(id)
126
+ ensure_lookup_indexes
127
+ @diagrams_by_id[id]
128
+ end
129
+
130
+ def attributes_for_object(id)
131
+ ensure_lookup_indexes
132
+ @attributes_by_object_id[id] || []
133
+ end
134
+
135
+ def operations_for_object(id)
136
+ ensure_lookup_indexes
137
+ @operations_by_object_id[id] || []
138
+ end
139
+
140
+ def operation_params_for(id)
141
+ ensure_lookup_indexes
142
+ @operation_params_by_id[id] || []
143
+ end
144
+
145
+ def child_packages_for(id)
146
+ ensure_lookup_indexes
147
+ @packages_by_parent[id] || []
148
+ end
149
+
150
+ def objects_in_package(id)
151
+ ensure_lookup_indexes
152
+ @objects_by_package_id[id] || []
153
+ end
154
+
155
+ def diagrams_in_package(id)
156
+ ensure_lookup_indexes
157
+ @diagrams_by_package_id[id] || []
158
+ end
159
+
160
+ def diagram_objects_for(id)
161
+ ensure_lookup_indexes
162
+ @diagram_objects_by_id[id] || []
163
+ end
164
+
165
+ def diagram_links_for(id)
166
+ ensure_lookup_indexes
167
+ @diagram_links_by_id[id] || []
168
+ end
169
+
170
+ # Find an object by ID
171
+ def find_object(id)
172
+ objects.find_by_key(:ea_object_id, id)
173
+ end
174
+
175
+ # Find object by ea_guid
176
+ def find_object_by_guid(ea_guid)
177
+ ensure_lookup_indexes
178
+ @objects_by_guid[ea_guid]
179
+ end
180
+
181
+ # Get connectors involving a specific object (start or end)
182
+ def connectors_for_object(object_id)
183
+ ensure_lookup_indexes
184
+ @connectors_by_object[object_id] || []
185
+ end
186
+
187
+ def constraints_for_object(object_id)
188
+ ensure_lookup_indexes
189
+ @constraints_by_object_id[object_id] || []
190
+ end
191
+
192
+ def tagged_values_for_element(ea_guid)
193
+ ensure_lookup_indexes
194
+ @tagged_values_by_element_id[ea_guid] || []
195
+ end
196
+
197
+ def properties_for_object(object_id)
198
+ ensure_lookup_indexes
199
+ @properties_by_object_id[object_id] || []
200
+ end
201
+
202
+ def xrefs_for_client(ea_guid)
203
+ ensure_lookup_indexes
204
+ @xrefs_by_client[ea_guid] || []
205
+ end
206
+
207
+ # Check if database is empty
208
+ #
209
+ # @return [Boolean] true if no collections loaded
210
+ def empty?
211
+ @collections.empty? || total_records.zero?
212
+ end
213
+
214
+ # Get collection names
215
+ #
216
+ # @return [Array<Symbol>] Array of collection names
217
+ def collection_names
218
+ @collections.keys
219
+ end
220
+
221
+ # Freeze all collections to make database immutable
222
+ #
223
+ # @return [self]
224
+ def freeze
225
+ objects
226
+ ensure_lookup_indexes
227
+ @collections.freeze
228
+ super
229
+ end
230
+
231
+ private
232
+
233
+ def ensure_lookup_indexes
234
+ return if @lookup_indexes_built
235
+
236
+ build_lookup_indexes
237
+ @lookup_indexes_built = true
238
+ end
239
+
240
+ def build_group_index(collection, method, single: false)
241
+ collection.each_with_object({}) do |item, hash|
242
+ key = item.public_send(method)
243
+ next unless key
244
+
245
+ single ? (hash[key] = item) : ((hash[key] ||= []) << item)
246
+ end
247
+ end
248
+
249
+ def build_lookup_indexes
250
+ build_primary_indexes
251
+ build_secondary_indexes
252
+ end
253
+
254
+ def build_primary_indexes
255
+ build_object_indexes
256
+ build_feature_indexes
257
+ build_connector_indexes
258
+ build_diagram_indexes
259
+ end
260
+
261
+ def build_object_indexes
262
+ @objects_by_guid = build_group_index(objects, :ea_guid, single: true)
263
+ @objects_by_package_id = build_group_index(objects, :package_id)
264
+ @packages_by_parent = build_group_index(packages, :parent_id)
265
+ end
266
+
267
+ def build_feature_indexes
268
+ @attributes_by_object_id = build_group_index(attributes, :ea_object_id)
269
+ @operations_by_object_id = build_group_index(operations, :ea_object_id)
270
+ @operation_params_by_id = build_group_index(operation_params,
271
+ :operationid)
272
+ end
273
+
274
+ def build_connector_indexes
275
+ @connectors_by_start = build_group_index(connectors, :start_object_id)
276
+ @connectors_by_end = build_group_index(connectors, :end_object_id)
277
+ @connectors_by_object = {}
278
+ @connectors_by_start.each do |id, conns|
279
+ (@connectors_by_object[id] ||= []).concat(conns)
280
+ end
281
+ @connectors_by_end.each do |id, conns|
282
+ (@connectors_by_object[id] ||= []).concat(conns)
283
+ end
284
+ end
285
+
286
+ def build_diagram_indexes
287
+ @diagrams_by_package_id = build_group_index(diagrams, :package_id)
288
+ @diagram_objects_by_id = build_group_index(diagram_objects, :diagram_id)
289
+ @diagram_links_by_id = build_group_index(diagram_links, :diagramid)
290
+ end
291
+
292
+ def build_secondary_indexes
293
+ @packages_by_id = build_group_index(packages, :package_id, single: true)
294
+ @connectors_by_id = build_group_index(connectors, :connector_id,
295
+ single: true)
296
+ @diagrams_by_id = build_group_index(diagrams, :diagram_id, single: true)
297
+ @attributes_by_id = build_group_index(attributes, :id, single: true)
298
+ @constraints_by_object_id = build_group_index(object_constraints,
299
+ :ea_object_id)
300
+ @tagged_values_by_element_id = build_group_index(tagged_values,
301
+ :element_id)
302
+ @properties_by_object_id = build_group_index(object_properties,
303
+ :ea_object_id)
304
+ @xrefs_by_client = build_group_index(xrefs, :client)
305
+ end
306
+ end
307
+ end
308
+ end
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Factory
6
+ class AssociationBuilder < BaseTransformer
7
+ ASSOC_TYPES = ["Association", "Aggregation", "Composition"].freeze
8
+
9
+ def load_class_associations(object_id, object_guid)
10
+ return [] if object_id.nil?
11
+
12
+ normalized_owner_xmi_id = normalize_guid_to_xmi_format(object_guid,
13
+ "EAID")
14
+
15
+ assoc_connectors = database.connectors_for_object(object_id)
16
+ .select { |c| ASSOC_TYPES.include?(c.connector_type) }
17
+
18
+ assoc_connectors.filter_map do |ea_connector|
19
+ build_association(ea_connector, object_id, normalized_owner_xmi_id)
20
+ end
21
+ end
22
+
23
+ def load_association_attributes(object_id)
24
+ return [] if object_id.nil?
25
+
26
+ assoc_connectors = database.connectors_for_object(object_id)
27
+ .select { |c| ASSOC_TYPES.include?(c.connector_type) }
28
+ obj = find_object_by_id(object_id)
29
+ obj_pkg_name = find_package_name(obj&.package_id)
30
+
31
+ assoc_connectors.filter_map do |ea_connector|
32
+ build_connector_attribute(ea_connector, object_id, obj,
33
+ obj_pkg_name)
34
+ end
35
+ end
36
+
37
+ def build_connector_attribute(ea_connector, object_id, obj,
38
+ obj_pkg_name)
39
+ if ea_connector.start_object_id == object_id
40
+ build_end_attribute(ea_connector, obj, obj_pkg_name)
41
+ elsif ea_connector.end_object_id == object_id
42
+ build_start_attribute(ea_connector, obj, obj_pkg_name)
43
+ end
44
+ end
45
+
46
+ def create_association_attribute( # rubocop:disable Metrics/ParameterLists
47
+ name:, type:, type_xmi_id:,
48
+ association_xmi_id:, cardinality:, definition:,
49
+ gen_name:, name_ns:, type_ns:, is_src: true
50
+ )
51
+ Lutaml::Uml::GeneralAttribute.new.tap do |attr|
52
+ assign_assoc_attr_basic(attr, name, type, gen_name, definition,
53
+ name_ns, type_ns)
54
+ attr.xmi_id = normalize_guid_to_xmi_format(type_xmi_id, "EAID")
55
+ attr.association = normalize_guid_to_xmi_format(
56
+ association_xmi_id, "EAID"
57
+ )
58
+ attr.has_association = true
59
+ attr.id = normalize_guid_to_xmi_src_dst_format(
60
+ association_xmi_id, "EAID", is_src
61
+ )
62
+ attr.cardinality = build_cardinality(cardinality)
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def assign_assoc_attr_basic(attr, name, type, gen_name,
69
+ definition, name_ns, type_ns)
70
+ attr.name = name
71
+ attr.type = type
72
+ attr.gen_name = gen_name
73
+ attr.definition = definition
74
+ attr.name_ns = name_ns
75
+ attr.type_ns = type_ns
76
+ end
77
+
78
+ def build_association(ea_connector, object_id, normalized_owner_xmi_id)
79
+ is_start = ea_connector.start_object_id == object_id
80
+ owner_role = is_start ? ea_connector.destrole : ea_connector.sourcerole
81
+ return nil if owner_role.nil? || owner_role.empty?
82
+
83
+ member_obj = resolve_member_object(ea_connector, is_start)
84
+ return nil unless member_obj
85
+
86
+ member_role = resolve_member_role(ea_connector, is_start, member_obj)
87
+
88
+ build_association_record(ea_connector, object_id, normalized_owner_xmi_id,
89
+ owner_role, member_obj, member_role, is_start)
90
+ end
91
+
92
+ def build_association_record(ea_connector, object_id, owner_xmi_id,
93
+ owner_role, member_obj, member_role, is_start)
94
+ cardinality_str = is_start ? ea_connector.destcard : ea_connector.sourcecard
95
+
96
+ Lutaml::Uml::Association.new.tap do |assoc|
97
+ assoc.xmi_id = normalize_guid_to_xmi_format(ea_connector.ea_guid,
98
+ "EAID")
99
+ assign_assoc_name(assoc, ea_connector)
100
+ assign_association_ends(assoc, object_id, owner_xmi_id,
101
+ owner_role, member_obj, member_role)
102
+ assoc.member_end_type = ea_connector.connector_type&.downcase
103
+ assoc.member_end_cardinality = build_cardinality(cardinality_str)
104
+ end
105
+ end
106
+
107
+ def assign_assoc_name(assoc, ea_connector)
108
+ return if ea_connector.name.nil? || ea_connector.name.empty?
109
+
110
+ assoc.name = ea_connector.name
111
+ end
112
+
113
+ def assign_association_ends(assoc, object_id, owner_xmi_id,
114
+ owner_role, member_obj, member_role)
115
+ assoc.owner_end = find_object_by_id(object_id)&.name
116
+ assoc.owner_end_xmi_id = owner_xmi_id
117
+ assoc.owner_end_attribute_name = owner_role
118
+ assoc.member_end = member_obj.name
119
+ assoc.member_end_xmi_id = normalize_guid_to_xmi_format(
120
+ member_obj.ea_guid, "EAID"
121
+ )
122
+ assoc.member_end_attribute_name = member_role
123
+ end
124
+
125
+ def resolve_member_object(ea_connector, is_start)
126
+ peer_id = is_start ? ea_connector.end_object_id : ea_connector.start_object_id
127
+ find_object_by_id(peer_id)
128
+ end
129
+
130
+ def resolve_member_role(ea_connector, is_start, member_obj)
131
+ role = is_start ? ea_connector.sourcerole : ea_connector.destrole
132
+ role.nil? || role.empty? ? member_obj.name : role
133
+ end
134
+
135
+ def build_end_attribute(ea_connector, obj, obj_pkg_name)
136
+ return nil if ea_connector.destrole.nil? || ea_connector.destrole.empty?
137
+
138
+ target_obj = find_object_by_id(ea_connector.end_object_id)
139
+ return nil unless target_obj
140
+
141
+ build_directional_attribute(
142
+ role: ea_connector.destrole,
143
+ peer_obj: target_obj,
144
+ ea_connector: ea_connector,
145
+ cardinality: ea_connector.destcard,
146
+ obj: obj, obj_pkg_name: obj_pkg_name,
147
+ is_src: false
148
+ )
149
+ end
150
+
151
+ def build_start_attribute(ea_connector, obj, obj_pkg_name)
152
+ return nil if ea_connector.sourcerole.nil? || ea_connector.sourcerole.empty?
153
+
154
+ source_obj = find_object_by_id(ea_connector.start_object_id)
155
+ return nil unless source_obj
156
+
157
+ build_directional_attribute(
158
+ role: ea_connector.sourcerole,
159
+ peer_obj: source_obj,
160
+ ea_connector: ea_connector,
161
+ cardinality: ea_connector.sourcecard,
162
+ obj: obj, obj_pkg_name: obj_pkg_name
163
+ )
164
+ end
165
+
166
+ def build_directional_attribute(role:, peer_obj:, ea_connector:,
167
+ cardinality:, obj:, obj_pkg_name:,
168
+ is_src: true)
169
+ create_association_attribute(
170
+ name: role,
171
+ type: peer_obj.name,
172
+ type_xmi_id: peer_obj.ea_guid,
173
+ association_xmi_id: ea_connector.ea_guid,
174
+ cardinality: cardinality,
175
+ definition: ea_connector.notes,
176
+ gen_name: obj.name,
177
+ name_ns: obj_pkg_name,
178
+ type_ns: find_package_name(peer_obj.package_id),
179
+ is_src: is_src,
180
+ )
181
+ end
182
+
183
+ def build_cardinality(cardinality_str)
184
+ return nil unless cardinality_str && !cardinality_str.empty?
185
+
186
+ parsed = parse_cardinality(cardinality_str)
187
+ return nil unless parsed[:min] || parsed[:max]
188
+
189
+ Lutaml::Uml::Cardinality.new.tap do |card|
190
+ card.min = parsed[:min]
191
+ card.max = parsed[:max]
192
+ end
193
+ end
194
+
195
+ def find_package_name(package_id)
196
+ return nil if package_id.nil?
197
+
198
+ database.find_package(package_id)&.name
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Factory
6
+ # Transforms EA connectors (Association type) to UML associations
7
+ class AssociationTransformer < BaseTransformer
8
+ # Transform EA connector to UML association
9
+ # @param ea_connector [EaConnector] EA connector model
10
+ # @return [Lutaml::Uml::Association] UML association
11
+ def transform(ea_connector) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
12
+ return nil if ea_connector.nil?
13
+ return nil unless ea_connector.association?
14
+
15
+ Lutaml::Uml::Association.new.tap do |assoc| # rubocop:disable Metrics/BlockLength
16
+ # Map basic properties
17
+ assoc.name = ea_connector.name unless
18
+ ea_connector.name.nil? || ea_connector.name.empty?
19
+ assoc.xmi_id = normalize_guid_to_xmi_format(ea_connector.ea_guid,
20
+ "EAID")
21
+
22
+ # Map source (owner) end
23
+ source_obj = find_object(ea_connector.start_object_id)
24
+ if source_obj
25
+ assoc.owner_end = source_obj.name
26
+ assoc.owner_end_xmi_id = normalize_guid_to_xmi_format(
27
+ source_obj.ea_guid, "EAID"
28
+ )
29
+ assoc.owner_end_attribute_name = ea_connector.sourcerole
30
+ assoc.owner_end_cardinality = build_cardinality_from_string(
31
+ ea_connector.sourcecard,
32
+ )
33
+ end
34
+
35
+ # Map target (member) end
36
+ target_obj = find_object(ea_connector.end_object_id)
37
+ if target_obj
38
+ assoc.member_end = target_obj.name
39
+ assoc.member_end_xmi_id = normalize_guid_to_xmi_format(
40
+ target_obj.ea_guid, "EAID"
41
+ )
42
+ assoc.member_end_attribute_name = ea_connector.destrole
43
+ assoc.member_end_cardinality = build_cardinality_from_string(
44
+ ea_connector.destcard,
45
+ )
46
+ end
47
+
48
+ # Map definition/notes
49
+ assoc.definition = ea_connector.notes unless
50
+ ea_connector.notes.nil? || ea_connector.notes.empty?
51
+
52
+ # Map stereotype
53
+ if ea_connector.stereotype && !ea_connector.stereotype.empty?
54
+ assoc.stereotype = [ea_connector.stereotype]
55
+ end
56
+
57
+ # Load and transform tagged values
58
+ assoc.tagged_values = load_tagged_values(ea_connector.ea_guid)
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ # Find object by ID
65
+ # @param object_id [Integer] Object ID
66
+ # @return [EaObject, nil] EA object or nil if not found
67
+ def find_object(object_id)
68
+ return nil if object_id.nil?
69
+
70
+ database.find_object(object_id)
71
+ end
72
+
73
+ # Build cardinality from string
74
+ # @param cardinality_str [String] Cardinality string
75
+ # @return [Lutaml::Uml::Cardinality, nil] Cardinality object
76
+ def build_cardinality_from_string(cardinality_str)
77
+ return nil if cardinality_str.nil? || cardinality_str.empty?
78
+
79
+ parsed = parse_cardinality(cardinality_str)
80
+ return nil if parsed[:min].nil? && parsed[:max].nil?
81
+
82
+ Lutaml::Uml::Cardinality.new.tap do |card|
83
+ card.min = parsed[:min]
84
+ card.max = parsed[:max]
85
+ end
86
+ end
87
+
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Factory
6
+ # Transforms EA AttributeTag to UML TaggedValue
7
+ #
8
+ # This transformer converts Enterprise Architect attribute tags
9
+ # (GML/XML Schema encoding metadata for attributes) to UML
10
+ # TaggedValue objects.
11
+ #
12
+ # @example Transform an attribute tag
13
+ # ea_tag = Models::EaAttributeTag.new(
14
+ # property_id: 1,
15
+ # element_id: 367,
16
+ # property: "isMetadata",
17
+ # value: "false"
18
+ # )
19
+ # transformer = AttributeTagTransformer.new
20
+ # uml_tag = transformer.transform(ea_tag)
21
+ class AttributeTagTransformer < BaseTransformer
22
+ # Transform EA attribute tag to UML TaggedValue
23
+ #
24
+ # Attribute tags enhance UML attributes with GML/XML encoding
25
+ # metadata. They are transformed into tagged values to preserve
26
+ # this semantic information.
27
+ #
28
+ # @param ea_tag [Models::EaAttributeTag] EA attribute tag
29
+ # @return [Lutaml::Uml::TaggedValue, nil] UML tagged value or nil
30
+ def transform(ea_tag)
31
+ return nil unless ea_tag
32
+ return nil unless ea_tag.property
33
+
34
+ Lutaml::Uml::TaggedValue.new.tap do |tag|
35
+ tag.name = ea_tag.property
36
+ tag.value = ea_tag.value || ""
37
+ tag.notes = format_notes(ea_tag)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ # Format notes from EA attribute tag
44
+ #
45
+ # @param ea_tag [Models::EaAttributeTag] EA tag
46
+ # @return [String, nil] Formatted notes
47
+ def format_notes(ea_tag)
48
+ return nil unless ea_tag.notes
49
+
50
+ # Clean up EA's note format
51
+ notes = ea_tag.notes.strip
52
+ notes.empty? ? nil : notes
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end