lutaml 0.10.13 → 0.10.15

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +29 -33
  3. data/lib/lutaml/cli/interactive_shell/export_handler.rb +25 -17
  4. data/lib/lutaml/cli/interactive_shell/help_display.rb +39 -45
  5. data/lib/lutaml/cli/interactive_shell/navigation_commands.rb +45 -26
  6. data/lib/lutaml/cli/interactive_shell/query_commands.rb +73 -47
  7. data/lib/lutaml/cli/interactive_shell.rb +53 -27
  8. data/lib/lutaml/cli/tree_view_formatter.rb +11 -3
  9. data/lib/lutaml/converter/xmi_to_uml.rb +11 -6
  10. data/lib/lutaml/formatter/graphviz.rb +65 -35
  11. data/lib/lutaml/model_transformations/parsers/base_parser.rb +27 -29
  12. data/lib/lutaml/qea/factory/association_builder.rb +144 -127
  13. data/lib/lutaml/qea/factory/class_transformer.rb +91 -53
  14. data/lib/lutaml/qea/factory/ea_to_uml_factory.rb +11 -22
  15. data/lib/lutaml/qea/factory/enum_transformer.rb +41 -31
  16. data/lib/lutaml/qea/factory/generalization_builder.rb +155 -125
  17. data/lib/lutaml/qea/factory/stereotype_loader.rb +13 -7
  18. data/lib/lutaml/qea/lookup_indexes.rb +31 -13
  19. data/lib/lutaml/uml/inheritance_walker.rb +11 -7
  20. data/lib/lutaml/uml_repository/exporters/markdown/class_page_builder.rb +33 -25
  21. data/lib/lutaml/uml_repository/exporters/markdown/index_page_builder.rb +17 -9
  22. data/lib/lutaml/uml_repository/exporters/markdown_exporter.rb +27 -20
  23. data/lib/lutaml/uml_repository/index_builders/association_index.rb +60 -48
  24. data/lib/lutaml/uml_repository/index_builders/class_index.rb +35 -24
  25. data/lib/lutaml/uml_repository/queries/class_query.rb +79 -48
  26. data/lib/lutaml/uml_repository/queries/inheritance_query.rb +42 -32
  27. data/lib/lutaml/uml_repository/queries/search_query.rb +93 -85
  28. data/lib/lutaml/uml_repository/query_dsl/conditions/package_condition.rb +9 -2
  29. data/lib/lutaml/uml_repository/repository/loader.rb +14 -7
  30. data/lib/lutaml/uml_repository/static_site/data_transformer.rb +64 -35
  31. data/lib/lutaml/uml_repository/static_site/search_index_builder.rb +32 -19
  32. data/lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb +36 -20
  33. data/lib/lutaml/uml_repository/static_site/serializers/inheritance_resolver.rb +131 -105
  34. data/lib/lutaml/uml_repository/static_site/serializers/package_serializer.rb +15 -9
  35. data/lib/lutaml/uml_repository/static_site/serializers/package_tree_builder.rb +38 -24
  36. data/lib/lutaml/version.rb +1 -1
  37. data/lib/lutaml/xmi/liquid_drops/klass_drop.rb +34 -18
  38. data/lib/lutaml/xmi/parsers/xmi_connector.rb +35 -23
  39. metadata +2 -9
  40. data/TODO.cleanups/01-resolve-production-todos.md +0 -65
  41. data/TODO.cleanups/02-reduce-metrics-offenses.md +0 -37
  42. data/TODO.cleanups/03-reduce-rspec-multiple-expectations.md +0 -54
  43. data/TODO.cleanups/04-reduce-rspec-example-length.md +0 -45
  44. data/TODO.cleanups/07-fix-lint-offenses.md +0 -74
  45. data/TODO.cleanups/08-reduce-memoized-helpers-and-nesting.md +0 -43
  46. data/TODO.cleanups/09-reduce-verified-doubles-and-rspec-style.md +0 -57
@@ -275,13 +275,21 @@ module Lutaml
275
275
 
276
276
  # Determine class type (class, interface, enumeration)
