Class: Lutaml::UmlRepository::IndexBuilder
- Inherits:
-
Object
- Object
- Lutaml::UmlRepository::IndexBuilder
- Defined in:
- lib/lutaml/uml_repository/index_builder.rb,
lib/lutaml/uml_repository/index_builders/class_index.rb,
lib/lutaml/uml_repository/index_builders/package_index.rb,
lib/lutaml/uml_repository/index_builders/association_index.rb
Overview
IndexBuilder builds fast lookup indexes from a Lutaml::Uml::Document
This class creates immutable hash indexes that enable O(1) lookups for:
-
Package paths (e.g., “ModelRoot::i-UR::urf”)
-
Qualified names (e.g., “ModelRoot::i-UR::urf::Building”)
-
Stereotypes (e.g., “featureType” => [Class, Class, …])
-
Inheritance graph (parent_qname => [child_qname, …])
-
Diagram index (package_id => [Diagram, …])
-
Package to path mapping (package_id => path)
-
Class to qualified name mapping (class_id => qualified_name)
-
Classes (class_id => Class)
-
Associations (association_id => Association)
All indexes are frozen to ensure immutability.
Constant Summary collapse
- ROOT_PACKAGE_NAME =
"ModelRoot"
Instance Attribute Summary collapse
-
#associations ⇒ Object
readonly
Returns the value of attribute associations.
-
#class_to_qname ⇒ Object
readonly
Returns the value of attribute class_to_qname.
-
#classes ⇒ Object
readonly
Returns the value of attribute classes.
-
#diagram_index ⇒ Object
readonly
Returns the value of attribute diagram_index.
-
#inheritance_graph ⇒ Object
readonly
Returns the value of attribute inheritance_graph.
-
#package_paths ⇒ Object
Returns the value of attribute package_paths.
-
#package_to_path ⇒ Object
readonly
Returns the value of attribute package_to_path.
-
#qualified_names ⇒ Object
Returns the value of attribute qualified_names.
-
#stereotypes ⇒ Object
readonly
Returns the value of attribute stereotypes.
Class Method Summary collapse
-
.build_all(document) ⇒ Hash
Build all indexes from a UML document.
- .build_associations(document) ⇒ Object
- .build_class_to_qname(document) ⇒ Object
- .build_classes(document) ⇒ Object
-
.build_diagram_index(document, indexes) ⇒ Hash
Build diagram index.
-
.build_inheritance_graph(document, indexes) ⇒ Hash
Build inheritance graph index.
-
.build_package_paths(document) ⇒ Hash
Build package paths index.
- .build_package_to_path(document) ⇒ Object
-
.build_qualified_names(document) ⇒ Hash
Build qualified names index.
-
.build_stereotypes(document) ⇒ Hash
Build stereotypes index.
Instance Method Summary collapse
-
#build_all ⇒ Hash
Build all indexes and return them as a frozen hash.
- #build_association_index ⇒ Object
-
#build_diagram_index ⇒ Object
Build the diagram index.
-
#build_inheritance_graph_index ⇒ Object
Build the inheritance graph index.
-
#build_package_path(name, parent_path) ⇒ String
Build a package path from a package name and parent path.
-
#build_package_path_index ⇒ Object
Build the package path index.
-
#build_qualified_name_index ⇒ Object
Build the qualified name index.
-
#build_stereotype_index ⇒ Object
Build the stereotype index.
-
#extract_parent_name(generalization) ⇒ String?
Extract parent name from generalization object.
- #has_stereotype?(classifier) ⇒ Boolean
-
#index_by_stereotype(classifiers) ⇒ Object
Index classifiers by their stereotypes.
- #index_class_level_associations ⇒ Object
- #index_classifier(classifier, package_path) ⇒ Object
-
#index_classifiers(classifiers, package_path) ⇒ Object
Index classifiers (classes, data types, enums) by their qualified names.
- #index_document_associations ⇒ Object
- #index_document_classifiers ⇒ Object
- #index_document_stereotypes ⇒ Object
- #index_generalization_edge(child_qname, klass, package_path) ⇒ Object
- #index_inheritance_assoc_edges(child_qname, klass, package_path) ⇒ Object
- #index_inheritance_edge(child_qname, assoc, package_path) ⇒ Object
- #index_package_classifiers ⇒ Object
- #index_package_stereotypes ⇒ Object
-
#initialize(document) ⇒ IndexBuilder
constructor
A new instance of IndexBuilder.
- #klassifiable?(klass) ⇒ Boolean
-
#name_from_general(generalization) ⇒ String?
Resolve a class name to its qualified name.
-
#process_generalizations(classes, package_path) ⇒ Object
Process generalization relationships to build inheritance graph.
- #resolve_parent_name_from_assoc(assoc) ⇒ Object
- #resolve_qualified_name(name, current_package_path) ⇒ Object
-
#traverse_packages(packages, parent_path: nil) {|package, path| ... } ⇒ Object
Traverse packages recursively, yielding each package with its path.
Constructor Details
#initialize(document) ⇒ IndexBuilder
Returns a new instance of IndexBuilder.
141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/lutaml/uml_repository/index_builder.rb', line 141 def initialize(document) @document = document @package_paths = {} @qualified_names = {} @stereotypes = {} @inheritance_graph = {} @diagram_index = {} @package_to_path = {} @class_to_qname = {} @classes = {} @associations = {} @simple_name_to_qnames = {} @package_to_classes = {} end |
Instance Attribute Details
#associations ⇒ Object (readonly)
Returns the value of attribute associations.
157 158 159 |
# File 'lib/lutaml/uml_repository/index_builder.rb', line 157 def associations @associations end |
#class_to_qname ⇒ Object (readonly)
Returns the value of attribute class_to_qname.
157 158 159 |
# File 'lib/lutaml/uml_repository/index_builder.rb', line 157 def class_to_qname @class_to_qname end |
#classes ⇒ Object (readonly)
Returns the value of attribute classes.
157 158 159 |
# File 'lib/lutaml/uml_repository/index_builder.rb', line 157 def classes @classes end |
#diagram_index ⇒ Object (readonly)
Returns the value of attribute diagram_index.
157 158 159 |
# File 'lib/lutaml/uml_repository/index_builder.rb', line 157 def diagram_index @diagram_index end |
#inheritance_graph ⇒ Object (readonly)
Returns the value of attribute inheritance_graph.
157 158 159 |
# File 'lib/lutaml/uml_repository/index_builder.rb', line 157 def inheritance_graph @inheritance_graph end |
#package_paths ⇒ Object
Returns the value of attribute package_paths.
156 157 158 |
# File 'lib/lutaml/uml_repository/index_builder.rb', line 156 def package_paths @package_paths end |
#package_to_path ⇒ Object (readonly)
Returns the value of attribute package_to_path.
157 158 159 |
# File 'lib/lutaml/uml_repository/index_builder.rb', line 157 def package_to_path @package_to_path end |
#qualified_names ⇒ Object
Returns the value of attribute qualified_names.
156 157 158 |
# File 'lib/lutaml/uml_repository/index_builder.rb', line 156 def qualified_names @qualified_names end |
#stereotypes ⇒ Object (readonly)
Returns the value of attribute stereotypes.
157 158 159 |
# File 'lib/lutaml/uml_repository/index_builder.rb', line 157 def stereotypes @stereotypes end |
Class Method Details
.build_all(document) ⇒ Hash
Build all indexes from a UML document
48 49 50 |
# File 'lib/lutaml/uml_repository/index_builder.rb', line 48 def self.build_all(document) new(document).build_all end |
.build_associations(document) ⇒ Object
90 91 92 93 94 95 96 97 |
# File 'lib/lutaml/uml_repository/index_builder.rb', line 90 def self.build_associations(document) builder = new(document) # build_association_index needs @qualified_names to collect # class-level associations builder.build_qualified_name_index builder.build_association_index builder.associations.freeze end |
.build_class_to_qname(document) ⇒ Object
78 79 80 81 82 |
# File 'lib/lutaml/uml_repository/index_builder.rb', line 78 def self.build_class_to_qname(document) builder = new(document) builder.build_qualified_name_index builder.class_to_qname.freeze end |
.build_classes(document) ⇒ Object
84 85 86 87 88 |
# File 'lib/lutaml/uml_repository/index_builder.rb', line 84 def self.build_classes(document) builder = new(document) builder.build_qualified_name_index builder.classes.freeze end |
.build_diagram_index(document, indexes) ⇒ Hash
Build diagram index
130 131 132 133 134 135 136 137 138 139 |
# File 'lib/lutaml/uml_repository/index_builder.rb', line 130 def self.build_diagram_index(document, indexes) builder = new(document) if indexes && indexes[:package_paths] builder.package_paths = indexes[:package_paths] else builder.build_package_path_index end builder.build_diagram_index builder.diagram_index.freeze end |
.build_inheritance_graph(document, indexes) ⇒ Hash
Build inheritance graph index
114 115 116 117 118 119 120 121 122 123 |
# File 'lib/lutaml/uml_repository/index_builder.rb', line 114 def self.build_inheritance_graph(document, indexes) builder = new(document) if indexes && indexes[:qualified_names] builder.qualified_names = indexes[:qualified_names] else builder.build_qualified_name_index end builder.build_inheritance_graph_index builder.inheritance_graph.freeze end |
.build_package_paths(document) ⇒ Hash
Build package paths index
56 57 58 59 60 |
# File 'lib/lutaml/uml_repository/index_builder.rb', line 56 def self.build_package_paths(document) builder = new(document) builder.build_package_path_index builder.package_paths.freeze end |
.build_package_to_path(document) ⇒ Object
62 63 64 65 66 |
# File 'lib/lutaml/uml_repository/index_builder.rb', line 62 def self.build_package_to_path(document) builder = new(document) builder.build_package_path_index builder.package_to_path.freeze end |
.build_qualified_names(document) ⇒ Hash
Build qualified names index
72 73 74 75 76 |
# File 'lib/lutaml/uml_repository/index_builder.rb', line 72 def self.build_qualified_names(document) builder = new(document) builder.build_qualified_name_index builder.qualified_names.freeze end |
.build_stereotypes(document) ⇒ Hash
Build stereotypes index
103 104 105 106 107 |
# File 'lib/lutaml/uml_repository/index_builder.rb', line 103 def self.build_stereotypes(document) builder = new(document) builder.build_stereotype_index builder.stereotypes.freeze end |
Instance Method Details
#build_all ⇒ Hash
Build all indexes and return them as a frozen hash
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/lutaml/uml_repository/index_builder.rb', line 163 def build_all # rubocop:disable Metrics/AbcSize,Metrics/MethodLength build_package_path_index build_qualified_name_index build_stereotype_index build_inheritance_graph_index build_diagram_index build_association_index { package_paths: @package_paths.freeze, qualified_names: @qualified_names.freeze, stereotypes: @stereotypes.freeze, inheritance_graph: @inheritance_graph.freeze, diagram_index: @diagram_index.freeze, package_to_path: @package_to_path.freeze, class_to_qname: @class_to_qname.freeze, classes: @classes.freeze, associations: @associations.freeze, package_to_classes: plain_hash(@package_to_classes).freeze, }.freeze end |
#build_association_index ⇒ Object
6 7 8 9 |
# File 'lib/lutaml/uml_repository/index_builders/association_index.rb', line 6 def build_association_index index_document_associations index_class_level_associations end |
#build_diagram_index ⇒ Object
Build the diagram index
Creates a hash mapping package IDs/paths to arrays of Diagram objects:
"package_id" => [Diagram{}, Diagram{}]
190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/lutaml/uml_repository/index_builder.rb', line 190 def build_diagram_index # Traverse packages and collect diagrams traverse_packages(@document.packages) do |package, path| next unless package.diagrams && !package.diagrams.empty? # Index by package ID if available, otherwise by path key = package.xmi_id || path @diagram_index[key] ||= [] @diagram_index[key].concat(package.diagrams) end end |
#build_inheritance_graph_index ⇒ Object
Build the inheritance graph index
Creates a hash mapping parent qualified names to arrays of child qualified names:
"ModelRoot::Parent" => ["ModelRoot::Child1", "ModelRoot::Child2"]
41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/lutaml/uml_repository/index_builders/association_index.rb', line 41 def build_inheritance_graph_index # Process top-level classes if @document.classes process_generalizations(@document.classes, ROOT_PACKAGE_NAME) end # Process classes in packages traverse_packages(@document.packages, parent_path: ROOT_PACKAGE_NAME) do |package, path| process_generalizations(package.classes, path) if package.classes end end |
#build_package_path(name, parent_path) ⇒ String
Build a package path from a package name and parent path
50 51 52 53 54 |
# File 'lib/lutaml/uml_repository/index_builders/package_index.rb', line 50 def build_package_path(name, parent_path) return name unless parent_path "#{parent_path}::#{name}" end |
#build_package_path_index ⇒ Object
Build the package path index
Creates a hash mapping package paths to Package objects:
"ModelRoot" => Package{},
"ModelRoot::i-UR" => Package{},
"ModelRoot::i-UR::urf" => Package{}
13 14 15 16 17 18 19 20 21 22 23 |
# File 'lib/lutaml/uml_repository/index_builders/package_index.rb', line 13 def build_package_path_index # Add root package if it exists @package_paths[ROOT_PACKAGE_NAME] = @document if @document # Traverse all packages recursively traverse_packages(@document.packages, parent_path: ROOT_PACKAGE_NAME) do |package, path| @package_paths[path] = package @package_to_path[package.xmi_id] = path if package.xmi_id end end |
#build_qualified_name_index ⇒ Object
Build the qualified name index
Creates a hash mapping qualified names to Class/DataType/Enum objects:
"ModelRoot::i-UR::urf::Building" => Class{}
11 12 13 14 |
# File 'lib/lutaml/uml_repository/index_builders/class_index.rb', line 11 def build_qualified_name_index index_document_classifiers index_package_classifiers end |
#build_stereotype_index ⇒ Object
Build the stereotype index
Creates a hash grouping classes by their stereotype:
"featureType" => [Class{}, Class{}],
"dataType" => [Class{}]
43 44 45 46 |
# File 'lib/lutaml/uml_repository/index_builders/class_index.rb', line 43 def build_stereotype_index index_document_stereotypes index_package_stereotypes end |
#extract_parent_name(generalization) ⇒ String?
Extract parent name from generalization object
Generalization object
117 118 119 120 121 |
# File 'lib/lutaml/uml_repository/index_builders/association_index.rb', line 117 def extract_parent_name(generalization) return nil unless generalization name_from_general(generalization) || generalization.name end |
#has_stereotype?(classifier) ⇒ Boolean
100 101 102 |
# File 'lib/lutaml/uml_repository/index_builders/class_index.rb', line 100 def has_stereotype?(classifier) classifier.stereotype && !classifier.stereotype.empty? end |
#index_by_stereotype(classifiers) ⇒ Object
Index classifiers by their stereotypes
88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/lutaml/uml_repository/index_builders/class_index.rb', line 88 def index_by_stereotype(classifiers) return unless classifiers classifiers.each do |classifier| next unless has_stereotype?(classifier) Array(classifier.stereotype).each do |stereotype| (@stereotypes[stereotype] ||= []) << classifier end end end |
#index_class_level_associations ⇒ Object
19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/lutaml/uml_repository/index_builders/association_index.rb', line 19 def index_class_level_associations @qualified_names.each_value do |klass| next unless klassifiable?(klass) && klass.associations klass.associations.each do |assoc| next unless assoc.xmi_id @associations[assoc.xmi_id] ||= assoc end end end |
#index_classifier(classifier, package_path) ⇒ Object
76 77 78 79 80 81 82 83 |
# File 'lib/lutaml/uml_repository/index_builders/class_index.rb', line 76 def index_classifier(classifier, package_path) qualified_name = "#{package_path}::#{classifier.name}" @qualified_names[qualified_name] = classifier @class_to_qname[classifier.xmi_id] = qualified_name if classifier.xmi_id @classes[classifier.xmi_id] = classifier if classifier.xmi_id (@simple_name_to_qnames[classifier.name] ||= []) << qualified_name (@package_to_classes[package_path] ||= []) << classifier end |
#index_classifiers(classifiers, package_path) ⇒ Object
Index classifiers (classes, data types, enums) by their qualified names
66 67 68 69 70 71 72 73 74 |
# File 'lib/lutaml/uml_repository/index_builders/class_index.rb', line 66 def index_classifiers(classifiers, package_path) return unless classifiers classifiers.each do |classifier| next unless classifier.name index_classifier(classifier, package_path) end end |
#index_document_associations ⇒ Object
11 12 13 14 15 16 17 |
# File 'lib/lutaml/uml_repository/index_builders/association_index.rb', line 11 def index_document_associations @document.associations&.each do |assoc| next unless assoc.xmi_id @associations[assoc.xmi_id] = assoc end end |
#index_document_classifiers ⇒ Object
16 17 18 19 20 21 22 23 24 25 26 |
# File 'lib/lutaml/uml_repository/index_builders/class_index.rb', line 16 def index_document_classifiers if @document.classes index_classifiers(@document.classes, ROOT_PACKAGE_NAME) end if @document.data_types index_classifiers(@document.data_types, ROOT_PACKAGE_NAME) end index_classifiers(@document.enums, ROOT_PACKAGE_NAME) if @document.enums end |
#index_document_stereotypes ⇒ Object
48 49 50 51 52 |
# File 'lib/lutaml/uml_repository/index_builders/class_index.rb', line 48 def index_document_stereotypes index_by_stereotype(@document.classes) if @document.classes index_by_stereotype(@document.data_types) if @document.data_types index_by_stereotype(@document.enums) if @document.enums end |
#index_generalization_edge(child_qname, klass, package_path) ⇒ Object
71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/lutaml/uml_repository/index_builders/association_index.rb', line 71 def index_generalization_edge(child_qname, klass, package_path) return unless klass.generalization parent_name = extract_parent_name(klass.generalization) return unless parent_name parent_qname = resolve_qualified_name(parent_name, package_path) return unless parent_qname && child_qname != parent_qname (@inheritance_graph[parent_qname] ||= []) << child_qname end |
#index_inheritance_assoc_edges(child_qname, klass, package_path) ⇒ Object
83 84 85 86 87 88 89 90 91 92 |
# File 'lib/lutaml/uml_repository/index_builders/association_index.rb', line 83 def index_inheritance_assoc_edges(child_qname, klass, package_path) return unless klass.associations klass.associations .select { |assoc| assoc.member_end_type == "inheritance" } .each do |assoc| index_inheritance_edge(child_qname, assoc, package_path) end end |
#index_inheritance_edge(child_qname, assoc, package_path) ⇒ Object
94 95 96 97 98 99 100 101 102 |
# File 'lib/lutaml/uml_repository/index_builders/association_index.rb', line 94 def index_inheritance_edge(child_qname, assoc, package_path) parent_name = resolve_parent_name_from_assoc(assoc) return unless parent_name parent_qname = resolve_qualified_name(parent_name, package_path) return unless parent_qname && child_qname != parent_qname (@inheritance_graph[parent_qname] ||= []) << child_qname end |
#index_package_classifiers ⇒ Object
28 29 30 31 32 33 34 35 |
# File 'lib/lutaml/uml_repository/index_builders/class_index.rb', line 28 def index_package_classifiers traverse_packages(@document.packages, parent_path: ROOT_PACKAGE_NAME) do |package, path| index_classifiers(package.classes, path) if package.classes index_classifiers(package.data_types, path) if package.data_types index_classifiers(package.enums, path) if package.enums end end |
#index_package_stereotypes ⇒ Object
54 55 56 57 58 59 60 |
# File 'lib/lutaml/uml_repository/index_builders/class_index.rb', line 54 def index_package_stereotypes traverse_packages(@document.packages) do |package, _path| index_by_stereotype(package.classes) if package.classes index_by_stereotype(package.data_types) if package.data_types index_by_stereotype(package.enums) if package.enums end end |
#klassifiable?(klass) ⇒ Boolean
31 32 33 |
# File 'lib/lutaml/uml_repository/index_builders/association_index.rb', line 31 def klassifiable?(klass) klass.is_a?(Lutaml::Uml::Class) || klass.is_a?(Lutaml::Uml::DataType) end |
#name_from_general(generalization) ⇒ String?
Resolve a class name to its qualified name
This is a simplified resolution that checks:
-
Same package
-
Already qualified name in index
132 133 134 135 136 137 |
# File 'lib/lutaml/uml_repository/index_builders/association_index.rb', line 132 def name_from_general(generalization) parent = generalization.general return nil unless parent parent.respond_to?(:name) ? parent.name : parent.to_s end |
#process_generalizations(classes, package_path) ⇒ Object
Process generalization relationships to build inheritance graph
59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/lutaml/uml_repository/index_builders/association_index.rb', line 59 def process_generalizations(classes, package_path) return unless classes classes.each do |klass| next unless klass.name child_qname = "#{package_path}::#{klass.name}" index_generalization_edge(child_qname, klass, package_path) index_inheritance_assoc_edges(child_qname, klass, package_path) end end |
#resolve_parent_name_from_assoc(assoc) ⇒ Object
104 105 106 107 108 109 110 |
# File 'lib/lutaml/uml_repository/index_builders/association_index.rb', line 104 def resolve_parent_name_from_assoc(assoc) parent_name = assoc.member_end return nil unless parent_name parent_name = parent_name.name if parent_name.is_a?(Lutaml::Uml::Generalization) parent_name.is_a?(String) && !parent_name.empty? ? parent_name : nil end |
#resolve_qualified_name(name, current_package_path) ⇒ Object
139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/lutaml/uml_repository/index_builders/association_index.rb', line 139 def resolve_qualified_name(name, current_package_path) # If name contains "::", it might already be qualified return name if @qualified_names.key?(name) # Try in current package local_qname = "#{current_package_path}::#{name}" return local_qname if @qualified_names.key?(local_qname) # O(1) lookup using reverse index instead of O(n) scan candidates = @simple_name_to_qnames[name] candidates&.first end |
#traverse_packages(packages, parent_path: nil) {|package, path| ... } ⇒ Object
Traverse packages recursively, yielding each package with its path
30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/lutaml/uml_repository/index_builders/package_index.rb', line 30 def traverse_packages(packages, parent_path: nil, &block) return unless packages packages.each do |package| path = build_package_path(package.name, parent_path) yield package, path if block # Recursively traverse nested packages if package.packages traverse_packages(package.packages, parent_path: path, &block) end end end |