1use crate::scanner::{EntityIndex, EntityScanner};
8use crate::tokenizer::parse_entity_at;
9use bimifc_model::{DecodedEntity, EntityId, ParseError, Result};
10use rustc_hash::FxHashMap;
11use std::sync::Arc;
12
13pub struct EntityDecoder<'a> {
18 content: &'a str,
20 index: EntityIndex,
22 cache: FxHashMap<u32, Arc<DecodedEntity>>,
24 unit_scale: Option<f64>,
26}
27
28impl<'a> EntityDecoder<'a> {
29 pub fn new(content: &'a str) -> Self {
31 let index = EntityScanner::build_index(content);
32
33 Self {
34 content,
35 index,
36 cache: FxHashMap::default(),
37 unit_scale: None,
38 }
39 }
40
41 pub fn with_index(content: &'a str, index: EntityIndex) -> Self {
43 Self {
44 content,
45 index,
46 cache: FxHashMap::default(),
47 unit_scale: None,
48 }
49 }
50
51 pub fn content(&self) -> &'a str {
53 self.content
54 }
55
56 pub fn index(&self) -> &EntityIndex {
58 &self.index
59 }
60
61 pub fn all_ids(&self) -> Vec<EntityId> {
63 self.index.keys().map(|&id| EntityId(id)).collect()
64 }
65
66 pub fn entity_count(&self) -> usize {
68 self.index.len()
69 }
70
71 pub fn exists(&self, id: EntityId) -> bool {
73 self.index.contains_key(&id.0)
74 }
75
76 pub fn decode_by_id(&mut self, id: EntityId) -> Result<Arc<DecodedEntity>> {
78 if let Some(cached) = self.cache.get(&id.0) {
80 return Ok(Arc::clone(cached));
81 }
82
83 let (start, end) = self
85 .index
86 .get(&id.0)
87 .ok_or(ParseError::EntityNotFound(id))?;
88
89 let entity = parse_entity_at(self.content, *start, *end)
91 .map_err(|e| ParseError::EntityParse(id, e))?;
92
93 let arc = Arc::new(entity);
95 self.cache.insert(id.0, Arc::clone(&arc));
96 Ok(arc)
97 }
98
99 pub fn raw_bytes(&self, id: EntityId) -> Option<&'a [u8]> {
101 let (start, end) = self.index.get(&id.0)?;
102 Some(&self.content.as_bytes()[*start..*end])
103 }
104
105 pub fn raw_str(&self, id: EntityId) -> Option<&'a str> {
107 let (start, end) = self.index.get(&id.0)?;
108 Some(&self.content[*start..*end])
109 }
110
111 pub fn unit_scale(&self) -> Option<f64> {
113 self.unit_scale
114 }
115
116 pub fn set_unit_scale(&mut self, scale: f64) {
118 self.unit_scale = Some(scale);
119 }
120
121 pub fn clear_cache(&mut self) {
123 self.cache.clear();
124 }
125
126 pub fn cache_size(&self) -> usize {
128 self.cache.len()
129 }
130
131 pub fn preload(&mut self, ids: &[EntityId]) {
133 for id in ids {
134 let _ = self.decode_by_id(*id);
135 }
136 }
137
138 pub fn find_by_type(&mut self, type_name: &str) -> Vec<Arc<DecodedEntity>> {
140 let matches = EntityScanner::find_by_type(self.content, type_name);
141 let mut results = Vec::with_capacity(matches.len());
142
143 for (id, _, _) in matches {
144 if let Ok(entity) = self.decode_by_id(EntityId(id)) {
145 results.push(entity);
146 }
147 }
148
149 results
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 const TEST_IFC: &str = r#"ISO-10303-21;
158HEADER;
159FILE_SCHEMA(('IFC2X3'));
160ENDSEC;
161DATA;
162#1=IFCPROJECT('guid',$,'Project',$,$,$,$,$,#2);
163#2=IFCUNITASSIGNMENT((#3));
164#3=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);
165ENDSEC;
166END-ISO-10303-21;
167"#;
168
169 #[test]
170 fn test_decode_by_id() {
171 let mut decoder = EntityDecoder::new(TEST_IFC);
172
173 let entity = decoder.decode_by_id(EntityId(1)).unwrap();
174 assert_eq!(entity.id, EntityId(1));
175 assert_eq!(entity.ifc_type, bimifc_model::IfcType::IfcProject);
176 }
177
178 #[test]
179 fn test_caching() {
180 let mut decoder = EntityDecoder::new(TEST_IFC);
181
182 let entity1 = decoder.decode_by_id(EntityId(1)).unwrap();
184 assert_eq!(decoder.cache_size(), 1);
185
186 let entity2 = decoder.decode_by_id(EntityId(1)).unwrap();
188 assert_eq!(decoder.cache_size(), 1);
189
190 assert!(Arc::ptr_eq(&entity1, &entity2));
192 }
193
194 #[test]
195 fn test_entity_not_found() {
196 let mut decoder = EntityDecoder::new(TEST_IFC);
197
198 let result = decoder.decode_by_id(EntityId(999));
199 assert!(result.is_err());
200 }
201
202 #[test]
203 fn test_find_by_type() {
204 let mut decoder = EntityDecoder::new(TEST_IFC);
205
206 let projects = decoder.find_by_type("IFCPROJECT");
207 assert_eq!(projects.len(), 1);
208 assert_eq!(projects[0].id, EntityId(1));
209 }
210}