277
277
  def determine_class_type(klass)
278
- return :enumeration if klass.class.name&.include?("Enum")
279
- return :interface if klass.is_a?(Lutaml::Uml::TopElement) &&
280
- Array(klass.stereotype).any? { |s| s&.downcase == "interface" }
278
+ return :enumeration if enumeration?(klass)
279
+ return :interface if interface?(klass)
281
280
 
282
281
  :class
283
282
  end
284
283
 
284
+ def enumeration?(klass)
285
+ klass.class.name&.include?("Enum")
286
+ end
287
+
288
+ def interface?(klass)
289
+ klass.is_a?(Lutaml::Uml::TopElement) &&
290
+ Array(klass.stereotype).any? { |s| s&.downcase == "interface" }
291
+ end
292
+
285
293
  # Find package path for a package object
286
294
  def find_package_path(repository, package)
287
295
  repository.indexes[:package_paths].each do |path, pkg|
@@ -148,12 +148,17 @@ module Lutaml
148
148
  # Lazy-built hash index for O(1) diagram lookups by package
149
149
  # @return [Hash] Mapping of package_id => [diagrams]
150
150
  def diagram_lookup
151
- @diagram_lookup ||= begin
152
- idx = Hash.new { |h, k| h[k] = [] }
153
- diagrams = @xmi_root_model.extension&.diagrams&.diagram || []
154
- diagrams.each { |d| idx[d.model.package] << d if d.model&.package }
155
- idx
156
- end
151
+ @diagram_lookup ||= build_diagram_index
152
+ end
153
+
154
+ def build_diagram_index
155
+ idx = Hash.new { |h, k| h[k] = [] }
156
+ xmi_diagrams.each { |d| idx[d.model.package] << d if d.model&.package }
157
+ idx
158
+ end
159
+
160
+ def xmi_diagrams
161
+ @xmi_root_model.extension&.diagrams&.diagram || []
157
162
  end
158
163
 
159
164
  def create_uml_class_attributes(klass) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
@@ -227,50 +227,65 @@ module Lutaml
227
227
  field_table
228
228
  end
229
229
 
230
- def format_class(node, hide_members) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
230
+ def format_class(node, hide_members) # rubocop:disable Metrics/MethodLength
231
231
  name = ["<B>#{node.name}</B>"]
232
232
  name.unshift("«#{node.keyword}»") if node.keyword
233
- name_html = <<~HEREDOC
234
- <TABLE BORDER="0" CELLPADDING="0" CELLSPACING="0">
235
- #{name.map { |n| %(<TR><TD ALIGN="CENTER">#{n}</TD></TR>) }.join('\n')}
236
- </TABLE>
237
- HEREDOC
233
+ name_html = build_name_table(name)
238
234
 
239
235
  field_table = format_member_rows(node.attributes, hide_members)
240
236
  method_table = if node.operations&.any?
241
237
  format_member_rows(node.operations, hide_members)
242
238
  end
243
- table_body = [name_html, field_table, method_table].map do |type|
244
- next if type.nil?
239
+ table_body = build_table_body(name_html, field_table, method_table)
240
+
241
+ <<~HEREDOC.chomp
242
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="10">
243
+ #{table_body}
244
+ </TABLE>
245
+ HEREDOC
246
+ end
247
+
248
+ def build_name_table(name_parts)
249
+ <<~HEREDOC
250
+ <TABLE BORDER="0" CELLPADDING="0" CELLSPACING="0">
251
+ #{name_parts.map { |n| %(<TR><TD ALIGN="CENTER">#{n}</TD></TR>) }.join('\n')}
252
+ </TABLE>
253
+ HEREDOC
254
+ end
245
255
 
256
+ def build_table_body(name_html, field_table, method_table)
257
+ [name_html, field_table, method_table].compact.filter_map do |type|
246
258
  <<~TEXT
247
259
  <TR>
248
260
  <TD>#{type}</TD>
249
261
  </TR>
250
262
  TEXT
