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,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Factory
6
+ class GeneralizationBuilder < BaseTransformer
7
+ def load_generalization(object_id, visited = Set.new, is_leaf = true) # rubocop:disable Style/OptionalBooleanParameter
8
+ return nil if object_id.nil?
9
+ return nil if circular_inheritance?(object_id, visited)
10
+
11
+ visited = visited.dup.add(object_id)
12
+
13
+ current_obj = find_object_by_id(object_id)
14
+ return nil unless current_obj
15
+
16
+ generalization = build_generalization(object_id, current_obj)
17
+ return nil unless generalization
18
+
19
+ populate_generalization_attrs(generalization, object_id)
20
+ populate_parent_generalization(generalization,
21
+ ea_connector_for(object_id), visited)
22
+
23
+ collect_inherited_properties(generalization) if is_leaf && generalization.has_general
24
+
25
+ generalization
26
+ end
27
+
28
+ def circular_inheritance?(object_id, visited)
29
+ return false unless visited.include?(object_id)
30
+
31
+ warn "Circular inheritance detected for object_id #{object_id}, " \
32
+ "stopping recursion"
33
+ true
34
+ end
35
+
36
+ def load_association_generalizations(object_id)
37
+ return [] if object_id.nil?
38
+
39
+ gen_connectors = database.connectors_for_object(object_id)
40
+ .select { |c| c.generalization? && c.start_object_id == object_id }
41
+
42
+ gen_connectors.filter_map do |ea_connector|
43
+ build_assoc_generalization(ea_connector)
44
+ end
45
+ end
46
+
47
+ def convert_to_general_attributes(attributes)
48
+ attributes.map { |attr| to_general_attribute(attr) }
49
+ end
50
+
51
+ def convert_to_top_element_attributes(attributes)
52
+ attributes.map { |attr| to_top_element_attribute(attr) }
53
+ end
54
+
55
+ def to_general_attribute(attr)
56
+ base = base_attr_hash(attr)
57
+ Lutaml::Uml::GeneralAttribute.new.tap do |gen_attr|
58
+ base.each { |k, v| gen_attr.public_send(:"#{k}=", v) }
59
+ gen_attr.is_derived = !!attr.is_derived
60
+ gen_attr.has_association = !!attr.association
61
+ end
62
+ end
63
+
64
+ def to_top_element_attribute(attr)
65
+ base = base_attr_hash(attr)
66
+ Lutaml::Uml::TopElementAttribute.new.tap do |top_attr|
67
+ base.each { |k, v| top_attr.public_send(:"#{k}=", v) }
68
+ top_attr.is_derived = !!attr.is_derived
69
+ end
70
+ end
71
+
72
+ def base_attr_hash(attr)
73
+ {
74
+ id: attr.id,
75
+ name: attr.name,
76
+ type: attr.type,
77
+ xmi_id: attr.xmi_id,
78
+ cardinality: attr.cardinality,
79
+ definition: attr.definition&.strip,
80
+ association: attr.association,
81
+ type_ns: attr.type_ns,
82
+ }
83
+ end
84
+
85
+ private
86
+
87
+ def tag_ancestor_attributes(gen, level)
88
+ [gen.general_attributes, gen.attributes].each do |attr_list|
89
+ attr_list&.each do |attr|
90
+ attr.upper_klass = gen.general_upper_klass
91
+ attr.level = level
92
+ end
93
+ end
94
+ end
95
+
96
+ def collect_ancestor_attrs(gen, level, inherited_props,
97
+ inherited_assoc_props)
98
+ gen.attributes.reverse_each do |attr|
99
+ inherited_attr = attr.dup
100
+ inherited_attr.upper_klass = gen.general_upper_klass
101
+ inherited_attr.gen_name = gen.general_name
102
+ inherited_attr.level = level
103
+
104
+ if attr.has_association
105
+ inherited_assoc_props << inherited_attr
106
+ else
107
+ inherited_props << inherited_attr
108
+ end
109
+ end
110
+ end
111
+
112
+ def build_assoc_generalization(ea_connector)
113
+ parent_obj = find_object_by_id(ea_connector.end_object_id)
114
+ return nil unless parent_obj
115
+
116
+ Lutaml::Uml::AssociationGeneralization.new.tap do |ag|
117
+ ag.id = normalize_guid_to_xmi_format(ea_connector.ea_guid, "EAID")
118
+ ag.type = "uml:Generalization"
119
+ ag.general = normalize_guid_to_xmi_format(parent_obj.ea_guid,
120
+ "EAID")
121
+ end
122
+ end
123
+
124
+ def resolve_name_ns(type_ns, upper_klass)
125
+ ns = case type_ns
126
+ when "core", "gml"
127
+ upper_klass
128
+ else
129
+ type_ns
130
+ end
131
+ ns || upper_klass
132
+ end
133
+
134
+ def build_generalization(object_id, current_obj)
135
+ ea_connector = ea_connector_for(object_id)
136
+ gen_transformer = GeneralizationTransformer.new(database)
137
+ if ea_connector.nil?
138
+ gen_transformer.transform(nil, current_obj)
139
+ else
140
+ gen_transformer.transform(ea_connector, current_obj)
141
+ end
142
+ end
143
+
144
+ def ea_connector_for(object_id)
145
+ database.connectors_for_object(object_id)
146
+ .find { |c| c.generalization? && c.start_object_id == object_id }
147
+ end
148
+
149
+ def populate_generalization_attrs(generalization, object_id)
150
+ general_attrs = build_general_attrs(object_id)
151
+ apply_namespace_to_attrs(general_attrs, generalization)
152
+
153
+ generalization.general_attributes = general_attrs
154
+ .sort_by { |a| [a.name.to_s, a.id] }
155
+
156
+ generalization.attributes = transform_general_attributes(
157
+ generalization,
158
+ )
159
+
160
+ generalization.owned_props = generalization.attributes
161
+ .reject(&:has_association)
162
+ generalization.assoc_props = generalization.attributes
163
+ .select(&:has_association)
164
+ end
165
+
166
+ def build_general_attrs(object_id)
167
+ current_attrs = load_attributes(object_id)
168
+ current_assoc_attrs = AssociationBuilder.new(database)
169
+ .load_association_attributes(object_id)
170
+ convert_to_general_attributes(current_attrs + current_assoc_attrs)
171
+ end
172
+
173
+ def apply_namespace_to_attrs(general_attrs, generalization)
174
+ upper_klass = generalization.general_upper_klass
175
+ general_attrs.each do |attr|
176
+ attr.gen_name = generalization.general_name
177
+ attr.name_ns = resolve_name_ns(attr.type_ns, upper_klass)
178
+ end
179
+ end
180
+
181
+ def populate_parent_generalization(generalization, ea_connector,
182
+ visited)
183
+ parent_object_id = ea_connector&.end_object_id
184
+ return unless parent_object_id
185
+
186
+ parent_gen = load_generalization(parent_object_id, visited, false)
187
+ return unless parent_gen
188
+
189
+ generalization.general = parent_gen
190
+ generalization.has_general = true
191
+ end
192
+
193
+ def transform_general_attributes(generalization)
194
+ upper_klass = generalization.general_upper_klass
195
+ gen_name = generalization.general_name
196
+
197
+ generalization.general_attributes.map do |attr|
198
+ transformed = attr.dup
199
+ transformed.name_ns = resolve_name_ns(attr.type_ns, upper_klass)
200
+ transformed.gen_name = gen_name
201
+ transformed.name = "" if transformed.name.nil?
202
+ transformed
203
+ end
204
+ end
205
+
206
+ def collect_inherited_properties(generalization)
207
+ inherited_props = []
208
+ inherited_assoc_props = []
209
+ level = 0
210
+
211
+ current_gen = generalization.general
212
+ while current_gen
213
+ tag_ancestor_attributes(current_gen, level)
214
+ collect_ancestor_attrs(current_gen, level, inherited_props,
215
+ inherited_assoc_props)
216
+
217
+ level += 1
218
+ current_gen = current_gen.general
219
+ end
220
+
221
+ generalization.inherited_props = inherited_props.reverse
222
+ generalization.inherited_assoc_props = inherited_assoc_props.reverse
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Factory
6
+ # Transforms EA connectors (Generalization type) to UML generalizations
7
+ class GeneralizationTransformer < BaseTransformer
8
+ # Transform EA connector to UML generalization
9
+ # @param ea_connector [EaConnector, nil]
10
+ # EA connector model (nil for terminal nodes)
11
+ # @param current_object [EaObject]
12
+ # Current object that owns this generalization
13
+ # @return [Lutaml::Uml::Generalization] UML generalization
14
+ def transform(ea_connector, current_object) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
15
+ return nil if current_object.nil?
16
+
17
+ # ea_connector can be nil for terminal nodes (classes with no parent)
18
+ if ea_connector && !ea_connector.generalization?
19
+ return nil
20
+ end
21
+
22
+ Lutaml::Uml::Generalization.new.tap do |gen| # rubocop:disable Metrics/BlockLength
23
+ # Map properties from CURRENT object (not parent)
24
+ # This matches XMI's self-referential pattern
25
+ gen.general_id = normalize_guid_to_xmi_format(
26
+ current_object.ea_guid, "EAID"
27
+ )
28
+ gen.general_name = current_object.name
29
+ gen.name = current_object.name
30
+ gen.type = "uml:Generalization"
31
+
32
+ # Map definition from ea_connector notes
33
+ if !ea_connector&.notes.nil? && !ea_connector&.notes&.empty?
34
+ gen.definition = normalize_line_endings(ea_connector.notes)
35
+ end
36
+
37
+ # Map stereotype from current object
38
+ gen.stereotype = [current_object.stereotype] unless
39
+ current_object.stereotype.nil? || current_object.stereotype.empty?
40
+
41
+ # Find the package/upper class for the current object
42
+ if current_object.package_id
43
+ current_package = find_package(current_object.package_id)
44
+ if current_package
45
+ gen.general_upper_klass =
46
+ extract_package_prefix(current_package)
47
+ end
48
+ end
49
+
50
+ # Set has_general flag based on whether parent exists
51
+ # Use false (not nil) for terminal nodes to match XMI behavior
52
+ gen.has_general = if ea_connector
53
+ !ea_connector.end_object_id.nil?
54
+ else
55
+ false
56
+ end
57
+
58
+ # Note: general_attributes, attributes, owned_props, assoc_props,
59
+ # general, inherited_props, inherited_assoc_props
60
+ # will be populated in ClassTransformer.load_generalization
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ # Find object by ID
67
+ # @param object_id [Integer] Object ID
68
+ # @return [EaObject, nil] EA object or nil if not found
69
+ def find_object(object_id)
70
+ return nil if object_id.nil?
71
+
72
+ database.find_object(object_id)
73
+ end
74
+
75
+ # Find package by ID
76
+ # @param package_id [Integer] Package ID
77
+ # @return [EaPackage, nil] EA package or nil if not found
78
+ def find_package(package_id)
79
+ return nil if package_id.nil?
80
+
81
+ database.find_package(package_id)
82
+ end
83
+
84
+ # Extract package prefix from package
85
+ # @param package [EaPackage] EA package
86
+ # @return [String, nil] Package prefix or nil
87
+ def extract_package_prefix(package)
88
+ return nil unless package
89
+
90
+ # Try to extract a meaningful prefix from package name
91
+ # Common patterns: "ModelRoot::i-UR::urf" -> "urf"
92
+ parts = package.name&.split("::")
93
+ parts&.last
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Factory
6
+ # Transforms EA objects (Object type) to UML instances
7
+ class InstanceTransformer < BaseTransformer
8
+ # Transform EA object to UML instance
9
+ # @param ea_object [EaObject] EA object model
10
+ # @return [Lutaml::Uml::Instance] UML instance
11
+ def transform(ea_object) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
12
+ return nil if ea_object.nil?
13
+ return nil unless ea_object.instance?
14
+
15
+ Lutaml::Uml::Instance.new.tap do |instance|
16
+ # Map basic properties
17
+ instance.name = ea_object.name
18
+ instance.xmi_id = normalize_guid_to_xmi_format(ea_object.ea_guid,
19
+ "EAID")
20
+
21
+ # Map classifier (the class this is an instance of)
22
+ if ea_object.classifier&.positive?
23
+ classifier_obj = find_classifier(ea_object.classifier)
24
+ if classifier_obj
25
+ instance.classifier = classifier_obj.name
26
+ end
27
+ elsif ea_object.classifier_guid && !ea_object.classifier_guid.empty?
28
+ classifier_obj = find_classifier_by_guid(
29
+ ea_object.classifier_guid,
30
+ )
31
+ if classifier_obj
32
+ instance.classifier = classifier_obj.name
33
+ end
34
+ end
35
+
36
+ # Map definition/notes
37
+ instance.definition = ea_object.note unless
38
+ ea_object.note.nil? || ea_object.note.empty?
39
+
40
+ # Load and transform tagged values
41
+ instance.tagged_values = load_tagged_values(ea_object.ea_guid)
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ # Find classifier object by ID
48
+ # @param classifier_id [Integer] Classifier object ID
49
+ # @return [EaObject, nil] EA object or nil if not found
50
+ def find_classifier(classifier_id)
51
+ return nil if classifier_id.nil? || classifier_id.zero?
52
+
53
+ database.find_object(classifier_id)
54
+ end
55
+
56
+ # Find classifier object by GUID
57
+ # @param classifier_guid [String] Classifier GUID
58
+ # @return [EaObject, nil] EA object or nil if not found
59
+ def find_classifier_by_guid(classifier_guid)
60
+ return nil if classifier_guid.nil? || classifier_guid.empty?
61
+
62
+ database.find_object_by_guid(classifier_guid)
63
+ end
64
+
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Factory
6
+ # Transforms EA ObjectProperty to UML Property/TaggedValue
7
+ #
8
+ # This transformer converts Enterprise Architect object properties
9
+ # (GML/XML Schema encoding metadata) to UML elements. Object properties
10
+ # are treated as specialized tagged values that enhance UML classes
11
+ # with schema-specific metadata.
12
+ #
13
+ # @example Transform an object property
14
+ # ea_prop = Models::EaObjectProperty.new(
15
+ # property_id: 1,
16
+ # object_id: 684,
17
+ # property: "isCollection",
18
+ # value: "false"
19
+ # )
20
+ # transformer = ObjectPropertyTransformer.new
21
+ # uml_tag = transformer.transform(ea_prop)
22
+ class ObjectPropertyTransformer < BaseTransformer
23
+ # Transform EA object property to UML TaggedValue
24
+ #
25
+ # Object properties enhance UML elements with GML/XML encoding
26
+ # metadata. They are transformed into tagged values to preserve
27
+ # this semantic information.
28
+ #
29
+ # @param ea_property [Models::EaObjectProperty] EA object property
30
+ # @return [Lutaml::Uml::TaggedValue, nil] UML tagged value or nil
31
+ def transform(ea_property)
32
+ return nil unless ea_property
33
+ return nil unless ea_property.property
34
+
35
+ Lutaml::Uml::TaggedValue.new.tap do |tag|
36
+ tag.name = ea_property.property
37
+ tag.value = ea_property.value || ""
38
+ tag.notes = format_notes(ea_property)
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ # Format notes from EA property
45
+ #
46
+ # @param ea_property [Models::EaObjectProperty] EA property
47
+ # @return [String, nil] Formatted notes
48
+ def format_notes(ea_property)
49
+ return nil unless ea_property.notes
50
+
51
+ # Clean up EA's note format
52
+ notes = ea_property.notes.strip
53
+ notes.empty? ? nil : notes
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Factory
6
+ # Transforms EA operations to UML operations
7
+ class OperationTransformer < BaseTransformer
8
+ # Transform EA operation to UML operation
9
+ # @param ea_operation [EaOperation] EA operation model
10
+ # @return [Lutaml::Uml::Operation] UML operation
11
+ def transform(ea_operation) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
12
+ return nil if ea_operation.nil?
13
+
14
+ Lutaml::Uml::Operation.new.tap do |op|
15
+ op.name = ea_operation.name
16
+ op.return_type = ea_operation.type
17
+ op.visibility = map_visibility(ea_operation.scope)
18
+ op.xmi_id = ea_operation.ea_guid
19
+
20
+ # Build parameter type string from operation parameters
21
+ op.parameter_type = build_parameter_type(ea_operation)
22
+
23
+ # Map definition/notes
24
+ op.definition = ea_operation.notes unless
25
+ ea_operation.notes.nil? || ea_operation.notes.empty?
26
+
27
+ # Map stereotype if present
28
+ if ea_operation.stereotype && !ea_operation.stereotype.empty?
29
+ op.stereotype = [ea_operation.stereotype]
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ # Build parameter type string from operation parameters
37
+ # @param ea_operation [EaOperation] EA operation
38
+ # @return [String, nil] Parameter type string
39
+ def build_parameter_type(ea_operation)
40
+ # Load parameters for this operation
41
+ params = load_parameters(ea_operation.operationid)
42
+ return nil if params.empty?
43
+
44
+ # Filter out return parameters and build parameter string
45
+ input_params = params.reject(&:return?)
46
+ return nil if input_params.empty?
47
+
48
+ input_params.map do |param|
49
+ type_str = param.type || "void"
50
+ "#{param.name}: #{type_str}"
51
+ end.join(", ")
52
+ end
53
+
54
+ # Load parameters for an operation
55
+ # @param operation_id [Integer] Operation ID
56
+ # @return [Array<EaOperationParam>] Operation parameters
57
+ def load_parameters(operation_id)
58
+ return [] if operation_id.nil?
59
+
60
+ database.operation_params_for(operation_id)
61
+ .sort_by { |p| p.pos || 0 }
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Factory
6
+ # Transforms EA packages to UML packages
7
+ class PackageTransformer < BaseTransformer
8
+ # Transform EA package to UML package
9
+ # @param ea_package [EaPackage] EA package model
10
+ # @return [Lutaml::Uml::Package] UML package
11
+ def transform(ea_package) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
12
+ return nil if ea_package.nil?
13
+
14
+ Lutaml::Uml::Package.new.tap do |pkg|
15
+ # Map basic properties
16
+ pkg.name = ea_package.name
17
+ pkg.xmi_id = normalize_guid_to_xmi_format(ea_package.ea_guid,
18
+ "EAPK")
19
+
20
+ # Map definition/notes
21
+ pkg.definition = ea_package.notes unless
22
+ ea_package.notes.nil? || ea_package.notes.empty?
23
+
24
+ # Load and transform tagged values
25
+ pkg.tagged_values = load_tagged_values(ea_package.ea_guid)
26
+
27
+ # Load stereotype from t_xref
28
+ stereotype = load_stereotype(ea_package.ea_guid)
29
+ pkg.stereotype = [stereotype] if stereotype
30
+
31
+ # Note: Child packages and contents will be loaded separately
32
+ # to avoid circular dependencies and allow lazy loading
33
+ # Don't initialize collections - they have default values
34
+ end
35
+ end
36
+
37
+ # Transform and build complete package hierarchy
38
+ # @param ea_package [EaPackage] Root EA package
39
+ # @param include_children [Boolean] Whether to recursively load children
40
+ # @return [Lutaml::Uml::Package] Complete UML package with hierarchy
41
+ def transform_with_hierarchy(ea_package, include_children: true)
42
+ pkg = transform(ea_package)
43
+ return pkg unless include_children
44
+
45
+ # Load child packages
46
+ child_packages = load_child_packages(ea_package.package_id)
47
+ pkg.packages = child_packages.map do |child_pkg|
48
+ transform_with_hierarchy(child_pkg, include_children: true)
49
+ end
50
+
51
+ # Load package contents (classes, diagrams, etc.)
52
+ load_package_contents(pkg, ea_package.package_id)
53
+
54
+ pkg
55
+ end
56
+
57
+ private
58
+
59
+ # Load child packages
60
+ # @param parent_id [Integer] Parent package ID
61
+ # @return [Array<EaPackage>] Child packages
62
+ def load_child_packages(parent_id)
63
+ return [] if parent_id.nil?
64
+
65
+ database.child_packages_for(parent_id)
66
+ .sort_by { |p| p.tpos || 0 }
67
+ end
68
+
69
+ # Load package contents (objects and diagrams)
70
+ # @param pkg [Lutaml::Uml::Package] UML package to populate
71
+ # @param package_id [Integer] EA package ID
72
+ def load_package_contents(pkg, package_id)
73
+ return if package_id.nil?
74
+
75
+ # Load objects (classes, etc.) in this package
76
+ load_package_objects(pkg, package_id)
77
+
78
+ # Load diagrams in this package
79
+ load_package_diagrams(pkg, package_id)
80
+ end
81
+
82
+ # Maps transformer type keys to UML Package collection methods
83
+ COLLECTION_FOR_TYPE = {
84
+ class: :classes,
85
+ enumeration: :enums,
86
+ data_type: :data_types,
87
+ instance: :instances,
88
+ }.freeze
89
+
90
+ # Load objects for a package
91
+ # @param pkg [Lutaml::Uml::Package] UML package
92
+ # @param package_id [Integer] Package ID
93
+ def load_package_objects(pkg, package_id)
94
+ ea_objects = database.objects_in_package(package_id)
95
+
96
+ ea_objects.each do |ea_obj|
97
+ type_key = ea_obj.transformer_type
98
+ next unless type_key && COLLECTION_FOR_TYPE.key?(type_key)
99
+
100
+ transformer_class = TransformerRegistry.transformer_for(type_key)
101
+ next unless transformer_class
102
+
103
+ uml_element = transformer_class.new(database).transform(ea_obj)
104
+ next unless uml_element
105
+
106
+ collection = COLLECTION_FOR_TYPE[type_key]
107
+ pkg.public_send(collection) << uml_element
108
+ end
109
+ end
110
+
111
+ # Load diagrams for a package
112
+ # @param pkg [Lutaml::Uml::Package] UML package
113
+ # @param package_id [Integer] Package ID
114
+ def load_package_diagrams(pkg, package_id)
115
+ diagram_transformer = DiagramTransformer.new(database)
116
+
117
+ ea_diagrams = database.diagrams_in_package(package_id)
118
+ pkg.diagrams = diagram_transformer.transform_collection(ea_diagrams)
119
+ end
120
+
121
+ # Load stereotype from t_xref table
122
+ # @param ea_guid [String] Element GUID
123
+ # @return [String, nil] Stereotype value (as string to match XMI format)
124
+ def load_stereotype(ea_guid)
125
+ return nil if ea_guid.nil?
126
+
127
+ StereotypeLoader.new(database).load_from_xref(ea_guid)
128
+ end
129
+
130
+ # Check if an object appears on any diagram
131
+ # @param object_id [Integer] Object ID
132
+ # @return [Boolean] True if object appears on a diagram
133
+ def appears_on_diagram?(object_id)
134
+ return false if object_id.nil?
135
+ return false unless database.diagram_objects
136
+
137
+ # Check if object appears in any diagram's objects
138
+ database.diagram_objects.any? do |dobj|
139
+ dobj.ea_object_id == object_id
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end