1use std::collections::{HashMap, HashSet};
39
40use crate::attribute::Attribute;
41use crate::entity::EntityDef;
42use crate::types::{TypeDef, TypeKind};
43use crate::version::SchemaVersion;
44
45const MAX_CHAIN_DEPTH: usize = 64;
50
51#[derive(Debug, Clone)]
56pub struct Schema {
57 name: String,
58 entities: Vec<EntityDef>,
61 types: Vec<TypeDef>,
62 entity_index: HashMap<String, usize>,
64 type_index: HashMap<String, usize>,
65 children: HashMap<String, Vec<String>>,
69}
70
71impl PartialEq for Schema {
73 fn eq(&self, other: &Self) -> bool {
74 self.name == other.name && self.entities == other.entities && self.types == other.types
75 }
76}
77
78impl Eq for Schema {}
79
80impl Schema {
81 #[must_use]
86 pub fn new(name: impl Into<String>, entities: Vec<EntityDef>, types: Vec<TypeDef>) -> Self {
87 let (entities, entity_index) = index_by_name(entities, |entity| &entity.name);
88 let (types, type_index) = index_by_name(types, |type_def| &type_def.name);
89 let mut children: HashMap<String, Vec<String>> = HashMap::new();
90 for entity in &entities {
91 for supertype in &entity.supertypes {
92 children
93 .entry(supertype.to_ascii_uppercase())
94 .or_default()
95 .push(entity.name.clone());
96 }
97 }
98 for names in children.values_mut() {
99 names.sort_unstable_by_key(|name| name.to_ascii_uppercase());
100 names.dedup_by(|a, b| a.eq_ignore_ascii_case(b));
102 }
103 Self {
104 name: name.into(),
105 entities,
106 types,
107 entity_index,
108 type_index,
109 children,
110 }
111 }
112
113 #[cfg(feature = "express")]
119 #[must_use]
120 pub fn from_express(source: &str) -> Self {
121 crate::express::parse(source)
122 }
123
124 #[cfg(feature = "express")]
130 #[must_use]
131 pub fn from_express_bytes(bytes: &[u8]) -> Self {
132 let text: String = bytes.iter().map(|&byte| byte as char).collect();
133 Self::from_express(&text)
134 }
135
136 #[must_use]
138 pub fn name(&self) -> &str {
139 &self.name
140 }
141
142 #[must_use]
147 pub fn version(&self) -> Option<SchemaVersion> {
148 SchemaVersion::from_header_token(&self.name)
149 }
150
151 #[must_use]
153 pub fn entity_count(&self) -> usize {
154 self.entities.len()
155 }
156
157 #[must_use]
159 pub fn type_count(&self) -> usize {
160 self.types.len()
161 }
162
163 #[must_use]
165 pub fn entity(&self, name: &str) -> Option<&EntityDef> {
166 self.entity_index
167 .get(&name.to_ascii_uppercase())
168 .map(|&index| &self.entities[index])
169 }
170
171 #[must_use]
173 pub fn type_def(&self, name: &str) -> Option<&TypeDef> {
174 self.type_index
175 .get(&name.to_ascii_uppercase())
176 .map(|&index| &self.types[index])
177 }
178
179 pub fn entities(&self) -> impl Iterator<Item = &EntityDef> {
181 self.entities.iter()
182 }
183
184 pub fn types(&self) -> impl Iterator<Item = &TypeDef> {
186 self.types.iter()
187 }
188
189 pub fn entity_names(&self) -> impl Iterator<Item = &str> {
193 self.entities.iter().map(|entity| entity.name.as_str())
194 }
195
196 #[must_use]
201 pub fn accepts_type(&self, declared: &str, candidate: &str) -> bool {
202 self.accepts_type_inner(declared, candidate, &mut HashSet::new(), 32)
203 }
204
205 fn accepts_type_inner(
206 &self,
207 declared: &str,
208 candidate: &str,
209 seen: &mut HashSet<(String, String)>,
210 depth: usize,
211 ) -> bool {
212 if declared.eq_ignore_ascii_case(candidate) {
213 return self.entity(declared).is_some() || self.type_def(declared).is_some();
214 }
215 if depth == 0
216 || !seen.insert((
217 declared.to_ascii_uppercase(),
218 candidate.to_ascii_uppercase(),
219 ))
220 {
221 return false;
222 }
223 if self.entity(declared).is_some() && self.entity(candidate).is_some() {
224 return self.is_a(candidate, declared);
225 }
226 if let Some(definition) = self.type_def(declared) {
227 match &definition.kind {
228 TypeKind::Defined(alias) => {
229 if self.accepts_type_inner(alias, candidate, seen, depth - 1) {
230 return true;
231 }
232 }
233 TypeKind::Select(members) => {
234 if members
235 .iter()
236 .any(|member| self.accepts_type_inner(member, candidate, seen, depth - 1))
237 {
238 return true;
239 }
240 }
241 TypeKind::Enumeration(_) => {}
242 }
243 }
244 if let Some(definition) = self.type_def(candidate) {
245 if let TypeKind::Defined(alias) = &definition.kind {
246 return self.accepts_type_inner(declared, alias, seen, depth - 1);
247 }
248 }
249 false
250 }
251
252 #[must_use]
258 pub fn is_a(&self, name: &str, ancestor: &str) -> bool {
259 if name.eq_ignore_ascii_case(ancestor) {
260 return self.entity(name).is_some();
261 }
262 self.supertypes(name)
263 .iter()
264 .any(|super_name| super_name.eq_ignore_ascii_case(ancestor))
265 }
266
267 #[must_use]
273 pub fn supertypes(&self, name: &str) -> Vec<&str> {
274 let mut seen = HashSet::new();
275 seen.insert(name.to_ascii_uppercase());
276 let mut out = Vec::new();
277 self.collect_supertypes(name, 0, &mut seen, &mut out);
278 out
279 }
280
281 fn collect_supertypes<'s>(
282 &'s self,
283 name: &str,
284 depth: usize,
285 seen: &mut HashSet<String>,
286 out: &mut Vec<&'s str>,
287 ) {
288 if depth >= MAX_CHAIN_DEPTH {
289 return;
290 }
291 let Some(def) = self.entity(name) else {
292 return;
293 };
294 for supertype in &def.supertypes {
295 if !seen.insert(supertype.to_ascii_uppercase()) {
296 continue;
297 }
298 match self.entity(supertype) {
299 Some(parent) => {
300 out.push(parent.name.as_str());
301 self.collect_supertypes(&parent.name, depth + 1, seen, out);
302 }
303 None => out.push(supertype.as_str()),
304 }
305 }
306 }
307
308 #[must_use]
310 pub fn direct_subtypes(&self, name: &str) -> Vec<&str> {
311 self.children
312 .get(&name.to_ascii_uppercase())
313 .map(|names| names.iter().map(String::as_str).collect())
314 .unwrap_or_default()
315 }
316
317 #[must_use]
321 pub fn subtypes(&self, name: &str) -> Vec<&str> {
322 let mut seen = HashSet::new();
323 seen.insert(name.to_ascii_uppercase());
324 let mut out = Vec::new();
325 let mut stack: Vec<&str> = self.direct_subtypes(name).into_iter().rev().collect();
328 while let Some(current) = stack.pop() {
329 if !seen.insert(current.to_ascii_uppercase()) {
330 continue;
331 }
332 out.push(current);
333 stack.extend(self.direct_subtypes(current).into_iter().rev());
334 }
335 out
336 }
337
338 #[must_use]
346 pub fn attributes(&self, name: &str) -> Vec<&Attribute> {
347 let mut seen = HashSet::new();
348 let mut out = Vec::new();
349 self.collect_attributes(name, 0, &mut seen, &mut out);
350 out
351 }
352
353 fn collect_attributes<'s>(
354 &'s self,
355 name: &str,
356 depth: usize,
357 seen: &mut HashSet<String>,
358 out: &mut Vec<&'s Attribute>,
359 ) {
360 if depth > MAX_CHAIN_DEPTH {
361 return;
362 }
363 let Some(def) = self.entity(name) else {
364 return;
365 };
366 if !seen.insert(def.name.to_ascii_uppercase()) {
367 return;
368 }
369 for supertype in &def.supertypes {
370 self.collect_attributes(supertype, depth + 1, seen, out);
371 }
372 out.extend(def.attributes.iter());
373 }
374
375 #[must_use]
377 pub fn attribute_names(&self, name: &str) -> Vec<&str> {
378 self.attributes(name)
379 .into_iter()
380 .map(|attribute| attribute.name.as_str())
381 .collect()
382 }
383
384 #[must_use]
391 pub fn resolve_defined(&self, name: &str) -> String {
392 let mut current = name.to_string();
393 for _ in 0..MAX_CHAIN_DEPTH {
394 let Some(def) = self.type_def(¤t) else {
395 return current;
396 };
397 let TypeKind::Defined(target) = &def.kind else {
398 return current;
399 };
400 let next = target.trim().to_string();
401 if next.eq_ignore_ascii_case(¤t) {
402 return current;
403 }
404 current = next;
405 }
406 current
407 }
408}
409
410fn index_by_name<T>(
413 declarations: Vec<T>,
414 name: impl Fn(&T) -> &String,
415) -> (Vec<T>, HashMap<String, usize>) {
416 let mut kept: Vec<T> = Vec::with_capacity(declarations.len());
417 let mut index = HashMap::with_capacity(declarations.len());
418 for declaration in declarations {
419 let key = name(&declaration).to_ascii_uppercase();
420 match index.get(&key) {
421 Some(&position) => kept[position] = declaration,
422 None => {
423 index.insert(key, kept.len());
424 kept.push(declaration);
425 }
426 }
427 }
428 (kept, index)
429}
430
431#[cfg(test)]
432mod tests {
433 use super::*;
434
435 fn chain() -> Schema {
437 Schema::new(
438 "IFC4",
439 vec![
440 EntityDef::new("IfcRoot")
441 .abstract_entity()
442 .with_attribute(Attribute::new("GlobalId", "IfcGloballyUniqueId"))
443 .with_attribute(Attribute::new("OwnerHistory", "IfcOwnerHistory").optional())
444 .with_attribute(Attribute::new("Name", "IfcLabel").optional())
445 .with_attribute(Attribute::new("Description", "IfcText").optional()),
446 EntityDef::new("IfcObjectDefinition")
447 .abstract_entity()
448 .with_supertype("IfcRoot"),
449 EntityDef::new("IfcObject")
450 .with_supertype("IfcObjectDefinition")
451 .with_attribute(Attribute::new("ObjectType", "IfcLabel").optional()),
452 ],
453 vec![
454 TypeDef::new("IfcLengthMeasure", TypeKind::Defined("REAL".into())),
455 TypeDef::new(
456 "IfcPositiveLengthMeasure",
457 TypeKind::Defined("IfcLengthMeasure".into()),
458 ),
459 ],
460 )
461 }
462
463 #[test]
464 fn the_declared_schema_name_maps_onto_a_known_ifc_version() {
465 let schema = chain();
466 assert_eq!(schema.name(), "IFC4");
467 assert_eq!(schema.version(), Some(SchemaVersion::Ifc4));
468 }
469
470 #[test]
472 fn an_unrecognized_schema_name_has_no_version_but_still_works() {
473 let schema = Schema::new(
474 "AP242",
475 vec![EntityDef::new("Product").with_attribute(Attribute::new("Id", "Identifier"))],
476 Vec::new(),
477 );
478 assert_eq!(schema.version(), None, "not an IFC schema");
479 assert_eq!(schema.attribute_names("Product"), ["Id"]);
480 }
481
482 #[test]
483 fn inherited_attributes_come_first_in_positional_order() {
484 assert_eq!(
485 chain().attribute_names("IFCOBJECT"),
486 [
487 "GlobalId",
488 "OwnerHistory",
489 "Name",
490 "Description",
491 "ObjectType"
492 ],
493 );
494 }
495
496 #[test]
497 fn defined_types_resolve_through_the_alias_chain() {
498 assert_eq!(chain().resolve_defined("IfcPositiveLengthMeasure"), "REAL");
499 }
500
501 #[test]
502 fn subtype_tests_cross_intermediate_levels_and_are_reflexive_only_when_declared() {
503 let schema = chain();
504 assert!(schema.is_a("IFCOBJECT", "IfcRoot"));
505 assert!(schema.is_a("IfcObject", "IfcObject"));
506 assert!(!schema.is_a("IfcRoot", "IfcObject"));
507 assert!(!schema.is_a("NotAThing", "NotAThing"));
508 assert_eq!(
509 schema.subtypes("ifcroot"),
510 ["IfcObjectDefinition", "IfcObject"]
511 );
512 assert_eq!(
513 schema.supertypes("IfcObject"),
514 ["IfcObjectDefinition", "IfcRoot"]
515 );
516 }
517
518 #[test]
519 fn cyclic_chains_terminate() {
520 let schema = Schema::new(
521 "S",
522 vec![
523 EntityDef::new("A").with_supertype("B"),
524 EntityDef::new("B").with_supertype("A"),
525 ],
526 vec![
527 TypeDef::new("X", TypeKind::Defined("Y".into())),
528 TypeDef::new("Y", TypeKind::Defined("X".into())),
529 ],
530 );
531 assert_eq!(schema.supertypes("A"), ["B"]);
532 assert_eq!(schema.subtypes("A"), ["B"]);
533 let resolved = schema.resolve_defined("X");
534 assert!(resolved == "X" || resolved == "Y");
535 }
536
537 #[test]
538 fn an_undeclared_supertype_is_still_named_in_both_directions() {
539 let schema = Schema::new(
540 "S",
541 vec![EntityDef::new("A").with_supertype("Missing")],
542 Vec::new(),
543 );
544 assert_eq!(schema.supertypes("A"), ["Missing"]);
545 assert!(schema.is_a("A", "Missing"));
546 assert_eq!(schema.subtypes("Missing"), ["A"]);
547 }
548
549 #[test]
550 fn a_repeated_name_keeps_its_first_position_and_last_declaration() {
551 let schema = Schema::new(
552 "S",
553 vec![
554 EntityDef::new("A"),
555 EntityDef::new("B"),
556 EntityDef::new("a").with_attribute(Attribute::new("X", "INTEGER")),
557 ],
558 Vec::new(),
559 );
560 assert_eq!(schema.entity_count(), 2);
561 assert_eq!(schema.entity_names().collect::<Vec<_>>(), ["a", "B"]);
562 assert_eq!(schema.attribute_names("A"), ["X"]);
563 }
564}
565
566#[cfg(all(test, feature = "express"))]
569mod parity_with_step {
570 use super::*;
571
572 const TREE: &str = "\
573SCHEMA TREE;
574ENTITY Root; Id : INTEGER; END_ENTITY;
575ENTITY Wall SUBTYPE OF (Root); Name : OPTIONAL STRING; END_ENTITY;
576ENTITY Door SUBTYPE OF (Root); END_ENTITY;
577ENTITY WallStandardCase SUBTYPE OF (Wall); Tag : LIST [1:?] OF STRING; END_ENTITY;
578ENTITY WallElementedCase SUBTYPE OF (Wall); END_ENTITY;
579ENTITY Orphan SUBTYPE OF (Missing); END_ENTITY;
580ENTITY Unrelated; END_ENTITY;
581TYPE Count = INTEGER; END_TYPE;
582TYPE PositiveCount = Count; END_TYPE;
583TYPE Colour = ENUMERATION OF (RED, GREEN); END_TYPE;
584END_SCHEMA;";
585
586 #[test]
587 fn every_query_matches_the_step_schema_graph() {
588 let owned = Schema::from_express(TREE);
589 let graph = openbim_step::SchemaGraph::from_express(TREE);
590 assert_eq!(owned.name(), graph.name());
591 assert_eq!(owned.entity_count(), graph.entity_count());
592 assert_eq!(owned.type_count(), graph.type_count());
593 let mut names: Vec<&str> = graph.entity_names().collect();
594 names.extend(["Missing", "NotAThing"]);
595 for &name in &names {
596 assert_eq!(owned.supertypes(name), graph.supertypes(name), "{name}");
597 assert_eq!(owned.subtypes(name), graph.subtypes(name), "{name}");
598 assert_eq!(
599 owned.direct_subtypes(name),
600 graph.direct_subtypes(name),
601 "{name}"
602 );
603 assert_eq!(
604 owned.attribute_names(name),
605 graph.attribute_names(name),
606 "{name}"
607 );
608 for &ancestor in &names {
609 assert_eq!(
610 owned.is_a(name, ancestor),
611 graph.is_a(name, ancestor),
612 "{name} / {ancestor}"
613 );
614 }
615 }
616 for name in ["PositiveCount", "Colour", "Count", "Unknown"] {
617 assert_eq!(owned.resolve_defined(name), graph.resolve_defined(name));
618 }
619 }
620}