251
- end
252
-
253
- <<~HEREDOC.chomp
254
- <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="10">
255
- #{table_body.compact.join("\n")}
256
- </TABLE>
257
- HEREDOC
263
+ end.join("\n")
258
264
  end
259
265
 
260
- def format_document(node) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
266
+ def format_document(node)
261
267
  @fontname = node.fontname || DEFAULT_CLASS_FONT
262
268
  @node["fontname"] = "#{@fontname}-bold"
263
269
 
270
+ hide_members, hide_other_classes = extract_fidelity_options(node)
271
+ classes = format_all_classes(node, hide_members)
272
+ associations = build_associations(node, hide_other_classes)
273
+
274
+ build_digraph(classes, associations)
275
+ end
276
+
277
+ def extract_fidelity_options(node)
264
278
  if node.fidelity
265
- hide_members = node.fidelity.hideMembers
266
- hide_other_classes = node.fidelity.hideOtherClasses
279
+ [node.fidelity.hideMembers, node.fidelity.hideOtherClasses]
280
+ else
281
+ [nil, nil]
267
282
  end
268
- classes = (node.classes +
269
- node.enums +
270
- node.data_types +
271
- node.primitives).map do |class_node|
272
- graph_node_name = generate_graph_name(class_node.name)
283
+ end
273
284
 
285
+ def format_all_classes(node, hide_members)
286
+ all_classes = node.classes + node.enums + node.data_types + node.primitives
287
+ all_classes.map do |class_node|
288
+ graph_node_name = generate_graph_name(class_node.name)
274
289
  <<~HEREDOC
275
290
  #{graph_node_name} [
276
291
  shape="plain"
@@ -278,25 +293,36 @@ module Lutaml
278
293
  label=<#{format_class(class_node, hide_members)}>]
279
294
  HEREDOC
280
295
  end.join("\n")
281
- associations = node.classes.filter_map(&:associations).flatten +
282
- node.associations
296
+ end
297
+
298
+ def build_associations(node, hide_other_classes)
299
+ associations = collect_all_associations(node)
283
300
  if node.groups
284
301
  associations = sort_by_document_grouping(node.groups,
285
302
  associations)
286
303
  end
304
+
287
305
  classes_names = node.classes.map(&:name)
288
- associations = associations.map do |assoc_node|
289
- if hide_other_classes &&
290
- !classes_names.include?(assoc_node.member_end)
291
- next
292
- end
306
+ format_filtered_associations(associations, classes_names,
307
+ hide_other_classes)
308
+ end
309
+
310
+ def collect_all_associations(node)
311
+ node.classes.filter_map(&:associations).flatten + node.associations
312
+ end
313
+
314
+ def format_filtered_associations(associations, classes_names,
315
+ hide_other_classes)
316
+ associations.filter_map do |assoc_node|
317
+ next if hide_other_classes && !classes_names.include?(assoc_node.member_end)
293
318
 
294
319
  format_relationship(assoc_node)
295
320
  end.join("\n")
321
+ end
296
322
 
297
- classes = classes.lines.map { |line| " #{line}" }.join.chomp
298
- associations = associations
299
- .lines.map { |line| " #{line}" }.join.chomp
323
+ def build_digraph(classes, associations)
324
+ indented_classes = indent_lines(classes)
325
+ indented_assocs = indent_lines(associations)
300
326
 
301
327
  <<~HEREDOC
