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,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Factory
6
+ # Resolves references between EA and UML elements
7
+ # Maps EA GUIDs to UML xmi_ids and maintains object relationships
8
+ class ReferenceResolver
9
+ # Initialize empty resolver
10
+ def initialize
11
+ @guid_to_element = {}
12
+ @object_id_to_name = {}
13
+ end
14
+
15
+ # Register EA GUID to UML element mapping
16
+ # @param ea_guid [String] EA GUID
17
+ # @param uml_element [Object] UML element with xmi_id
18
+ # @return [void]
19
+ def register(ea_guid, uml_element)
20
+ return if ea_guid.nil? || uml_element.nil?
21
+
22
+ @guid_to_element[normalize_guid(ea_guid)] = uml_element
23
+ end
24
+
25
+ # Register object ID to name mapping
26
+ # @param object_id [Integer] EA object ID
27
+ # @param name [String] Object name
28
+ # @return [void]
29
+ def register_object_name(object_id, name)
30
+ return if object_id.nil?
31
+
32
+ @object_id_to_name[object_id] = name
33
+ end
34
+
35
+ # Resolve EA GUID to UML element
36
+ # @param ea_guid [String] EA GUID
37
+ # @return [Object, nil] UML element or nil if not found
38
+ def resolve(ea_guid)
39
+ return nil if ea_guid.nil?
40
+
41
+ @guid_to_element[normalize_guid(ea_guid)]
42
+ end
43
+
44
+ # Get object name by object ID
45
+ # @param object_id [Integer] EA object ID
46
+ # @return [String, nil] Object name or nil if not found
47
+ def resolve_object_name(object_id)
48
+ return nil if object_id.nil?
49
+
50
+ @object_id_to_name[object_id]
51
+ end
52
+
53
+ # Get UML xmi_id by EA GUID
54
+ # @param ea_guid [String] EA GUID
55
+ # @return [String, nil] xmi_id or nil if not found
56
+ def resolve_xmi_id(ea_guid)
57
+ element = resolve(ea_guid)
58
+ element&.xmi_id
59
+ end
60
+
61
+ # Check if GUID is registered
62
+ # @param ea_guid [String] EA GUID
63
+ # @return [Boolean] True if registered
64
+ def registered?(ea_guid)
65
+ return false if ea_guid.nil?
66
+
67
+ @guid_to_element.key?(normalize_guid(ea_guid))
68
+ end
69
+
70
+ # Clear all mappings
71
+ # @return [void]
72
+ def clear
73
+ @guid_to_element.clear
74
+ @object_id_to_name.clear
75
+ end
76
+
77
+ # Get statistics
78
+ # @return [Hash] Statistics about registered elements
79
+ def stats
80
+ {
81
+ total_elements: @guid_to_element.size,
82
+ total_objects: @object_id_to_name.size,
83
+ }
84
+ end
85
+
86
+ private
87
+
88
+ # Normalize GUID format (remove braces, upcase)
89
+ # @param guid [String] GUID string
90
+ # @return [String] Normalized GUID
91
+ def normalize_guid(guid)
92
+ return guid if guid.nil?
93
+
94
+ guid.to_s.gsub(/[{}]/, "").upcase
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Factory
6
+ class StereotypeLoader
7
+ def initialize(database)
8
+ @database = database
9
+ end
10
+
11
+ def load_from_xref(ea_guid)
12
+ return nil if ea_guid.nil?
13
+ return nil unless @database.xrefs
14
+
15
+ xref = find_stereotype_xref(ea_guid)
16
+ return nil unless xref
17
+
18
+ extract_stereotype_name(xref.description)
19
+ end
20
+
21
+ private
22
+
23
+ def find_stereotype_xref(ea_guid)
24
+ @database.xrefs_for_client(ea_guid).find do |x|
25
+ x.name == "Stereotypes" && x.type == "element property"
26
+ end
27
+ end
28
+
29
+ def extract_stereotype_name(description)
30
+ return nil if description.nil? || description.empty?
31
+
32
+ if description =~ /@STEREO;Name=([^;]+);/
33
+ Regexp.last_match(1)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Factory
6
+ # Transforms EA TaggedValue to UML TaggedValue
7
+ #
8
+ # This transformer converts Enterprise Architect tagged value
9
+ # definitions (custom metadata) to standard UML TaggedValue objects.
10
+ #
11
+ # @example Transform a tagged value
12
+ # ea_tag = Models::EaTaggedValue.new(
13
+ # property_id: "{GUID}",
14
+ # element_id: "{ELEMENT-GUID}",
15
+ # base_class: "ASSOCIATION_SOURCE",
16
+ # tag_value: "sequenceNumber|15$ea_notes=Unique integer..."
17
+ # )
18
+ # transformer = TaggedValueTransformer.new(database)
19
+ # uml_tag = transformer.transform(ea_tag)
20
+ class TaggedValueTransformer < BaseTransformer
21
+ # Transform EA tagged value to UML TaggedValue
22
+ #
23
+ # @param ea_tag [Models::EaTaggedValue] EA tagged value model
24
+ # @return [Lutaml::Uml::TaggedValue, nil] UML tagged value or nil
25
+ def transform(ea_tag)
26
+ return nil unless ea_tag
27
+ return nil unless ea_tag.tag_name
28
+
29
+ Lutaml::Uml::TaggedValue.new.tap do |tag|
30
+ tag.name = ea_tag.tag_name
31
+ tag.value = ea_tag.parsed_value || ""
32
+ tag.notes = ea_tag.parsed_notes
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Factory
6
+ # Registry for EA to UML transformers
7
+ # Implements the Registry pattern for transformer lookup
8
+ class TransformerRegistry
9
+ class << self
10
+ # Get or initialize the registry
11
+ # @return [Hash] Registry hash
12
+ def registry
13
+ @registry ||= {}
14
+ end
15
+
16
+ # Register a transformer for an EA type
17
+ # @param ea_type [Symbol, String] EA model type
18
+ # @param transformer_class [Class] Transformer class
19
+ def register(ea_type, transformer_class)
20
+ registry[ea_type.to_sym] = transformer_class
21
+ end
22
+
23
+ # Get transformer for an EA type
24
+ # @param ea_type [Symbol, String] EA model type
25
+ # @return [Class, nil] Transformer class or nil if not found
26
+ def transformer_for(ea_type)
27
+ registry[ea_type.to_sym]
28
+ end
29
+
30
+ # Get all registered transformers
31
+ # @return [Hash] All registered transformers
32
+ def all_transformers
33
+ registry.dup
34
+ end
35
+
36
+ # Check if a transformer is registered for a type
37
+ # @param ea_type [Symbol, String] EA model type
38
+ # @return [Boolean] True if registered
39
+ def registered?(ea_type)
40
+ registry.key?(ea_type.to_sym)
41
+ end
42
+
43
+ # Clear all registrations (mainly for testing)
44
+ def clear
45
+ @registry = {}
46
+ end
47
+
48
+ # Reset to default registrations
49
+ def reset_defaults
50
+ clear
51
+ register_defaults
52
+ end
53
+
54
+ # Register default transformers
55
+ def register_defaults
56
+ # Object types
57
+ register(:class, ClassTransformer)
58
+ register(:interface, ClassTransformer)
59
+ register(:enumeration, EnumTransformer)
60
+ register(:data_type, DataTypeTransformer)
61
+ register(:instance, InstanceTransformer)
62
+ register(:package, PackageTransformer)
63
+
64
+ # Connector types
65
+ register(:association, AssociationTransformer)
66
+ register(:generalization, GeneralizationTransformer)
67
+
68
+ # Feature types
69
+ register(:attribute, AttributeTransformer)
70
+ register(:operation, OperationTransformer)
71
+ register(:diagram, DiagramTransformer)
72
+ end
73
+ end
74
+
75
+ # Initialize registry with default transformers
76
+ register_defaults
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Factory
6
+ autoload :BaseTransformer, "ea/qea/factory/base_transformer"
7
+ autoload :AssociationBuilder, "ea/qea/factory/association_builder"
8
+ autoload :AssociationTransformer,
9
+ "ea/qea/factory/association_transformer"
10
+ autoload :AttributeTagTransformer,
11
+ "ea/qea/factory/attribute_tag_transformer"
12
+ autoload :AttributeTransformer, "ea/qea/factory/attribute_transformer"
13
+ autoload :ClassTransformer, "ea/qea/factory/class_transformer"
14
+ autoload :ConstraintTransformer,
15
+ "ea/qea/factory/constraint_transformer"
16
+ autoload :DataTypeTransformer, "ea/qea/factory/data_type_transformer"
17
+ autoload :DiagramTransformer, "ea/qea/factory/diagram_transformer"
18
+ autoload :DocumentBuilder, "ea/qea/factory/document_builder"
19
+ autoload :EnumTransformer, "ea/qea/factory/enum_transformer"
20
+ autoload :GeneralizationBuilder,
21
+ "ea/qea/factory/generalization_builder"
22
+ autoload :GeneralizationTransformer,
23
+ "ea/qea/factory/generalization_transformer"
24
+ autoload :InstanceTransformer, "ea/qea/factory/instance_transformer"
25
+ autoload :ObjectPropertyTransformer,
26
+ "ea/qea/factory/object_property_transformer"
27
+ autoload :OperationTransformer, "ea/qea/factory/operation_transformer"
28
+ autoload :PackageTransformer, "ea/qea/factory/package_transformer"
29
+ autoload :ReferenceResolver, "ea/qea/factory/reference_resolver"
30
+ autoload :StereotypeLoader, "ea/qea/factory/stereotype_loader"
31
+ autoload :TaggedValueTransformer,
32
+ "ea/qea/factory/tagged_value_transformer"
33
+ autoload :TransformerRegistry, "ea/qea/factory/transformer_registry"
34
+ autoload :EaToUmlFactory, "ea/qea/factory/ea_to_uml_factory"
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sqlite3"
4
+
5
+ module Ea
6
+ module Qea
7
+ # Utility for detecting and validating QEA files
8
+ class FileDetector
9
+ # SQLite magic bytes
10
+ SQLITE_MAGIC = "SQLite format 3\x00"
11
+
12
+ # Required EA tables for valid QEA file
13
+ REQUIRED_EA_TABLES = %w[
14
+ t_object
15
+ t_attribute
16
+ t_connector
17
+ t_package
18
+ ].freeze
19
+
20
+ class << self
21
+ # Check if file is a QEA file
22
+ #
23
+ # @param path [String] File path
24
+ # @return [Boolean] True if file appears to be QEA
25
+ def qea_file?(path)
26
+ return false unless File.exist?(path)
27
+ return false unless File.file?(path)
28
+ return false unless path.end_with?(".qea")
29
+
30
+ # Quick check: is it SQLite?
31
+ sqlite_file?(path)
32
+ end
33
+
34
+ # Validate QEA file structure
35
+ #
36
+ # @param path [String] File path
37
+ # @return [Hash] Validation result with :valid, :errors, :warnings
38
+ #
39
+ # @example
40
+ # result = FileDetector.validate_qea("model.qea")
41
+ # if result[:valid]
42
+ # puts "Valid QEA file"
43
+ # else
44
+ # puts "Errors: #{result[:errors]}"
45
+ # end
46
+ def validate_qea(path) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
47
+ errors = []
48
+ warnings = []
49
+
50
+ # Check file exists
51
+ unless File.exist?(path)
52
+ return {
53
+ valid: false,
54
+ errors: ["File not found: #{path}"],
55
+ warnings: [],
56
+ }
57
+ end
58
+
59
+ # Check file extension
60
+ unless path.end_with?(".qea")
61
+ warnings << "File does not have .qea extension"
62
+ end
63
+
64
+ # Check SQLite format
65
+ unless sqlite_file?(path)
66
+ errors << "File is not a valid SQLite database"
67
+ return { valid: false, errors: errors, warnings: warnings }
68
+ end
69
+
70
+ # Check for EA tables
71
+ begin
72
+ db = SQLite3::Database.new(path, readonly: true)
73
+ tables = get_table_names(db)
74
+
75
+ REQUIRED_EA_TABLES.each do |required_table|
76
+ unless tables.include?(required_table)
77
+ errors << "Missing required EA table: #{required_table}"
78
+ end
79
+ end
80
+
81
+ # Check for data
82
+ if tables.include?("t_object")
83
+ count = db.execute("SELECT COUNT(*) FROM t_object").first.first
84
+ if count.zero?
85
+ warnings << "No objects found in t_object table"
86
+ end
87
+ end
88
+ rescue SQLite3::Exception => e
89
+ errors << "Failed to open database: #{e.message}"
90
+ ensure
91
+ db&.close
92
+ end
93
+
94
+ {
95
+ valid: errors.empty?,
96
+ errors: errors,
97
+ warnings: warnings,
98
+ }
99
+ end
100
+
101
+ # Get file information
102
+ #
103
+ # @param path [String] File path
104
+ # @return [Hash] File information
105
+ #
106
+ # @example
107
+ # info = FileDetector.file_info("model.qea")
108
+ # puts "Size: #{info[:size_mb]} MB"
109
+ # puts "Tables: #{info[:table_count]}"
110
+ def file_info(path) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
111
+ return { error: "File not found" } unless File.exist?(path)
112
+
113
+ info = {
114
+ path: path,
115
+ size_bytes: File.size(path),
116
+ size_mb: (File.size(path) / 1024.0 / 1024.0).round(2),
117
+ modified: File.mtime(path),
118
+ is_qea: qea_file?(path),
119
+ }
120
+
121
+ if sqlite_file?(path)
122
+ begin
123
+ db = SQLite3::Database.new(path, readonly: true)
124
+ tables = get_table_names(db)
125
+ info[:is_sqlite] = true
126
+ info[:table_count] = tables.size
127
+ info[:has_ea_tables] = REQUIRED_EA_TABLES.all? do |t|
128
+ tables.include?(t)
129
+ end
130
+
131
+ # Get record counts for key tables
132
+ if tables.include?("t_object")
133
+ info[:object_count] =
134
+ db.execute("SELECT COUNT(*) FROM t_object").first.first
135
+ end
136
+ if tables.include?("t_package")
137
+ info[:package_count] =
138
+ db.execute("SELECT COUNT(*) FROM t_package").first.first
139
+ end
140
+ rescue SQLite3::Exception => e
141
+ info[:error] = e.message
142
+ ensure
143
+ db&.close
144
+ end
145
+ else
146
+ info[:is_sqlite] = false
147
+ end
148
+
149
+ info
150
+ end
151
+
152
+ private
153
+
154
+ # Check if file is SQLite database
155
+ #
156
+ # @param path [String] File path
157
+ # @return [Boolean] True if SQLite database
158
+ def sqlite_file?(path)
159
+ File.open(path, "rb") do |f|
160
+ magic = f.read(16)
161
+ magic == SQLITE_MAGIC
162
+ end
163
+ rescue StandardError
164
+ false
165
+ end
166
+
167
+ # Get table names from database
168
+ #
169
+ # @param db [SQLite3::Database] Database connection
170
+ # @return [Array<String>] Table names
171
+ def get_table_names(db)
172
+ db.execute("SELECT name FROM sqlite_master WHERE type='table'")
173
+ .flatten
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sqlite3"
4
+
5
+ module Ea
6
+ module Qea
7
+ module Infrastructure
8
+ # DatabaseConnection manages the SQLite database connection lifecycle
9
+ # for QEA files (Enterprise Architect SQLite databases).
10
+ #
11
+ # @example Connect to a QEA file
12
+ # conn = DatabaseConnection.new("model.qea")
13
+ # conn.connect
14
+ # # ... use connection
15
+ # conn.close
16
+ #
17
+ # @example Using with_connection block
18
+ # conn = DatabaseConnection.new("model.qea")
19
+ # conn.with_connection do |db|
20
+ # # ... use db
21
+ # end
22
+ class DatabaseConnection
23
+ attr_reader :file_path, :connection
24
+
25
+ # Initialize a new database connection
26
+ #
27
+ # @param file_path [String] Path to the .qea file
28
+ # @raise [ArgumentError] if file_path is nil or empty
29
+ def initialize(file_path)
30
+ if file_path.nil? || file_path.empty?
31
+ raise ArgumentError,
32
+ "file_path cannot be nil or empty"
33
+ end
34
+
35
+ @file_path = file_path
36
+ @connection = nil
37
+ end
38
+
39
+ # Connect to the database
40
+ #
41
+ # @return [SQLite3::Database] The database connection
42
+ # @raise [Errno::ENOENT] if the file does not exist
43
+ # @raise [SQLite3::Exception] if connection fails
44
+ def connect
45
+ unless File.exist?(@file_path)
46
+ raise Errno::ENOENT, "QEA file not found: #{@file_path}"
47
+ end
48
+
49
+ @connection = SQLite3::Database.new(@file_path, readonly: true)
50
+ @connection.results_as_hash = true
51
+ @connection
52
+ end
53
+
54
+ # Close the database connection
55
+ #
56
+ # @return [void]
57
+ def close
58
+ return unless @connection
59
+
60
+ @connection.close
61
+ @connection = nil
62
+ end
63
+
64
+ # Check if the connection is open
65
+ #
66
+ # @return [Boolean] true if connection is open
67
+ def connected?
68
+ !@connection.nil? && !@connection.closed?
69
+ end
70
+
71
+ # Execute a block with an active connection
72
+ #
73
+ # This method ensures the connection is properly opened and closed.
74
+ # If a connection already exists, it reuses it. Otherwise, it creates
75
+ # a new connection and closes it after the block executes.
76
+ #
77
+ # @yield [SQLite3::Database] The database connection
78
+ # @return [Object] The result of the block
79
+ # @raise [Errno::ENOENT] if the file does not exist
80
+ # @raise [SQLite3::Exception] if connection fails
81
+ #
82
+ # @example
83
+ # conn = DatabaseConnection.new("model.qea")
84
+ # result = conn.with_connection do |db|
85
+ # db.execute("SELECT COUNT(*) FROM t_object")
86
+ # end
87
+ def with_connection
88
+ should_close = !connected?
89
+
90
+ begin
91
+ connect unless connected?
92
+ yield @connection
93
+ ensure
94
+ close if should_close
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end