bimifc_model/resolver.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Entity resolution trait for looking up and resolving IFC entities
6
7use crate::{AttributeValue, DecodedEntity, EntityId, IfcType};
8use std::sync::Arc;
9
10/// Entity lookup and reference resolution
11///
12/// This trait provides the core functionality for accessing IFC entities
13/// and resolving entity references. Implementations should provide O(1)
14/// lookup by entity ID.
15///
16/// # Example
17///
18/// ```ignore
19/// use bimifc_model::{EntityResolver, EntityId, AttributeValue};
20///
21/// fn process_wall(resolver: &dyn EntityResolver, wall_id: EntityId) {
22/// if let Some(wall) = resolver.get(wall_id) {
23/// println!("Wall type: {:?}", wall.ifc_type);
24///
25/// // Resolve a reference attribute
26/// if let Some(ref_attr) = wall.get(5) {
27/// if let Some(related) = resolver.resolve_ref(ref_attr) {
28/// println!("Related entity: {:?}", related.ifc_type);
29/// }
30/// }
31/// }
32/// }
33/// ```
34pub trait EntityResolver: Send + Sync {
35 /// Get entity by ID
36 ///
37 /// Returns the decoded entity if it exists, wrapped in an Arc for
38 /// efficient sharing.
39 ///
40 /// # Arguments
41 /// * `id` - The entity ID to look up
42 ///
43 /// # Returns
44 /// `Some(Arc<DecodedEntity>)` if found, `None` otherwise
45 fn get(&self, id: EntityId) -> Option<Arc<DecodedEntity>>;
46
47 /// Resolve an entity reference from an attribute value
48 ///
49 /// If the attribute value is an EntityRef, this looks up and returns
50 /// the referenced entity.
51 ///
52 /// # Arguments
53 /// * `attr` - The attribute value that may contain an entity reference
54 ///
55 /// # Returns
56 /// `Some(Arc<DecodedEntity>)` if the attribute is a valid reference, `None` otherwise
57 fn resolve_ref(&self, attr: &AttributeValue) -> Option<Arc<DecodedEntity>> {
58 match attr {
59 AttributeValue::EntityRef(id) => self.get(*id),
60 _ => None,
61 }
62 }
63
64 /// Resolve a list of entity references
65 ///
66 /// If the attribute value is a List containing EntityRefs, this resolves
67 /// all of them and returns the entities.
68 ///
69 /// # Arguments
70 /// * `attr` - The attribute value that may contain a list of entity references
71 ///
72 /// # Returns
73 /// A vector of resolved entities (empty if the attribute is not a list or contains no refs)
74 fn resolve_ref_list(&self, attr: &AttributeValue) -> Vec<Arc<DecodedEntity>> {
75 match attr {
76 AttributeValue::List(items) => items
77 .iter()
78 .filter_map(|item| self.resolve_ref(item))
79 .collect(),
80 _ => Vec::new(),
81 }
82 }
83
84 /// Get all entities of a specific type
85 ///
86 /// # Arguments
87 /// * `ifc_type` - The IFC type to filter by
88 ///
89 /// # Returns
90 /// A vector of all entities matching the specified type
91 fn entities_by_type(&self, ifc_type: &IfcType) -> Vec<Arc<DecodedEntity>>;
92
93 /// Find entities by type name string
94 ///
95 /// This is useful for dynamic lookups where the type is not known at compile time.
96 ///
97 /// # Arguments
98 /// * `type_name` - The type name to search for (case-insensitive)
99 ///
100 /// # Returns
101 /// A vector of entities matching the type name
102 fn find_by_type_name(&self, type_name: &str) -> Vec<Arc<DecodedEntity>>;
103
104 /// Count entities of a specific type
105 ///
106 /// # Arguments
107 /// * `ifc_type` - The IFC type to count
108 ///
109 /// # Returns
110 /// The number of entities of the specified type
111 fn count_by_type(&self, ifc_type: &IfcType) -> usize;
112
113 /// Get all entity IDs in the model
114 ///
115 /// # Returns
116 /// A vector of all entity IDs
117 fn all_ids(&self) -> Vec<EntityId>;
118
119 /// Get total entity count
120 fn entity_count(&self) -> usize {
121 self.all_ids().len()
122 }
123
124 /// Fast raw bytes access for optimized parsing
125 ///
126 /// Returns the raw bytes of an entity's definition for parsers that
127 /// want to do direct parsing without going through the attribute system.
128 /// This is useful for performance-critical paths like coordinate parsing.
129 ///
130 /// # Arguments
131 /// * `id` - The entity ID to get raw bytes for
132 ///
133 /// # Returns
134 /// The raw bytes of the entity definition, or `None` if not available
135 fn raw_bytes(&self, id: EntityId) -> Option<&[u8]>;
136}
137
138/// Extension methods for EntityResolver
139pub trait EntityResolverExt: EntityResolver {
140 /// Get entity by raw u32 ID
141 fn get_by_u32(&self, id: u32) -> Option<Arc<DecodedEntity>> {
142 self.get(EntityId(id))
143 }
144
145 /// Check if an entity exists
146 fn exists(&self, id: EntityId) -> bool {
147 self.get(id).is_some()
148 }
149
150 /// Get entity or return error
151 fn get_or_err(&self, id: EntityId) -> crate::Result<Arc<DecodedEntity>> {
152 self.get(id).ok_or(crate::ParseError::EntityNotFound(id))
153 }
154
155 /// Resolve reference or return error
156 fn resolve_ref_or_err(
157 &self,
158 entity_id: EntityId,
159 attr_index: usize,
160 attr: &AttributeValue,
161 ) -> crate::Result<Arc<DecodedEntity>> {
162 self.resolve_ref(attr)
163 .ok_or(crate::ParseError::InvalidReference {
164 entity: entity_id,
165 attribute: attr_index,
166 })
167 }
168}
169
170// Blanket implementation for all EntityResolver types
171impl<T: EntityResolver + ?Sized> EntityResolverExt for T {}