302
328
  digraph G {
@@ -304,13 +330,17 @@ module Lutaml
304
330
  edge [#{@edge}]
305
331
  node [#{@node}]
306
332
 
307
- #{classes}
333
+ #{indented_classes}
308
334
 
309
- #{associations}
335
+ #{indented_assocs}
310
336
  }
311
337
  HEREDOC
312
338
  end
313
339
 
340
+ def indent_lines(text)
341
+ text.lines.map { |line| " #{line}" }.join.chomp
342
+ end
343
+
314
344
  protected
315
345
 
316
346
  def sort_by_document_grouping(groups, associations) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
@@ -55,7 +55,7 @@ module Lutaml
55
55
  # @param file_path [String] Path to the model file
56
56
  # @return [Lutaml::Uml::Document] Parsed UML document
57
57
  # @raise [ParseError] if parsing fails
58
- def parse(file_path) # rubocop:disable Metrics/MethodLength
58
+ def parse(file_path)
59
59
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
60
60
  @stats_mutex.synchronize { @parse_stats[:total_parses] += 1 }
61
61
  parse_succeeded = false
@@ -65,37 +65,35 @@ module Lutaml
65
65
  validate_file!(file_path) if should_validate_input?
66
66
  clear_errors_and_warnings
67
67
 
68
- begin
69
- # Pre-parsing hook
70
- before_parse(file_path)
71
-
72
- # Core parsing (implemented by subclasses)
73
- document = parse_internal(file_path)
68
+ parse_succeeded, result = execute_parse(file_path)
69
+ parse_handled = !parse_succeeded
70
+ result
71
+ ensure
72
+ record_parse_stats(parse_succeeded, parse_handled, start_time)
73
+ end
74
+ end
74
75
 
75
- # Post-parsing processing
76
- document = after_parse(document, file_path)
76
+ def execute_parse(file_path)
77
+ before_parse(file_path)
78
+ document = parse_internal(file_path)
79
+ document = after_parse(document, file_path)
80
+ validate_output!(document) if should_validate_output?
77
81
 
78
- # Validate output if requested
79
- validate_output!(document) if should_validate_output?
82
+ @stats_mutex.synchronize { @parse_stats[:successful_parses] += 1 }
83
+ [true, document]
84
+ rescue StandardError => e
85
+ [false, handle_parsing_error(e, file_path)]
86
+ end
80
87
 
81
- parse_succeeded = true
82
- @stats_mutex.synchronize { @parse_stats[:successful_parses] += 1 }
83
- document
84
- rescue StandardError => e
85
- parse_handled = true
86
- @stats_mutex.synchronize { @parse_stats[:failed_parses] += 1 }
87
- handle_parsing_error(e, file_path)
88
- end
89
- ensure
90
- unless parse_succeeded || parse_handled
91
- @stats_mutex.synchronize { @parse_stats[:failed_parses] += 1 }
92
- end
93
- duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
94
- @last_duration = duration
95
- @stats_mutex.synchronize do
96
- @parse_stats[:total_duration] += duration
97
- @parse_stats[:durations] << duration
98
- end
88
+ def record_parse_stats(succeeded, handled, start_time)
89
+ unless succeeded || handled
90
+ @stats_mutex.synchronize { @parse_stats[:failed_parses] += 1 }
91
+ end
92
+ duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
93
+ @last_duration = duration
94
+ @stats_mutex.synchronize do
95
+ @parse_stats[:total_duration] += duration
96
+ @parse_stats[:durations] << duration
99
97
  end
100
98
  end
101
99
 
@@ -11,147 +11,48 @@ module Lutaml
11
11
  def load_class_associations(object_id, object_guid)
12
12
  return [] if object_id.nil?
13
13
 
14
- associations = []
15
14
  normalized_owner_xmi_id = normalize_guid_to_xmi_format(object_guid,
16
15
  "EAID")
17
16
 
18
17
  assoc_connectors = database.connectors_for_object(object_id)
19
18
  .select { |c| ASSOC_TYPES.include?(c.connector_type) }
20
19
 
21
- assoc_connectors.each do |ea_connector|
22
- is_start = ea_connector.start_object_id == object_id
23
-
24
- owner_end_attribute_name = if is_start
25
- ea_connector.destrole
26
- else
27
- ea_connector.sourcerole
28
- end
29
-
30
- if owner_end_attribute_name.nil? || owner_end_attribute_name.empty?
31
- next
32
- end
33
-
34
- member_obj = if is_start
35
- find_object_by_id(ea_connector.end_object_id)
36
- else
37
- find_object_by_id(ea_connector.start_object_id)
38
- end
39
- next unless member_obj
40
-
41
- member_end_attribute_name = if is_start
42
- ea_connector.sourcerole
43
- else
44
- ea_connector.destrole
45
- end
46
- if member_end_attribute_name.nil? ||
47
- member_end_attribute_name.empty?
48
- member_end_attribute_name = member_obj.name
49
- end
50
-
51
- member_cardinality_str = if is_start
52
- ea_connector.destcard
53
- else
54
- ea_connector.sourcecard
55
- end
56
-
57
- associations << Lutaml::Uml::Association.new.tap do |assoc|
58
- assoc.xmi_id = normalize_guid_to_xmi_format(ea_connector.ea_guid,
59
- "EAID")
60
- assoc.name = ea_connector.name unless ea_connector.name.nil? || ea_connector.name.empty?
61
-
62
- assoc.owner_end = find_object_by_id(object_id)&.name
63
- assoc.owner_end_xmi_id = normalized_owner_xmi_id
64
- assoc.owner_end_attribute_name = owner_end_attribute_name
65
-
66
- assoc.member_end = member_obj.name
67
- assoc.member_end_xmi_id = normalize_guid_to_xmi_format(
68
- member_obj.ea_guid, "EAID"
69
- )
70
- assoc.member_end_attribute_name = member_end_attribute_name
71
-
72
- assoc.member_end_type = ea_connector.connector_type&.downcase
73
-
74
- if member_cardinality_str && !member_cardinality_str.empty?
75
- parsed = parse_cardinality(member_cardinality_str)
76
- if parsed[:min] || parsed[:max]
77
- assoc.member_end_cardinality = Lutaml::Uml::Cardinality.new
78
- .tap do |card|
79
- card.min = parsed[:min]
80
- card.max = parsed[:max]
81
- end
82
- end
83
- end
84
- end
20
+ assoc_connectors.filter_map do |ea_connector|
21
+ build_association(ea_connector, object_id, normalized_owner_xmi_id)
85
22
  end
86
-
87
- associations.compact
88
23
  end
89
24
 
90
25
  def load_association_attributes(object_id)
91
26
  return [] if object_id.nil?
92
27
 
93
- attributes = []
94
28
  assoc_connectors = database.connectors_for_object(object_id)
95
29
  .select { |c| ASSOC_TYPES.include?(c.connector_type) }
96
30
  obj = find_object_by_id(object_id)
97
31
  obj_pkg_name = find_package_name(obj&.package_id)
98
32
 
99
- assoc_connectors.each do |ea_connector|
100
- if ea_connector.start_object_id == object_id
101
- next if ea_connector.destrole.nil? || ea_connector.destrole.empty?
102
-
103
- target_obj = find_object_by_id(ea_connector.end_object_id)
104
- next unless target_obj
105
-
106
- target_obj_pkg_name = find_package_name(target_obj.package_id)
107
-
108
- attributes << create_association_attribute(
109
- name: ea_connector.destrole,
110
- type: target_obj.name,
111
- type_xmi_id: target_obj.ea_guid,
112
- association_xmi_id: ea_connector.ea_guid,
113
- cardinality: ea_connector.destcard,
114
- definition: ea_connector.notes,
115
- gen_name: obj.name,
116
- name_ns: obj_pkg_name,
117
- type_ns: target_obj_pkg_name,
118
- is_src: false,
119
- )
120
- elsif ea_connector.end_object_id == object_id
121
- next if ea_connector.sourcerole.nil? || ea_connector.sourcerole.empty?
122
-
123
- source_obj = find_object_by_id(ea_connector.start_object_id)
124
- next unless source_obj
125
-
126
- source_obj_pkg_name = find_package_name(source_obj.package_id)
127
-
128
- attributes << create_association_attribute(
129
- name: ea_connector.sourcerole,
130
- type: source_obj.name,
131
- type_xmi_id: source_obj.ea_guid,
132
- association_xmi_id: ea_connector.ea_guid,
133
- cardinality: ea_connector.sourcecard,
134
- definition: ea_connector.notes,
135
- gen_name: obj.name,
136
- name_ns: obj_pkg_name,
137
- type_ns: source_obj_pkg_name,
138
- )
139
- end
33
+ assoc_connectors.filter_map do |ea_connector|
34
+ build_connector_attribute(ea_connector, object_id, obj,
35
+ obj_pkg_name)
140
36
  end
37
+ end
141
38
 
142
- attributes.compact
39
+ def build_connector_attribute(ea_connector, object_id, obj,
40
+ obj_pkg_name)
41
+ if ea_connector.start_object_id == object_id
42
+ build_end_attribute(ea_connector, obj, obj_pkg_name)
43
+ elsif ea_connector.end_object_id == object_id
44
+ build_start_attribute(ea_connector, obj, obj_pkg_name)
45
+ end
143
46
  end
144
47
 
145
- def create_association_attribute( # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/ParameterLists
48
+ def create_association_attribute( # rubocop:disable Metrics/ParameterLists
146
49
  name:, type:, type_xmi_id:,
147
50
  association_xmi_id:, cardinality:, definition:,
148
51
  gen_name:, name_ns:, type_ns:, is_src: true
149
52
  )
150
53
  Lutaml::Uml::GeneralAttribute.new.tap do |attr|
151
- attr.name = name
152
- attr.type = type
153
- attr.gen_name = gen_name
154
- attr.definition = definition
54
+ assign_assoc_attr_basic(attr, name, type, gen_name, definition,
55
+ name_ns, type_ns)
155
56
  attr.xmi_id = normalize_guid_to_xmi_format(type_xmi_id, "EAID")
156
57
  attr.association = normalize_guid_to_xmi_format(
157
58
  association_xmi_id, "EAID"
@@ -160,23 +61,139 @@ module Lutaml
160
61
  attr.id = normalize_guid_to_xmi_src_dst_format(
161
62
  association_xmi_id, "EAID", is_src
162
63
  )
163
- attr.name_ns = name_ns
164
- attr.type_ns = type_ns
165
-
166
- if cardinality && !cardinality.empty?
167
- parsed = parse_cardinality(cardinality)
168
- if parsed[:min] || parsed[:max]
169
- attr.cardinality = Lutaml::Uml::Cardinality.new.tap do |card|
170
- card.min = parsed[:min]
171
- card.max = parsed[:max]
172
- end
173
- end
174
- end
64
+ attr.cardinality = build_cardinality(cardinality)
175
65
  end
176
66
  end
177
67
 
178
68
  private
179
69
 
70
+ def assign_assoc_attr_basic(attr, name, type, gen_name,
71
+ definition, name_ns, type_ns)
72
+ attr.name = name
73
+ attr.type = type
74
+ attr.gen_name = gen_name
75
+ attr.definition = definition
76
+ attr.name_ns = name_ns
77
+ attr.type_ns = type_ns
78
+ end
79
+
80
+ def build_association(ea_connector, object_id, normalized_owner_xmi_id)
81
+ is_start = ea_connector.start_object_id == object_id
82
+ owner_role = is_start ? ea_connector.destrole : ea_connector.sourcerole
83
+ return nil if owner_role.nil? || owner_role.empty?
84
+
85
+ member_obj = resolve_member_object(ea_connector, is_start)
86
+ return nil unless member_obj
87
+
88
+ member_role = resolve_member_role(ea_connector, is_start, member_obj)
89
+
90
+ build_association_record(ea_connector, object_id, normalized_owner_xmi_id,
91
+ owner_role, member_obj, member_role, is_start)
92
+ end
93
+
94
+ def build_association_record(ea_connector, object_id, owner_xmi_id,
95
+ owner_role, member_obj, member_role, is_start)
96
+ cardinality_str = is_start ? ea_connector.destcard : ea_connector.sourcecard
97
+
98
+ Lutaml::Uml::Association.new.tap do |assoc|
99
+ assoc.xmi_id = normalize_guid_to_xmi_format(ea_connector.ea_guid,
100
+ "EAID")
101
+ assign_assoc_name(assoc, ea_connector)
102
+ assign_association_ends(assoc, object_id, owner_xmi_id,
103
+ owner_role, member_obj, member_role)
104
+ assoc.member_end_type = ea_connector.connector_type&.downcase
105
+ assoc.member_end_cardinality = build_cardinality(cardinality_str)
106
+ end
107
+ end
108
+
109
+ def assign_assoc_name(assoc, ea_connector)
110
+ return if ea_connector.name.nil? || ea_connector.name.empty?
111
+
112
+ assoc.name = ea_connector.name
113
+ end
114
+
115
+ def assign_association_ends(assoc, object_id, owner_xmi_id,
116
+ owner_role, member_obj, member_role)
117
+ assoc.owner_end = find_object_by_id(object_id)&.name
118
+ assoc.owner_end_xmi_id = owner_xmi_id
119
+ assoc.owner_end_attribute_name = owner_role
120
+ assoc.member_end = member_obj.name
121
+ assoc.member_end_xmi_id = normalize_guid_to_xmi_format(
122
+ member_obj.ea_guid, "EAID"
123
+ )
124
+ assoc.member_end_attribute_name = member_role
125
+ end
126
+
127
+ def resolve_member_object(ea_connector, is_start)
128
+ peer_id = is_start ? ea_connector.end_object_id : ea_connector.start_object_id
129
+ find_object_by_id(peer_id)
130
+ end
131
+
132
+ def resolve_member_role(ea_connector, is_start, member_obj)
133
+ role = is_start ? ea_connector.sourcerole : ea_connector.destrole
134
+ role.nil? || role.empty? ? member_obj.name : role
135
+ end
136
+
137
+ def build_end_attribute(ea_connector, obj, obj_pkg_name)
138
+ return nil if ea_connector.destrole.nil? || ea_connector.destrole.empty?
139
+
140
+ target_obj = find_object_by_id(ea_connector.end_object_id)
141
+ return nil unless target_obj
142
+
143
+ build_directional_attribute(
144
+ role: ea_connector.destrole,
145
+ peer_obj: target_obj,
146
+ ea_connector: ea_connector,
147
+ cardinality: ea_connector.destcard,
148
+ obj: obj, obj_pkg_name: obj_pkg_name,
149
+ is_src: false
150
+ )
151
+ end
152
+
153
+ def build_start_attribute(ea_connector, obj, obj_pkg_name)
154
+ return nil if ea_connector.sourcerole.nil? || ea_connector.sourcerole.empty?
155
+
156
+ source_obj = find_object_by_id(ea_connector.start_object_id)
157
+ return nil unless source_obj
158
+
159
+ build_directional_attribute(
160
+ role: ea_connector.sourcerole,
161
+ peer_obj: source_obj,
162
+ ea_connector: ea_connector,
163
+ cardinality: ea_connector.sourcecard,
164
+ obj: obj, obj_pkg_name: obj_pkg_name
165
+ )
166
+ end
167
+
168
+ def build_directional_attribute(role:, peer_obj:, ea_connector:,
169
+ cardinality:, obj:, obj_pkg_name:,
170
+ is_src: true)
171
+ create_association_attribute(
172
+ name: role,
173
+ type: peer_obj.name,
174
+ type_xmi_id: peer_obj.ea_guid,
175
+ association_xmi_id: ea_connector.ea_guid,
176
+ cardinality: cardinality,
177
+ definition: ea_connector.notes,
178
+ gen_name: obj.name,
179
+ name_ns: obj_pkg_name,
180
+ type_ns: find_package_name(peer_obj.package_id),
181
+ is_src: is_src,
182
+ )
183
+ end
184
+
185
+ def build_cardinality(cardinality_str)
186
+ return nil unless cardinality_str && !cardinality_str.empty?
187
+
188
+ parsed = parse_cardinality(cardinality_str)
189
+ return nil unless parsed[:min] || parsed[:max]
190
+
191
+ Lutaml::Uml::Cardinality.new.tap do |card|
192
+ card.min = parsed[:min]
193
+ card.max = parsed[:max]
194
+ end
195
+ end
196
+
180
197
  def find_package_name(package_id)
181
198
  return nil if package_id.nil?
182
199