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,326 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Load local development version
5
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
6
+
7
+ # Example: Repository Statistics and Analysis
8
+ #
9
+ # This example demonstrates how to extract and analyze comprehensive
10
+ # statistics from a UML repository, including package structure,
11
+ # class complexity, relationships, and model quality metrics.
12
+
13
+ require "lutaml"
14
+ require "lutaml/uml_repository"
15
+ require "ea"
16
+
17
+ puts "=" * 80
18
+ puts "LUR Repository Statistics Examples"
19
+ puts "=" * 80
20
+ puts
21
+
22
+ # Setup: Find a test file
23
+ qea_file = Dir.glob("examples/qea/*.qea").first
24
+ xmi_file = Dir.glob("examples/qea/*.xmi").first
25
+ test_file = qea_file || xmi_file
26
+
27
+ unless test_file
28
+ puts "No test files found in examples/qea/"
29
+ puts
30
+ puts "This example demonstrates statistics extraction. To run:"
31
+ puts " 1. Place a .qea or .xmi file in examples/qea/"
32
+ puts " 2. Or examine the statistics API below"
33
+ puts
34
+ puts "Statistics API example:"
35
+ puts <<~RUBY
36
+ repo = Lutaml::UmlRepository::Repository.from_xmi("model.xmi")
37
+
38
+ stats = repo.statistics
39
+
40
+ # Core counts
41
+ puts "Packages: \#{stats[:total_packages]}"
42
+ puts "Classes: \#{stats[:total_classes]}"
43
+ puts "Associations: \#{stats[:total_associations]}"
44
+
45
+ # Stereotypes
46
+ stats[:classes_by_stereotype].each do |stereotype, count|
47
+ puts "\#{stereotype}: \#{count}"
48
+ end
49
+
50
+ # Complexity metrics
51
+ puts "Max attributes: \#{stats[:max_attributes]}"
52
+ puts "Avg attributes: \#{stats[:avg_attributes]}"
53
+
54
+ # Package structure
55
+ puts "Max package depth: \#{stats[:max_package_depth]}"
56
+ RUBY
57
+ exit 0
58
+ end
59
+
60
+ puts "Loading model: #{test_file}"
61
+ puts "File size: #{File.size(test_file) / 1024}KB"
62
+ puts
63
+
64
+ # Load repository
65
+ if test_file.end_with?(".qea")
66
+ document = Ea::Qea.parse(test_file)
67
+ repo = Lutaml::UmlRepository::Repository.new(document: document)
68
+ elsif test_file.end_with?(".xmi")
69
+ repo = Lutaml::UmlRepository::Repository.from_xmi(test_file)
70
+ end
71
+
72
+ puts "Repository loaded successfully"
73
+ puts
74
+
75
+ # Statistics Section 1: Overview
76
+ puts "=" * 80
77
+ puts "1. Model Overview"
78
+ puts "=" * 80
79
+
80
+ stats = repo.statistics
81
+
82
+ puts "Total elements:"
83
+ puts " Packages: #{stats[:total_packages]}"
84
+ puts " Classes: #{stats[:total_classes]}"
85
+ puts " Data types: #{stats[:total_data_types]}"
86
+ puts " Enumerations: #{stats[:total_enums]}"
87
+ puts " Associations: #{stats[:total_associations]}"
88
+ puts " Diagrams: #{stats[:total_diagrams]}"
89
+ puts
90
+
91
+ # Statistics Section 2: Package Structure
92
+ puts "=" * 80
93
+ puts "2. Package Structure"
94
+ puts "=" * 80
95
+
96
+ if stats[:max_package_depth]
97
+ puts "Package hierarchy depth: #{stats[:max_package_depth]}"
98
+ end
99
+
100
+ packages = repo.packages_index
101
+ if packages.any?
102
+ puts "Total packages: #{packages.size}"
103
+ puts "\nSample packages:"
104
+ packages.first(5).each do |pkg|
105
+ puts " - #{pkg.name}"
106
+ end
107
+ end
108
+ puts
109
+
110
+ # Statistics Section 3: Class Stereotypes
111
+ puts "=" * 80
112
+ puts "3. Class Stereotypes"
113
+ puts "=" * 80
114
+
115
+ if stats[:classes_by_stereotype]
116
+ stereotype_count = stats[:classes_by_stereotype].size
117
+ puts "Unique stereotypes: #{stereotype_count}"
118
+ puts "\nStereotype distribution:"
119
+
120
+ stats[:classes_by_stereotype].sort_by do |_, count|
121
+ -count
122
+ end.first(10).each do |stereotype, count|
123
+ percentage = (count.to_f / stats[:total_classes] * 100).round(1)
124
+ puts " #{stereotype.ljust(30)} #{count.to_s.rjust(5)} (#{percentage}%)"
125
+ end
126
+ else
127
+ puts "No stereotype information available"
128
+ end
129
+ puts
130
+
131
+ # Statistics Section 4: Class Complexity
132
+ puts "=" * 80
133
+ puts "4. Class Complexity Metrics"
134
+ puts "=" * 80
135
+
136
+ all_classes = repo.classes_index.grep(Lutaml::Uml::UmlClass)
137
+
138
+ if all_classes.any?
139
+ # Attribute counts
140
+ attr_counts = all_classes.map { |c| c.attributes&.size || 0 }
141
+
142
+ puts "Attribute statistics:"
143
+ puts " Classes with attributes: #{attr_counts.count(&:positive?)}"
144
+ puts " Min attributes: #{attr_counts.min}"
145
+ puts " Max attributes: #{attr_counts.max}"
146
+ puts " Avg attributes: #{(attr_counts.sum.to_f / attr_counts.size).round(2)}"
147
+ puts " Median attributes: #{attr_counts.sort[attr_counts.size / 2]}"
148
+
149
+ # Find most complex classes
150
+ puts "\nMost complex classes (by attribute count):"
151
+ complex_classes = all_classes.map { |c| [c.name, c.attributes&.size || 0] }
152
+ .sort_by { |_, count| -count }
153
+ .first(5)
154
+
155
+ complex_classes.each do |name, count|
156
+ puts " #{name}: #{count} attributes"
157
+ end
158
+
159
+ # Operation counts
160
+ op_counts = all_classes.map { |c| c.operations&.size || 0 }
161
+
162
+ puts "\nOperation statistics:"
163
+ puts " Classes with operations: #{op_counts.count(&:positive?)}"
164
+ puts " Max operations: #{op_counts.max}"
165
+ puts " Avg operations: #{(op_counts.sum.to_f / op_counts.size).round(2)}"
166
+ else
167
+ puts "No class data available"
168
+ end
169
+ puts
170
+
171
+ # Statistics Section 5: Inheritance
172
+ puts "=" * 80
173
+ puts "5. Inheritance Analysis"
174
+ puts "=" * 80
175
+
176
+ if all_classes.any?
177
+ classes_with_parent = all_classes.select do |c|
178
+ repo.supertype_of(c)
179
+ end
180
+
181
+ puts "Inheritance statistics:"
182
+ puts " Classes with parent: #{classes_with_parent.size}"
183
+ puts " Root classes: #{all_classes.size - classes_with_parent.size}"
184
+ puts " Inheritance usage: " \
185
+ "#{(classes_with_parent.size.to_f / all_classes.size * 100).round(1)}%"
186
+
187
+ # Find deepest hierarchies
188
+ max_depth = 0
189
+ deepest_class = nil
190
+
191
+ all_classes.first(100).each do |klass|
192
+ ancestors = repo.ancestors_of(klass)
193
+ if ancestors.size > max_depth
194
+ max_depth = ancestors.size
195
+ deepest_class = klass
196
+ end
197
+ end
198
+
199
+ if deepest_class
200
+ puts "\nDeepest inheritance hierarchy: #{max_depth} levels"
201
+ puts " Class: #{deepest_class.name}"
202
+ ancestors = repo.ancestors_of(deepest_class)
203
+ puts " Ancestors: #{ancestors.map(&:name).join(' < ')}"
204
+ end
205
+ end
206
+ puts
207
+
208
+ # Statistics Section 6: Associations
209
+ puts "=" * 80
210
+ puts "6. Association Analysis"
211
+ puts "=" * 80
212
+
213
+ associations = repo.associations_index
214
+
215
+ if associations.any?
216
+ puts "Total associations: #{associations.size}"
217
+
218
+ # Association types
219
+ assoc_types = associations.group_by do |a|
220
+ a.member_end_type&.first || "unknown"
221
+ end
222
+
223
+ puts "\nAssociation types:"
224
+ assoc_types.sort_by { |_, list| -list.size }.first(10).each do |type, list|
225
+ puts " #{type.ljust(20)} #{list.size}"
226
+ end
227
+
228
+ # Most connected classes
229
+ connection_counts = Hash.new(0)
230
+
231
+ all_classes.first(100).each do |klass|
232
+ assocs = repo.associations_of(klass)
233
+ connection_counts[klass.name] = assocs.size
234
+ end
235
+
236
+ puts "\nMost connected classes:"
237
+ connection_counts.sort_by { |_, count| -count }.first(5).each do |name, count|
238
+ puts " #{name}: #{count} associations"
239
+ end
240
+ end
241
+ puts
242
+
243
+ # Statistics Section 7: Model Quality
244
+ puts "=" * 80
245
+ puts "7. Model Quality Indicators"
246
+ puts "=" * 80
247
+
248
+ # Run validation
249
+ validation = repo.validate
250
+
251
+ puts "Validation status: #{validation.valid? ? 'PASS ✓' : 'FAIL ✗'}"
252
+ puts " Errors: #{validation.errors.size}"
253
+ puts " Warnings: #{validation.warnings.size}"
254
+
255
+ if all_classes.any?
256
+ # Calculate documentation coverage
257
+ documented_classes = all_classes.count do |c|
258
+ c.respond_to?(:documentation) && c.documentation && !c.documentation.empty?
259
+ end
260
+ doc_coverage = (documented_classes.to_f / all_classes.size * 100).round(1)
261
+
262
+ puts "\nDocumentation coverage:"
263
+ puts " Documented classes: #{documented_classes}/#{all_classes.size} " \
264
+ "(#{doc_coverage}%)"
265
+
266
+ # Naming conventions
267
+ camel_case = all_classes.count { |c| c.name =~ /^[A-Z][a-zA-Z0-9]*$/ }
268
+ naming_compliance = (camel_case.to_f / all_classes.size * 100).round(1)
269
+
270
+ puts "\nNaming conventions:"
271
+ puts " CamelCase classes: #{camel_case}/#{all_classes.size} " \
272
+ "(#{naming_compliance}%)"
273
+
274
+ # Abstract classes
275
+ abstract_classes = all_classes.count do |c|
276
+ c.respond_to?(:is_abstract) && c.is_abstract
277
+ end
278
+ abstract_ratio = (abstract_classes.to_f / all_classes.size * 100).round(1)
279
+
280
+ puts "\nAbstraction:"
281
+ puts " Abstract classes: #{abstract_classes}/#{all_classes.size} " \
282
+ "(#{abstract_ratio}%)"
283
+ end
284
+ puts
285
+
286
+ # Statistics Section 8: Export Summary
287
+ puts "=" * 80
288
+ puts "8. Summary Statistics (JSON format)"
289
+ puts "=" * 80
290
+
291
+ require "json"
292
+
293
+ summary = {
294
+ model: {
295
+ packages: stats[:total_packages],
296
+ classes: stats[:total_classes],
297
+ data_types: stats[:total_data_types],
298
+ enumerations: stats[:total_enums],
299
+ associations: stats[:total_associations],
300
+ diagrams: stats[:total_diagrams],
301
+ },
302
+ structure: {
303
+ max_package_depth: stats[:max_package_depth],
304
+ },
305
+ validation: {
306
+ valid: validation.valid?,
307
+ errors: validation.errors.size,
308
+ warnings: validation.warnings.size,
309
+ },
310
+ }
311
+
312
+ if stats[:classes_by_stereotype]
313
+ summary[:stereotypes] = stats[:classes_by_stereotype]
314
+ end
315
+
316
+ puts JSON.pretty_generate(summary)
317
+ puts
318
+
319
+ puts "=" * 80
320
+ puts "Statistics analysis complete!"
321
+ puts "=" * 80
322
+ puts
323
+ puts "You can also get statistics via CLI:"
324
+ puts " $ lutaml uml stats model.lur"
325
+ puts " $ lutaml uml stats model.lur --detailed"
326
+ puts " $ lutaml uml stats model.lur --format json"
@@ -0,0 +1,230 @@
1
+ = QEA Parser - Enterprise Architect Database Parser
2
+
3
+ A Ruby library for parsing Enterprise Architect .qea (SQLite) database files into structured Ruby objects using Lutaml::Model.
4
+
5
+ == Overview
6
+
7
+ This library provides direct parsing of Enterprise Architect `.qea` files (SQLite databases) without requiring XMI export. This provides significant performance improvements and access to EA-specific metadata.
8
+
9
+ You can:
10
+
11
+ * Parse QEA files directly to UML documents
12
+ * Access UML model elements (classes, attributes, operations, associations, packages, diagrams)
13
+ * Query and analyze Enterprise Architect models programmatically
14
+ * Build LUR packages for fast querying
15
+
16
+ == Installation
17
+
18
+ [source,bash]
19
+ ----
20
+ gem install lutaml
21
+ ----
22
+
23
+ == Usage
24
+
25
+ === Basic Usage - Parse to UML Document
26
+
27
+ [source,ruby]
28
+ ----
29
+ require "ea/qea"
30
+
31
+ # Parse QEA file to UML Document
32
+ document = Ea::Qea.parse("model.qea")
33
+
34
+ # Use with UmlRepository for querying
35
+ repo = Lutaml::UmlRepository::Repository.new(document: document)
36
+
37
+ # Query the model
38
+ puts "Total classes: #{repo.classes_index.size}"
39
+ puts "Total packages: #{repo.packages_index.size}"
40
+
41
+ # Find classes by stereotype
42
+ feature_types = repo.find_classes_by_stereotype("featureType")
43
+ puts "Feature types: #{feature_types.size}"
44
+
45
+ # Get statistics
46
+ stats = repo.statistics
47
+ puts "Packages: #{stats[:total_packages]}"
48
+ puts "Classes: #{stats[:total_classes]}"
49
+ puts "Associations: #{stats[:total_associations]}"
50
+ ----
51
+
52
+ === Parse with Validation
53
+
54
+ [source,ruby]
55
+ ----
56
+ require "ea/qea"
57
+
58
+ # Parse with validation
59
+ result = Ea::Qea.parse("model.qea", validate: true)
60
+
61
+ document = result[:document]
62
+ validation = result[:validation_result]
63
+
64
+ if validation.valid?
65
+ puts "Model is valid!"
66
+ else
67
+ puts "Validation errors: #{validation.errors.size}"
68
+ validation.errors.first(5).each do |error|
69
+ puts " - #{error}"
70
+ end
71
+ end
72
+ ----
73
+
74
+ === Low-Level Database Access
75
+
76
+ [source,ruby]
77
+ ----
78
+ require "ea/qea"
79
+
80
+ # Load database with progress callback
81
+ database = Ea::Qea.load_database("model.qea") do |table, current, total|
82
+ puts "Loading #{table}: #{current}/#{total}"
83
+ end
84
+
85
+ # Access raw EA tables
86
+ puts "Objects: #{database.objects.size}"
87
+ puts "Attributes: #{database.attributes.size}"
88
+ puts "Connectors: #{database.connectors.size}"
89
+ puts "Packages: #{database.packages.size}"
90
+
91
+ # Get statistics
92
+ stats = database.stats
93
+ stats.each do |table_name, count|
94
+ puts "#{table_name}: #{count} records"
95
+ end
96
+ ----
97
+
98
+ === Quick Database Info
99
+
100
+ [source,ruby]
101
+ ----
102
+ require "ea/qea"
103
+
104
+ # Get quick statistics without full loading
105
+ info = Ea::Qea.database_info("model.qea")
106
+ puts info # => {"objects" => 693, "attributes" => 1910, ...}
107
+ ----
108
+
109
+ == Performance
110
+
111
+ Direct QEA parsing offers several advantages over XMI:
112
+
113
+ * *Performance*: 10-20x faster than XMI parsing
114
+ * *No export step*: Parse directly from EA database
115
+ * *EA metadata*: Access to EA-specific information not available in XMI
116
+ * *Memory efficient*: Streams data from SQLite without loading entire file
117
+
118
+ Typical performance for a 20MB EA model:
119
+
120
+ * *QEA parsing*: 1-2 seconds
121
+ * *XMI parsing*: 20-40 seconds
122
+ * *Speedup*: 10-20x faster
123
+ * *Memory*: Lower memory footprint with QEA
124
+
125
+ == CLI Usage
126
+
127
+ All CLI commands accept both XMI and QEA files:
128
+
129
+ [source,bash]
130
+ ----
131
+ # Build LUR from QEA (10-20x faster than XMI)
132
+ lutaml uml build model.qea -o model.lur
133
+
134
+ # All other commands work the same
135
+ lutaml uml search model.lur "Building"
136
+ lutaml uml tree model.lur
137
+ lutaml uml stats model.lur
138
+ ----
139
+
140
+ == File Validation
141
+
142
+ [source,ruby]
143
+ ----
144
+ require "ea/qea/file_detector"
145
+
146
+ # Check if file is valid QEA
147
+ if Ea::Qea::FileDetector.qea_file?("model.qea")
148
+ puts "Valid QEA file"
149
+ end
150
+
151
+ # Validate structure
152
+ result = Ea::Qea::FileDetector.validate_qea("model.qea")
153
+ if result[:valid]
154
+ puts "QEA file is valid"
155
+ else
156
+ result[:errors].each { |e| puts "ERROR: #{e}" }
157
+ end
158
+
159
+ # Get file information
160
+ info = Ea::Qea::FileDetector.file_info("model.qea")
161
+ puts "Size: #{info[:size_mb]} MB"
162
+ puts "Tables: #{info[:table_count]}"
163
+ puts "Objects: #{info[:object_count]}"
164
+ ----
165
+
166
+ == Configuration
167
+
168
+ QEA parsing is configured via `config/qea_schema.yml`:
169
+
170
+ [source,yaml]
171
+ ----
172
+ tables:
173
+ - table_name: t_object
174
+ enabled: true
175
+ model_class: Object
176
+
177
+ - table_name: t_attribute
178
+ enabled: true
179
+ model_class: Attribute
180
+
181
+ # ... other tables
182
+ ----
183
+
184
+ == Database Schema
185
+
186
+ The QEA parser supports all Enterprise Architect tables:
187
+
188
+ *Core UML Model:*
189
+
190
+ * `objects` - UML objects (classes, interfaces, components, etc.)
191
+ * `attributes` - Class attributes and properties
192
+ * `operations` - Class operations and methods
193
+ * `operationparams` - Operation parameters
194
+ * `connectors` - Relationships between objects
195
+ * `packages` - UML packages and namespaces
196
+ * `diagrams` - UML diagrams
197
+
198
+ *Extended Model:*
199
+
200
+ * `taggedvalues` - Tagged values (stereotypes, metadata)
201
+ * `stereotypes` - Stereotype definitions
202
+ * `datatypes` - Data type definitions
203
+ * `constraints` - Model constraints
204
+ * And 80+ more tables...
205
+
206
+ == Examples
207
+
208
+ See the `examples/` directory for complete working examples:
209
+
210
+ * `examples/lur_basic_usage.rb` - Basic repository operations with QEA files
211
+ * `examples/lur_cli_workflow.rb` - Command-line workflow with QEA
212
+ * `examples/lur_statistics.rb` - Statistics and validation
213
+
214
+ == Limitations
215
+
216
+ * QEA files must be SQLite 3 format
217
+ * Requires EA-specific table structure
218
+ * Some EA proprietary features may not be fully supported
219
+ * Only reads from QEA; does not write back to EA database
220
+ * Read-only access (safe for concurrent use)
221
+
222
+ == License
223
+
224
+ This library is part of LutaML and follows the same BSD-3 license.
225
+
226
+ == Credits
227
+
228
+ * Built with https://github.com/lutaml/lutaml-model[Lutaml::Model]
229
+ * Uses SQLite3 for database access
230
+ * Part of the LutaML project ecosystem
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example: Standalone QEA loading and querying (no lutaml-uml required).
5
+ #
6
+ # The `ea` gem's core is a standalone Sparx EA QEA parser. You can load a
7
+ # .qea file and query the EA-native data structures directly — useful when
8
+ # you only need EA metadata, statistics, or specific table rows and don't
9
+ # want the UML metamodel in your dependency tree.
10
+ #
11
+ # Run: bundle exec ruby examples/qea_standalone_query.rb
12
+
13
+ require "bundler/setup"
14
+ require "ea"
15
+
16
+ QEA_PATH = ENV.fetch(
17
+ "QEA_PATH",
18
+ "/Users/mulgogi/src/mn/plateau-model/20251010_current_plateau_v5.1.qea",
19
+ )
20
+
21
+ abort "QEA not found: #{QEA_PATH}" unless File.exist?(QEA_PATH)
22
+
23
+ # Load the complete database. Returns an Ea::Qea::Database — an immutable
24
+ # container with lazy-built indexes for O(1) lookups.
25
+ #
26
+ # NOTE: EA objects expose their database primary key as `ea_object_id`, NOT
27
+ # `object_id`. Ruby's built-in `Object#object_id` (memory address) shadows
28
+ # any attribute named `object_id`, so always use `ea_object_id`.
29
+ database = Ea::Qea.load(QEA_PATH)
30
+
31
+ # Each EA table is exposed as a repository (Enumerable, with find/where/pluck).
32
+ puts "Loaded #{database.total_records} records across #{database.collection_names.size} collections"
33
+ puts
34
+
35
+ # Example queries ----------------------------------------------------------
36
+
37
+ # 1. Find a class with attributes inside a real package.
38
+ # Use `ea_object_id` (the DB primary key), never `object_id`.
39
+ sample = database.objects
40
+ .select { |o| o.object_type == "Class" && o.name && !o.name.empty? }
41
+ .select { |o| database.find_package(o.package_id) }
42
+ .select { |o| database.attributes_for_object(o.ea_object_id).any? }
43
+ .first
44
+ puts "Sample class: #{sample.name} (ea_object_id=#{sample.ea_object_id})"
45
+ puts
46
+
47
+ # 2. All connectors for that class
48
+ connectors = database.connectors_for_object(sample.ea_object_id)
49
+ puts "Connectors for #{sample.name}: #{connectors.size}"
50
+ connectors.first(3).each do |c|
51
+ puts " type=#{c.connector_type} dest=#{c.destelement.inspect} src=#{c.sourceelement.inspect}"
52
+ end
53
+ puts
54
+
55
+ # 3. All attributes for that class
56
+ attributes = database.attributes_for_object(sample.ea_object_id)
57
+ puts "Attributes for #{sample.name}: #{attributes.size}"
58
+ attributes.first(3).each { |a| puts " #{a.name}: #{a.type}" }
59
+ puts
60
+
61
+ # 4. Tagged values (stereotype properties) for the class
62
+ tags = database.tagged_values_for_element(sample.ea_guid)
63
+ puts "Tagged values for #{sample.name}: #{tags.size}"
64
+ tags.first(3).each { |t| puts " #{t.property}: #{t.value}" if t.value && !t.value.empty? }
65
+ puts
66
+
67
+ # 5. Packages hierarchy
68
+ root_packages = database.packages.select { |p| p.parent_id.nil? || p.parent_id.zero? }
69
+ puts "Root packages: #{root_packages.size}"
70
+ root_packages.each do |pkg|
71
+ children = database.child_packages_for(pkg.package_id)
72
+ puts " #{pkg.name} (id=#{pkg.package_id}, #{children.size} children)"
73
+ end
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example: QEA → UML Document → UmlRepository composition.
5
+ #
6
+ # This is the recommended composition pattern for getting from a .qea file to
7
+ # a queryable Lutaml::UmlRepository::Repository. Each gem does one job:
8
+ #
9
+ # ea parses .qea → Lutaml::Uml::Document
10
+ # lutaml-uml wraps the Document in a queryable Repository
11
+ #
12
+ # No gem reaches into another at load time. The caller composes.
13
+ #
14
+ # Run: bundle exec ruby examples/qea_to_repository.rb
15
+
16
+ require "bundler/setup"
17
+ require "ea"
18
+ require "lutaml/uml"
19
+ require "lutaml/uml_repository"
20
+
21
+ QEA_PATH = ENV.fetch(
22
+ "QEA_PATH",
23
+ "/Users/mulgogi/src/mn/plateau-model/20251010_current_plateau_v5.1.qea",
24
+ )
25
+
26
+ abort "QEA not found: #{QEA_PATH}" unless File.exist?(QEA_PATH)
27
+
28
+ # Step 1: ea parses the .qea into a UML Document.
29
+ puts "Parsing #{File.basename(QEA_PATH)}..."
30
+ document = Ea::Qea.parse(QEA_PATH)
31
+ puts " → #{document.class} (#{document.packages.size} root packages)"
32
+ puts
33
+
34
+ # Step 2: lutaml-uml wraps the document in a queryable repository.
35
+ # This is composition — no load-time registration, no hidden coupling.
36
+ repo = Lutaml::UmlRepository::Repository.from_document(document)
37
+ puts "Wrapped in #{repo.class}"
38
+ puts
39
+
40
+ # Step 3: use the repository's query API.
41
+ puts "Repository queries:"
42
+ puts " total classes: #{repo.all_classes.size}"
43
+ puts " total diagrams: #{repo.all_diagrams.size}"
44
+
45
+ # find_class takes a qualified name like "ModelRoot::Pkg::ClassName".
46
+ # Here we just take the first one from the index to demonstrate.
47
+ first_id, first_class = repo.all_classes.first
48
+ if first_class
49
+ puts
50
+ puts "Sample class from repository: #{first_class.respond_to?(:name) ? first_class.name : first_id}"
51
+ end