use std::collections::BTreeMap;
use std::sync::Arc;
use ifc_model::{EntityId, Model, Value};
use super::layout::Layout;
use crate::error::{PropertyAnomaly, TemplateError};
use crate::nesting::Nesting;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum PropertyTemplateKind {
Simple,
Complex,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct PropertyTemplate {
pub id: EntityId,
pub name: Option<Arc<str>>,
pub description: Option<Arc<str>>,
pub kind: PropertyTemplateKind,
pub template_type: Option<Arc<str>>,
pub primary_measure: Option<Arc<str>>,
pub secondary_measure: Option<Arc<str>>,
pub enumerators: Option<EntityId>,
pub primary_unit: Option<EntityId>,
pub secondary_unit: Option<EntityId>,
pub expression: Option<Arc<str>>,
pub access_state: Option<Arc<str>>,
pub usage_name: Option<Arc<str>>,
pub templates: Vec<PropertyTemplate>,
}
impl PropertyTemplate {
pub fn template(&self, name: &str) -> Option<&PropertyTemplate> {
self.templates
.iter()
.find(|t| t.name.as_deref() == Some(name))
}
}
pub fn property_template(model: &Model, id: EntityId) -> Option<PropertyTemplate> {
let mut anomalies = Vec::new();
let mut nesting = Nesting::new(&mut anomalies);
read_template(model, Layout::permissive(model), id, &mut nesting)
}
pub fn property_template_checked(
model: &Model,
id: EntityId,
) -> Result<(PropertyTemplate, Vec<PropertyAnomaly>), TemplateError> {
let layout = Layout::declared(model)?;
let entity = model.get(id).ok_or(TemplateError::MissingEntity { id })?;
let mut anomalies = Vec::new();
let mut nesting = Nesting::new(&mut anomalies);
let template =
read_template(model, layout, id, &mut nesting).ok_or(TemplateError::NotATemplate {
id,
type_name: entity.type_name.to_string(),
})?;
Ok((template, anomalies))
}
pub(crate) fn read_template(
model: &Model,
layout: Layout,
id: EntityId,
nesting: &mut Nesting<'_>,
) -> Option<PropertyTemplate> {
let entity = model.get(id)?;
let kind = if layout.is_a(&entity.type_name, "IFCSIMPLEPROPERTYTEMPLATE") {
PropertyTemplateKind::Simple
} else if layout.is_a(&entity.type_name, "IFCCOMPLEXPROPERTYTEMPLATE") {
PropertyTemplateKind::Complex
} else {
return None;
};
let mut found = Vec::new();
layout.check_arity(id, entity, &mut found);
let out = &mut found;
let mut template = PropertyTemplate {
id,
name: layout.text(id, entity, "Name", out),
description: layout.text(id, entity, "Description", out),
kind,
template_type: layout.enumeration(id, entity, "TemplateType", out),
primary_measure: layout.text(id, entity, "PrimaryMeasureType", out),
secondary_measure: layout.text(id, entity, "SecondaryMeasureType", out),
enumerators: layout.reference(model, id, entity, "Enumerators", out),
primary_unit: layout.reference(model, id, entity, "PrimaryUnit", out),
secondary_unit: layout.reference(model, id, entity, "SecondaryUnit", out),
expression: layout.text(id, entity, "Expression", out),
access_state: layout.enumeration(id, entity, "AccessState", out),
usage_name: layout.text(id, entity, "UsageName", out),
templates: Vec::new(),
};
for anomaly in found {
nesting.report(anomaly);
}
if kind == PropertyTemplateKind::Complex {
let members = layout.get(entity, "HasPropertyTemplates");
template.templates = nested_templates(model, layout, id, members, nesting);
}
Some(template)
}
pub(crate) fn member_templates(
model: &Model,
layout: Layout,
container: EntityId,
members: Option<&Value>,
nesting: &mut Nesting<'_>,
) -> Vec<PropertyTemplate> {
let mut templates = Vec::new();
for member in nesting.members(container, "HasPropertyTemplates", members) {
if !nesting.admit(model, container, member) {
continue;
}
match read_template(model, layout, member, nesting) {
Some(template) => templates.push(template),
None => nesting.report(PropertyAnomaly::NotATemplate {
container,
member,
type_name: model
.get(member)
.map(|entity| entity.type_name.to_string())
.unwrap_or_default(),
}),
}
}
duplicate_names(container, &templates, nesting);
templates
}
fn nested_templates(
model: &Model,
layout: Layout,
complex: EntityId,
members: Option<&Value>,
nesting: &mut Nesting<'_>,
) -> Vec<PropertyTemplate> {
if !nesting.enter(complex) {
return Vec::new();
}
let templates = member_templates(model, layout, complex, members, nesting);
nesting.leave();
templates
}
fn duplicate_names(container: EntityId, templates: &[PropertyTemplate], nesting: &mut Nesting<'_>) {
let mut first: BTreeMap<&str, EntityId> = BTreeMap::new();
for template in templates {
let Some(name) = template.name.as_deref() else {
continue;
};
match first.get(name) {
Some(&kept) => nesting.report(PropertyAnomaly::DuplicatePropertyName {
set: container,
kept,
rejected: template.id,
}),
None => {
first.insert(name, template.id);
}
}
}
}