use crate::scanner::EntityIndex;
use crate::tokenizer::parse_entity_at;
use bimifc_model::{AttributeValue, DecodedEntity, EntityId, EntityResolver, IfcType};
use rustc_hash::FxHashMap;
use std::sync::{Arc, RwLock};
pub struct ResolverImpl {
content: String,
index: EntityIndex,
cache: RwLock<FxHashMap<u32, Arc<DecodedEntity>>>,
type_index: FxHashMap<IfcType, Vec<EntityId>>,
}
impl ResolverImpl {
pub fn new(content: String, index: EntityIndex) -> Self {
let mut type_index: FxHashMap<IfcType, Vec<EntityId>> = FxHashMap::default();
for (&id, (start, end)) in &index {
if let Ok(entity) = parse_entity_at(&content, *start, *end) {
type_index
.entry(entity.ifc_type.clone())
.or_default()
.push(EntityId(id));
}
}
Self {
content,
index,
cache: RwLock::new(FxHashMap::default()),
type_index,
}
}
pub fn with_type_index(
content: String,
index: EntityIndex,
type_index: FxHashMap<IfcType, Vec<EntityId>>,
) -> Self {
Self {
content,
index,
cache: RwLock::new(FxHashMap::default()),
type_index,
}
}
pub fn content(&self) -> &str {
&self.content
}
fn decode_and_cache(&self, id: u32) -> Option<Arc<DecodedEntity>> {
{
let cache = self.cache.read().ok()?;
if let Some(cached) = cache.get(&id) {
return Some(Arc::clone(cached));
}
}
let (start, end) = self.index.get(&id)?;
let entity = parse_entity_at(&self.content, *start, *end).ok()?;
let arc = Arc::new(entity);
if let Ok(mut cache) = self.cache.write() {
cache.insert(id, Arc::clone(&arc));
}
Some(arc)
}
}
impl EntityResolver for ResolverImpl {
fn get(&self, id: EntityId) -> Option<Arc<DecodedEntity>> {
self.decode_and_cache(id.0)
}
fn resolve_ref(&self, attr: &AttributeValue) -> Option<Arc<DecodedEntity>> {
match attr {
AttributeValue::EntityRef(id) => self.get(*id),
_ => None,
}
}
fn resolve_ref_list(&self, attr: &AttributeValue) -> Vec<Arc<DecodedEntity>> {
match attr {
AttributeValue::List(items) => items
.iter()
.filter_map(|item| self.resolve_ref(item))
.collect(),
_ => Vec::new(),
}
}
fn entities_by_type(&self, ifc_type: &IfcType) -> Vec<Arc<DecodedEntity>> {
self.type_index
.get(ifc_type)
.map(|ids| ids.iter().filter_map(|id| self.get(*id)).collect())
.unwrap_or_default()
}
fn types_present(&self) -> Vec<IfcType> {
self.type_index.keys().cloned().collect()
}
fn find_by_type_name(&self, type_name: &str) -> Vec<Arc<DecodedEntity>> {
let target = IfcType::parse(type_name);
self.entities_by_type(&target)
}
fn count_by_type(&self, ifc_type: &IfcType) -> usize {
self.type_index.get(ifc_type).map(|v| v.len()).unwrap_or(0)
}
fn all_ids(&self) -> Vec<EntityId> {
self.index.keys().map(|&id| EntityId(id)).collect()
}
fn raw_bytes(&self, id: EntityId) -> Option<&[u8]> {
let (start, end) = self.index.get(&id.0)?;
Some(&self.content.as_bytes()[*start..*end])
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scanner::EntityScanner;
const TEST_IFC: &str = r#"ISO-10303-21;
HEADER;
FILE_SCHEMA(('IFC2X3'));
ENDSEC;
DATA;
#1=IFCPROJECT('guid',$,'Project',$,$,$,$,$,#2);
#2=IFCUNITASSIGNMENT((#3));
#3=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);
#4=IFCWALL('guid2',$,'Wall 1',$,$,$,$,$);
ENDSEC;
END-ISO-10303-21;
"#;
#[test]
fn test_resolver_get() {
let index = EntityScanner::build_index(TEST_IFC);
let resolver = ResolverImpl::new(TEST_IFC.to_string(), index);
let entity = resolver.get(EntityId(1)).unwrap();
assert_eq!(entity.id, EntityId(1));
}
#[test]
fn test_resolver_entities_by_type() {
let index = EntityScanner::build_index(TEST_IFC);
let resolver = ResolverImpl::new(TEST_IFC.to_string(), index);
let walls = resolver.entities_by_type(&IfcType::IfcWall);
assert_eq!(walls.len(), 1);
assert_eq!(walls[0].id, EntityId(4));
}
#[test]
fn test_resolver_find_by_type_name() {
let index = EntityScanner::build_index(TEST_IFC);
let resolver = ResolverImpl::new(TEST_IFC.to_string(), index);
let projects = resolver.find_by_type_name("IFCPROJECT");
assert_eq!(projects.len(), 1);
}
#[test]
fn test_resolver_thread_safe() {
use std::thread;
let index = EntityScanner::build_index(TEST_IFC);
let resolver = Arc::new(ResolverImpl::new(TEST_IFC.to_string(), index));
let handles: Vec<_> = (0..4)
.map(|_| {
let resolver = Arc::clone(&resolver);
thread::spawn(move || {
for id in 1..=4 {
let _ = resolver.get(EntityId(id));
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
}
}