1use crate::metamodel::{Aspect, ModelElement, Property};
35use crate::query::{ComplexityMetrics, Dependency, ModelQuery};
36use std::collections::HashMap;
37use std::sync::{Arc, RwLock};
38
39#[derive(Debug, Clone, Hash, Eq, PartialEq)]
41enum CacheKey {
42 ComplexityMetrics,
43 OptionalProperties,
44 RequiredProperties,
45 CollectionProperties,
46 DependencyGraph,
47 CircularDependencies,
48 FindByType(String),
49 FindByNamespace(String),
50}
51
52pub struct CachedModelQuery<'a> {
54 query: ModelQuery<'a>,
55 cache: Arc<RwLock<HashMap<CacheKey, CachedValue>>>,
56 hits: Arc<RwLock<usize>>,
57 misses: Arc<RwLock<usize>>,
58 max_cache_size: usize,
59}
60
61#[derive(Clone)]
63struct CachedValue {
64 value: CachedResult,
65 access_count: usize,
66 last_accessed: std::time::Instant,
67}
68
69#[derive(Clone)]
71enum CachedResult {
72 ComplexityMetrics(ComplexityMetrics),
73 Properties(Vec<String>), Dependencies(Vec<Dependency>),
75}
76
77impl<'a> CachedModelQuery<'a> {
78 pub fn new(aspect: &'a Aspect, max_cache_size: usize) -> Self {
95 Self {
96 query: ModelQuery::new(aspect),
97 cache: Arc::new(RwLock::new(HashMap::new())),
98 hits: Arc::new(RwLock::new(0)),
99 misses: Arc::new(RwLock::new(0)),
100 max_cache_size: max_cache_size.max(1),
101 }
102 }
103
104 pub fn complexity_metrics(&mut self) -> ComplexityMetrics {
106 let key = CacheKey::ComplexityMetrics;
107
108 if let Some(CachedResult::ComplexityMetrics(metrics)) = self.get_cached(&key) {
109 return metrics;
110 }
111
112 let metrics = self.query.complexity_metrics();
114 self.cache_result(key, CachedResult::ComplexityMetrics(metrics.clone()));
115 metrics
116 }
117
118 pub fn find_optional_properties(&mut self) -> Vec<&Property> {
120 let key = CacheKey::OptionalProperties;
121
122 if let Some(CachedResult::Properties(urns)) = self.get_cached(&key) {
123 return self
125 .query
126 .aspect()
127 .properties()
128 .iter()
129 .filter(|p| urns.contains(&p.urn().to_string()))
130 .collect();
131 }
132
133 let props = self.query.find_optional_properties();
135 let urns: Vec<String> = props.iter().map(|p| p.urn().to_string()).collect();
136 self.cache_result(key, CachedResult::Properties(urns));
137 props
138 }
139
140 pub fn find_required_properties(&mut self) -> Vec<&Property> {
142 let key = CacheKey::RequiredProperties;
143
144 if let Some(CachedResult::Properties(urns)) = self.get_cached(&key) {
145 return self
146 .query
147 .aspect()
148 .properties()
149 .iter()
150 .filter(|p| urns.contains(&p.urn().to_string()))
151 .collect();
152 }
153
154 let props = self.query.find_required_properties();
155 let urns: Vec<String> = props.iter().map(|p| p.urn().to_string()).collect();
156 self.cache_result(key, CachedResult::Properties(urns));
157 props
158 }
159
160 pub fn find_properties_with_collection_characteristic(&mut self) -> Vec<&Property> {
162 let key = CacheKey::CollectionProperties;
163
164 if let Some(CachedResult::Properties(urns)) = self.get_cached(&key) {
165 return self
166 .query
167 .aspect()
168 .properties()
169 .iter()
170 .filter(|p| urns.contains(&p.urn().to_string()))
171 .collect();
172 }
173
174 let props = self.query.find_properties_with_collection_characteristic();
175 let urns: Vec<String> = props.iter().map(|p| p.urn().to_string()).collect();
176 self.cache_result(key, CachedResult::Properties(urns));
177 props
178 }
179
180 pub fn build_dependency_graph(&mut self) -> Vec<Dependency> {
182 let key = CacheKey::DependencyGraph;
183
184 if let Some(CachedResult::Dependencies(deps)) = self.get_cached(&key) {
185 return deps;
186 }
187
188 let deps = self.query.build_dependency_graph();
189 self.cache_result(key, CachedResult::Dependencies(deps.clone()));
190 deps
191 }
192
193 pub fn detect_circular_dependencies(&mut self) -> Vec<Vec<String>> {
195 let key = CacheKey::CircularDependencies;
196
197 self.query.detect_circular_dependencies()
199 }
200
201 pub fn clear_cache(&mut self) {
203 let mut cache = self
204 .cache
205 .write()
206 .expect("cache mutex should not be poisoned");
207 cache.clear();
208 }
209
210 pub fn cache_statistics(&self) -> CacheStatistics {
212 let hits = *self.hits.read().expect("hits mutex should not be poisoned");
213 let misses = *self
214 .misses
215 .read()
216 .expect("misses mutex should not be poisoned");
217 let total = hits + misses;
218 let hit_rate = if total == 0 {
219 0.0
220 } else {
221 hits as f64 / total as f64
222 };
223
224 let cache = self
225 .cache
226 .read()
227 .expect("cache mutex should not be poisoned");
228
229 CacheStatistics {
230 size: cache.len(),
231 capacity: self.max_cache_size,
232 hits,
233 misses,
234 hit_rate,
235 }
236 }
237
238 fn get_cached(&self, key: &CacheKey) -> Option<CachedResult> {
240 let mut cache = self
241 .cache
242 .write()
243 .expect("cache mutex should not be poisoned");
244
245 if let Some(entry) = cache.get_mut(key) {
246 entry.access_count += 1;
247 entry.last_accessed = std::time::Instant::now();
248 *self
249 .hits
250 .write()
251 .expect("write lock should not be poisoned") += 1;
252 Some(entry.value.clone())
253 } else {
254 *self
255 .misses
256 .write()
257 .expect("write lock should not be poisoned") += 1;
258 None
259 }
260 }
261
262 fn cache_result(&self, key: CacheKey, value: CachedResult) {
264 let mut cache = self
265 .cache
266 .write()
267 .expect("cache mutex should not be poisoned");
268
269 if cache.len() >= self.max_cache_size && !cache.contains_key(&key) {
271 if let Some((lru_key, _)) = cache
272 .iter()
273 .min_by_key(|(_, v)| v.last_accessed)
274 .map(|(k, v)| (k.clone(), v.clone()))
275 {
276 cache.remove(&lru_key);
277 }
278 }
279
280 cache.insert(
281 key,
282 CachedValue {
283 value,
284 access_count: 0,
285 last_accessed: std::time::Instant::now(),
286 },
287 );
288 }
289
290 pub fn query(&self) -> &ModelQuery<'a> {
292 &self.query
293 }
294}
295
296#[derive(Debug, Clone)]
298pub struct CacheStatistics {
299 pub size: usize,
301 pub capacity: usize,
303 pub hits: usize,
305 pub misses: usize,
307 pub hit_rate: f64,
309}
310
311impl CacheStatistics {
312 pub fn total_accesses(&self) -> usize {
314 self.hits + self.misses
315 }
316
317 pub fn fill_percentage(&self) -> f64 {
319 if self.capacity == 0 {
320 0.0
321 } else {
322 (self.size as f64 / self.capacity as f64) * 100.0
323 }
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use super::*;
330 use crate::metamodel::{Characteristic, CharacteristicKind};
331
332 fn create_test_aspect() -> Aspect {
333 let mut aspect = Aspect::new("urn:samm:test:1.0.0#TestAspect".to_string());
334
335 let mut prop1 = Property::new("urn:samm:test:1.0.0#prop1".to_string());
336 prop1.optional = false;
337
338 let mut prop2 = Property::new("urn:samm:test:1.0.0#prop2".to_string());
339 prop2.optional = true;
340
341 let mut prop3 = Property::new("urn:samm:test:1.0.0#prop3".to_string());
342 prop3.is_collection = true;
343 let collection_char = Characteristic::new(
345 "urn:samm:test:1.0.0#CollectionChar".to_string(),
346 CharacteristicKind::Collection {
347 element_characteristic: None,
348 },
349 );
350 prop3.characteristic = Some(collection_char);
351
352 aspect.add_property(prop1);
353 aspect.add_property(prop2);
354 aspect.add_property(prop3);
355
356 aspect
357 }
358
359 #[test]
360 fn test_cached_complexity_metrics() {
361 let aspect = create_test_aspect();
362 let mut query = CachedModelQuery::new(&aspect, 10);
363
364 let metrics1 = query.complexity_metrics();
366 assert_eq!(metrics1.total_properties, 3);
367
368 let stats1 = query.cache_statistics();
369 assert_eq!(stats1.misses, 1);
370 assert_eq!(stats1.hits, 0);
371
372 let metrics2 = query.complexity_metrics();
374 assert_eq!(metrics2.total_properties, 3);
375
376 let stats2 = query.cache_statistics();
377 assert_eq!(stats2.misses, 1);
378 assert_eq!(stats2.hits, 1);
379 assert!((stats2.hit_rate - 0.5).abs() < 0.01);
380 }
381
382 #[test]
383 fn test_cached_optional_properties() {
384 let aspect = create_test_aspect();
385 let mut query = CachedModelQuery::new(&aspect, 10);
386
387 let props1 = query.find_optional_properties();
388 assert_eq!(props1.len(), 1);
389
390 let stats1 = query.cache_statistics();
391 assert_eq!(stats1.misses, 1);
392
393 let props2 = query.find_optional_properties();
394 assert_eq!(props2.len(), 1);
395
396 let stats2 = query.cache_statistics();
397 assert_eq!(stats2.hits, 1);
398 }
399
400 #[test]
401 fn test_cached_required_properties() {
402 let aspect = create_test_aspect();
403 let mut query = CachedModelQuery::new(&aspect, 10);
404
405 let props1 = query.find_required_properties();
406 assert_eq!(props1.len(), 2);
407
408 let props2 = query.find_required_properties();
409 assert_eq!(props2.len(), 2);
410
411 let stats = query.cache_statistics();
412 assert_eq!(stats.hits, 1);
413 assert_eq!(stats.misses, 1);
414 }
415
416 #[test]
417 fn test_cache_clear() {
418 let aspect = create_test_aspect();
419 let mut query = CachedModelQuery::new(&aspect, 10);
420
421 query.complexity_metrics();
422 query.complexity_metrics(); let stats_before = query.cache_statistics();
425 assert_eq!(stats_before.size, 1);
426
427 query.clear_cache();
428
429 let stats_after = query.cache_statistics();
430 assert_eq!(stats_after.size, 0);
431 assert_eq!(stats_after.hits, 1); }
433
434 #[test]
435 fn test_cache_lru_eviction() {
436 let aspect = create_test_aspect();
437 let mut query = CachedModelQuery::new(&aspect, 2); query.complexity_metrics();
441 query.find_optional_properties();
442
443 let stats1 = query.cache_statistics();
444 assert_eq!(stats1.size, 2);
445
446 query.find_required_properties();
448
449 let stats2 = query.cache_statistics();
450 assert_eq!(stats2.size, 2); }
452
453 #[test]
454 fn test_cache_statistics() {
455 let aspect = create_test_aspect();
456 let mut query = CachedModelQuery::new(&aspect, 10);
457
458 query.complexity_metrics();
459 query.complexity_metrics();
460 query.find_optional_properties();
461
462 let stats = query.cache_statistics();
463 assert_eq!(stats.total_accesses(), 3);
464 assert_eq!(stats.hits, 1);
465 assert_eq!(stats.misses, 2);
466 assert!((stats.hit_rate - 0.333).abs() < 0.01);
467 assert_eq!(stats.size, 2);
468 }
469
470 #[test]
471 fn test_collection_properties_caching() {
472 let aspect = create_test_aspect();
473 let mut query = CachedModelQuery::new(&aspect, 10);
474
475 let props1 = query.find_properties_with_collection_characteristic();
476 assert_eq!(props1.len(), 1);
477
478 let props2 = query.find_properties_with_collection_characteristic();
479 assert_eq!(props2.len(), 1);
480
481 let stats = query.cache_statistics();
482 assert_eq!(stats.hits, 1);
483 }
484
485 #[test]
486 fn test_dependency_graph_caching() {
487 let mut aspect = Aspect::new("urn:samm:test:1.0.0#TestAspect".to_string());
488
489 let mut prop = Property::new("urn:samm:test:1.0.0#prop1".to_string());
490 let char = Characteristic::new(
491 "urn:samm:test:1.0.0#Char1".to_string(),
492 CharacteristicKind::Trait,
493 );
494 prop.characteristic = Some(char);
495 aspect.add_property(prop);
496
497 let mut query = CachedModelQuery::new(&aspect, 10);
498
499 let deps1 = query.build_dependency_graph();
500 assert!(!deps1.is_empty());
501
502 let deps2 = query.build_dependency_graph();
503 assert!(!deps2.is_empty());
504
505 let stats = query.cache_statistics();
506 assert_eq!(stats.hits, 1);
507 }
508}