ifc_classification/query/
hierarchy.rs1use std::collections::HashMap;
4
5use ifc_model::{Budget, EntityId};
6
7use crate::view::{required_ref, required_refs};
8use crate::{
9 ClassificationAssignment, ClassificationError, ClassificationReference, ClassificationResult,
10 ClassificationSystem, ClassificationView,
11};
12
13#[derive(Debug, Clone)]
15#[non_exhaustive]
16pub struct ClassificationHierarchy<'m> {
17 pub references: Vec<ClassificationReference<'m>>,
19 pub system: Option<ClassificationSystem<'m>>,
21}
22
23#[derive(Debug, Clone)]
25#[non_exhaustive]
26pub struct EffectiveClassifications<'m> {
27 pub occurrence: Vec<ClassificationAssignment<'m>>,
29 pub type_object: Option<EntityId>,
31 pub inherited: Vec<ClassificationAssignment<'m>>,
33}
34
35fn require_select(
39 view: ClassificationView<'_>,
40 source: EntityId,
41 target: EntityId,
42) -> ClassificationResult<()> {
43 const RELATION: &str = "IFCRELASSOCIATESCLASSIFICATION";
44 let entity = view
45 .model()
46 .get(target)
47 .ok_or(ClassificationError::DanglingReference {
48 entity: RELATION,
49 id: source,
50 attribute: "RelatingClassification",
51 target,
52 })?;
53 let release = view.release();
54 if release.accepts(
55 RELATION,
56 source,
57 "RelatingClassification",
58 &entity.type_name,
59 )? {
60 Ok(())
61 } else {
62 Err(ClassificationError::ReferenceType {
63 entity: RELATION,
64 id: source,
65 attribute: "RelatingClassification",
66 target,
67 expected: release.declared_type(RELATION, source, "RelatingClassification")?,
68 actual: entity.type_name.to_string(),
69 })
70 }
71}
72
73impl<'m> ClassificationView<'m> {
74 pub fn hierarchy_from(
82 self,
83 leaf: EntityId,
84 budget: Budget,
85 ) -> ClassificationResult<ClassificationHierarchy<'m>> {
86 let mut current = leaf;
87 let mut from_reference = None;
88 let mut references: Vec<ClassificationReference<'m>> = Vec::new();
89 let mut positions = HashMap::new();
90 let mut visited_nodes = 0usize;
91 let mut edges_followed = 0usize;
92
93 loop {
94 if let Some(&start) = positions.get(¤t) {
95 let mut path: Vec<_> = references[start..].iter().map(|r| r.id()).collect();
96 path.push(current);
97 return Err(ClassificationError::Cycle { path });
98 }
99 if visited_nodes >= budget.max_nodes {
100 return Err(ClassificationError::BudgetExceeded {
101 max_depth: budget.max_depth,
102 max_nodes: budget.max_nodes,
103 });
104 }
105 let entity = self.model().get(current).ok_or_else(|| {
106 from_reference.map_or(
107 ClassificationError::UnknownEntity { id: current },
108 |source| ClassificationError::DanglingReference {
109 entity: "IFCCLASSIFICATIONREFERENCE",
110 id: source,
111 attribute: "ReferencedSource",
112 target: current,
113 },
114 )
115 })?;
116 visited_nodes += 1;
117
118 if let Some(source) = from_reference {
123 let release = self.release();
124 if !release.accepts(
125 "IFCCLASSIFICATIONREFERENCE",
126 source,
127 "ReferencedSource",
128 &entity.type_name,
129 )? {
130 return Err(ClassificationError::ReferenceType {
131 entity: "IFCCLASSIFICATIONREFERENCE",
132 id: source,
133 attribute: "ReferencedSource",
134 target: current,
135 expected: release.declared_type(
136 "IFCCLASSIFICATIONREFERENCE",
137 source,
138 "ReferencedSource",
139 )?,
140 actual: entity.type_name.to_string(),
141 });
142 }
143 }
144 if entity.is_type("IFCCLASSIFICATION") && from_reference.is_some() {
145 return Ok(ClassificationHierarchy {
146 references,
147 system: Some(ClassificationSystem::try_bound(
148 current,
149 entity,
150 self.release(),
151 )?),
152 });
153 }
154 if !entity.is_type("IFCCLASSIFICATIONREFERENCE") {
155 if let Some(source) = from_reference {
156 return Err(ClassificationError::ReferenceType {
157 entity: "IFCCLASSIFICATIONREFERENCE",
158 id: source,
159 attribute: "ReferencedSource",
160 target: current,
161 expected: "IfcClassificationReferenceSelect",
162 actual: entity.type_name.to_string(),
163 });
164 }
165 return Err(ClassificationError::WrongEntityType {
166 expected: "IFCCLASSIFICATIONREFERENCE",
167 actual: entity.type_name.to_string(),
168 });
169 }
170
171 positions.insert(current, references.len());
172 let reference = ClassificationReference::try_bound(current, entity, self.release())?;
173 let source = reference.referenced_source_id()?;
174 references.push(reference);
175 let Some(source) = source else {
176 return Ok(ClassificationHierarchy {
177 references,
178 system: None,
179 });
180 };
181 if edges_followed >= budget.max_depth {
182 return Err(ClassificationError::BudgetExceeded {
183 max_depth: budget.max_depth,
184 max_nodes: budget.max_nodes,
185 });
186 }
187 edges_followed += 1;
188 from_reference = Some(current);
189 current = source;
190 }
191 }
192
193 pub fn children_of(
195 self,
196 source: EntityId,
197 ) -> ClassificationResult<Vec<ClassificationReference<'m>>> {
198 let source_entity = self
199 .model()
200 .get(source)
201 .ok_or(ClassificationError::UnknownEntity { id: source })?;
202 let release = self.release();
203 if !release.accepts(
204 "IFCCLASSIFICATIONREFERENCE",
205 source,
206 "ReferencedSource",
207 &source_entity.type_name,
208 )? {
209 return Err(ClassificationError::ReferenceType {
210 entity: "IFCCLASSIFICATIONREFERENCE",
211 id: source,
212 attribute: "ReferencedSource",
213 target: source,
214 expected: release.declared_type(
215 "IFCCLASSIFICATIONREFERENCE",
216 source,
217 "ReferencedSource",
218 )?,
219 actual: source_entity.type_name.to_string(),
220 });
221 }
222 let mut out = Vec::new();
223 for reference in self.references() {
224 if reference.referenced_source_id()? == Some(source) {
225 out.push(reference);
226 }
227 }
228 out.sort_by_key(|reference| reference.id());
229 Ok(out)
230 }
231
232 pub fn effective_classifications(
234 self,
235 object: EntityId,
236 ) -> ClassificationResult<EffectiveClassifications<'m>> {
237 if self.model().get(object).is_none() {
238 return Err(ClassificationError::UnknownEntity { id: object });
239 }
240 let occurrence = self.classification_assignments_for(object)?;
241 for assignment in &occurrence {
242 require_select(
243 self,
244 assignment.id(),
245 assignment.relating_classification_id()?,
246 )?;
247 }
248
249 const TYPING: &str = "IFCRELDEFINESBYTYPE";
250 let release = self.release();
251 let mut types = Vec::new();
252 for (relationship_id, entity) in self.model().of_type(TYPING) {
253 if required_refs(
254 TYPING,
255 relationship_id,
256 entity,
257 release.slot(TYPING, relationship_id, "RelatedObjects")?,
258 "RelatedObjects",
259 )?
260 .contains(&object)
261 {
262 let type_id = required_ref(
263 TYPING,
264 relationship_id,
265 entity,
266 release.slot(TYPING, relationship_id, "RelatingType")?,
267 "RelatingType",
268 )?;
269 self.model()
270 .get(type_id)
271 .ok_or(ClassificationError::DanglingReference {
272 entity: "IFCRELDEFINESBYTYPE",
273 id: relationship_id,
274 attribute: "RelatingType",
275 target: type_id,
276 })?;
277 types.push(type_id);
278 }
279 }
280 if types.len() > 1 {
281 return Err(ClassificationError::AmbiguousType {
282 object,
283 count: types.len(),
284 });
285 }
286 let type_id = types.first().copied();
287 let inherited_from_type = if let Some(type_id) = type_id {
288 let assignments = self.classification_assignments_for(type_id)?;
289 for assignment in &assignments {
290 require_select(
291 self,
292 assignment.id(),
293 assignment.relating_classification_id()?,
294 )?;
295 }
296 assignments
297 } else {
298 Vec::new()
299 };
300 Ok(EffectiveClassifications {
301 occurrence,
302 type_object: type_id,
303 inherited: inherited_from_type,
304 })
305 }
306}