use crate::metamodel::{Aspect, Characteristic, Entity, Operation, Property};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
#[derive(Clone)]
struct CacheEntry<T> {
value: Arc<T>,
access_count: usize,
last_accessed: std::time::Instant,
}
pub struct LruModelCache<T> {
capacity: usize,
entries: Arc<RwLock<HashMap<String, CacheEntry<T>>>>,
access_order: Arc<RwLock<Vec<String>>>,
hits: Arc<RwLock<usize>>,
misses: Arc<RwLock<usize>>,
}
impl<T> LruModelCache<T>
where
T: Clone,
{
pub fn new(capacity: usize) -> Self {
Self {
capacity: capacity.max(1), entries: Arc::new(RwLock::new(HashMap::new())),
access_order: Arc::new(RwLock::new(Vec::new())),
hits: Arc::new(RwLock::new(0)),
misses: Arc::new(RwLock::new(0)),
}
}
pub fn get(&self, key: &str) -> Option<Arc<T>> {
let mut entries = self.entries.write().expect("lock poisoned");
if let Some(entry) = entries.get_mut(key) {
entry.access_count += 1;
entry.last_accessed = std::time::Instant::now();
let mut access_order = self.access_order.write().expect("lock poisoned");
if let Some(pos) = access_order.iter().position(|k| k == key) {
access_order.remove(pos);
}
access_order.push(key.to_string());
*self.hits.write().expect("lock poisoned") += 1;
Some(Arc::clone(&entry.value))
} else {
*self.misses.write().expect("lock poisoned") += 1;
None
}
}
pub fn put(&mut self, key: String, value: Arc<T>) {
let mut entries = self.entries.write().expect("lock poisoned");
let mut access_order = self.access_order.write().expect("lock poisoned");
if entries.contains_key(&key) {
if let Some(pos) = access_order.iter().position(|k| k == &key) {
access_order.remove(pos);
}
}
if entries.len() >= self.capacity && !entries.contains_key(&key) {
if let Some(lru_key) = access_order.first() {
let lru_key = lru_key.clone();
entries.remove(&lru_key);
access_order.remove(0);
}
}
let entry = CacheEntry {
value,
access_count: 0,
last_accessed: std::time::Instant::now(),
};
entries.insert(key.clone(), entry);
access_order.push(key);
}
pub fn contains(&self, key: &str) -> bool {
let entries = self.entries.read().expect("lock poisoned");
entries.contains_key(key)
}
pub fn remove(&mut self, key: &str) -> Option<Arc<T>> {
let mut entries = self.entries.write().expect("lock poisoned");
let mut access_order = self.access_order.write().expect("lock poisoned");
if let Some(pos) = access_order.iter().position(|k| k == key) {
access_order.remove(pos);
}
entries.remove(key).map(|entry| entry.value)
}
pub fn clear(&mut self) {
let mut entries = self.entries.write().expect("lock poisoned");
let mut access_order = self.access_order.write().expect("lock poisoned");
entries.clear();
access_order.clear();
}
pub fn len(&self) -> usize {
let entries = self.entries.read().expect("lock poisoned");
entries.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn resize(&mut self, new_capacity: usize) {
self.capacity = new_capacity.max(1);
let mut entries = self.entries.write().expect("lock poisoned");
let mut access_order = self.access_order.write().expect("lock poisoned");
while entries.len() > self.capacity {
if let Some(lru_key) = access_order.first() {
let lru_key = lru_key.clone();
entries.remove(&lru_key);
access_order.remove(0);
} else {
break;
}
}
}
pub fn hit_rate(&self) -> f64 {
let hits = *self.hits.read().expect("lock poisoned");
let misses = *self.misses.read().expect("lock poisoned");
let total = hits + misses;
if total == 0 {
0.0
} else {
hits as f64 / total as f64
}
}
pub fn hits(&self) -> usize {
*self.hits.read().expect("lock poisoned")
}
pub fn misses(&self) -> usize {
*self.misses.read().expect("lock poisoned")
}
pub fn reset_statistics(&mut self) {
*self.hits.write().expect("lock poisoned") = 0;
*self.misses.write().expect("lock poisoned") = 0;
}
pub fn keys(&self) -> Vec<String> {
let access_order = self.access_order.read().expect("lock poisoned");
access_order.clone()
}
pub fn statistics(&self) -> CacheStatistics {
CacheStatistics {
size: self.len(),
capacity: self.capacity,
hits: self.hits(),
misses: self.misses(),
hit_rate: self.hit_rate(),
}
}
}
impl<T> Clone for LruModelCache<T> {
fn clone(&self) -> Self {
Self {
capacity: self.capacity,
entries: Arc::clone(&self.entries),
access_order: Arc::clone(&self.access_order),
hits: Arc::clone(&self.hits),
misses: Arc::clone(&self.misses),
}
}
}
#[derive(Debug, Clone)]
pub struct CacheStatistics {
pub size: usize,
pub capacity: usize,
pub hits: usize,
pub misses: usize,
pub hit_rate: f64,
}
impl CacheStatistics {
pub fn fill_percentage(&self) -> f64 {
if self.capacity == 0 {
0.0
} else {
(self.size as f64 / self.capacity as f64) * 100.0
}
}
pub fn total_accesses(&self) -> usize {
self.hits + self.misses
}
}
pub type AspectCache = LruModelCache<Aspect>;
pub type PropertyCache = LruModelCache<Property>;
pub type CharacteristicCache = LruModelCache<Characteristic>;
pub type EntityCache = LruModelCache<Entity>;
pub type OperationCache = LruModelCache<Operation>;
#[derive(Clone)]
struct TtlCacheEntry<T> {
value: Arc<T>,
access_count: usize,
created_at: std::time::Instant,
expires_at: std::time::Instant,
}
pub struct TtlCache<T> {
capacity: usize,
ttl: std::time::Duration,
entries: Arc<RwLock<HashMap<String, TtlCacheEntry<T>>>>,
access_order: Arc<RwLock<Vec<String>>>,
hits: Arc<RwLock<usize>>,
misses: Arc<RwLock<usize>>,
expirations: Arc<RwLock<usize>>,
}
impl<T> TtlCache<T>
where
T: Clone,
{
pub fn new(capacity: usize, ttl: std::time::Duration) -> Self {
Self {
capacity: capacity.max(1),
ttl,
entries: Arc::new(RwLock::new(HashMap::new())),
access_order: Arc::new(RwLock::new(Vec::new())),
hits: Arc::new(RwLock::new(0)),
misses: Arc::new(RwLock::new(0)),
expirations: Arc::new(RwLock::new(0)),
}
}
pub fn get(&self, key: &str) -> Option<Arc<T>> {
let mut entries = self.entries.write().expect("lock poisoned");
if let Some(entry) = entries.get_mut(key) {
let now = std::time::Instant::now();
if now >= entry.expires_at {
entries.remove(key);
let mut access_order = self.access_order.write().expect("lock poisoned");
if let Some(pos) = access_order.iter().position(|k| k == key) {
access_order.remove(pos);
}
*self.expirations.write().expect("lock poisoned") += 1;
*self.misses.write().expect("lock poisoned") += 1;
return None;
}
entry.access_count += 1;
let mut access_order = self.access_order.write().expect("lock poisoned");
if let Some(pos) = access_order.iter().position(|k| k == key) {
access_order.remove(pos);
}
access_order.push(key.to_string());
*self.hits.write().expect("lock poisoned") += 1;
Some(Arc::clone(&entry.value))
} else {
*self.misses.write().expect("lock poisoned") += 1;
None
}
}
pub fn put(&mut self, key: String, value: Arc<T>) {
let mut entries = self.entries.write().expect("lock poisoned");
let mut access_order = self.access_order.write().expect("lock poisoned");
if entries.contains_key(&key) {
if let Some(pos) = access_order.iter().position(|k| k == &key) {
access_order.remove(pos);
}
}
if entries.len() >= self.capacity && !entries.contains_key(&key) {
if let Some(lru_key) = access_order.first() {
let lru_key = lru_key.clone();
entries.remove(&lru_key);
access_order.remove(0);
}
}
let now = std::time::Instant::now();
let entry = TtlCacheEntry {
value,
access_count: 0,
created_at: now,
expires_at: now + self.ttl,
};
entries.insert(key.clone(), entry);
access_order.push(key);
}
pub fn evict_expired(&mut self) -> usize {
let mut entries = self.entries.write().expect("lock poisoned");
let mut access_order = self.access_order.write().expect("lock poisoned");
let now = std::time::Instant::now();
let expired_keys: Vec<String> = entries
.iter()
.filter(|(_, entry)| now >= entry.expires_at)
.map(|(key, _)| key.clone())
.collect();
let count = expired_keys.len();
for key in expired_keys {
entries.remove(&key);
if let Some(pos) = access_order.iter().position(|k| k == &key) {
access_order.remove(pos);
}
}
*self.expirations.write().expect("lock poisoned") += count;
count
}
pub fn len(&self) -> usize {
let entries = self.entries.read().expect("lock poisoned");
entries.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn ttl(&self) -> std::time::Duration {
self.ttl
}
pub fn clear(&mut self) {
let mut entries = self.entries.write().expect("lock poisoned");
let mut access_order = self.access_order.write().expect("lock poisoned");
entries.clear();
access_order.clear();
}
pub fn hit_rate(&self) -> f64 {
let hits = *self.hits.read().expect("lock poisoned");
let misses = *self.misses.read().expect("lock poisoned");
let total = hits + misses;
if total == 0 {
0.0
} else {
hits as f64 / total as f64
}
}
pub fn expirations(&self) -> usize {
*self.expirations.read().expect("lock poisoned")
}
pub fn statistics(&self) -> TtlCacheStatistics {
TtlCacheStatistics {
size: self.len(),
capacity: self.capacity,
hits: *self.hits.read().expect("lock poisoned"),
misses: *self.misses.read().expect("lock poisoned"),
expirations: self.expirations(),
hit_rate: self.hit_rate(),
ttl_seconds: self.ttl.as_secs(),
}
}
}
#[derive(Debug, Clone)]
pub struct TtlCacheStatistics {
pub size: usize,
pub capacity: usize,
pub hits: usize,
pub misses: usize,
pub expirations: usize,
pub hit_rate: f64,
pub ttl_seconds: u64,
}
impl TtlCacheStatistics {
pub fn fill_percentage(&self) -> f64 {
if self.capacity == 0 {
0.0
} else {
(self.size as f64 / self.capacity as f64) * 100.0
}
}
pub fn total_accesses(&self) -> usize {
self.hits + self.misses
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::metamodel::ElementMetadata;
#[test]
fn test_cache_creation() {
let cache: LruModelCache<Aspect> = LruModelCache::new(10);
assert_eq!(cache.capacity(), 10);
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
}
#[test]
fn test_put_and_get() {
let mut cache = LruModelCache::new(5);
let aspect = Arc::new(Aspect::new("urn:test:1.0.0#Test".to_string()));
cache.put("test".to_string(), Arc::clone(&aspect));
assert_eq!(cache.len(), 1);
let retrieved = cache.get("test");
assert!(retrieved.is_some());
}
#[test]
fn test_lru_eviction() {
let mut cache = LruModelCache::new(3);
cache.put(
"a".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#A".to_string())),
);
cache.put(
"b".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#B".to_string())),
);
cache.put(
"c".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#C".to_string())),
);
assert_eq!(cache.len(), 3);
cache.put(
"d".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#D".to_string())),
);
assert_eq!(cache.len(), 3);
assert!(!cache.contains("a")); assert!(cache.contains("b"));
assert!(cache.contains("c"));
assert!(cache.contains("d"));
}
#[test]
fn test_lru_access_updates() {
let mut cache = LruModelCache::new(3);
cache.put(
"a".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#A".to_string())),
);
cache.put(
"b".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#B".to_string())),
);
cache.put(
"c".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#C".to_string())),
);
cache.get("a");
cache.put(
"d".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#D".to_string())),
);
assert!(cache.contains("a")); assert!(!cache.contains("b")); assert!(cache.contains("c"));
assert!(cache.contains("d"));
}
#[test]
fn test_remove() {
let mut cache = LruModelCache::new(5);
cache.put(
"test".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#Test".to_string())),
);
assert_eq!(cache.len(), 1);
let removed = cache.remove("test");
assert!(removed.is_some());
assert_eq!(cache.len(), 0);
}
#[test]
fn test_clear() {
let mut cache = LruModelCache::new(5);
cache.put(
"a".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#A".to_string())),
);
cache.put(
"b".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#B".to_string())),
);
assert_eq!(cache.len(), 2);
cache.clear();
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
}
#[test]
fn test_hit_rate() {
let mut cache = LruModelCache::new(5);
cache.put(
"test".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#Test".to_string())),
);
cache.get("test");
cache.get("test");
cache.get("nonexistent");
assert_eq!(cache.hits(), 2);
assert_eq!(cache.misses(), 1);
assert!((cache.hit_rate() - 0.666).abs() < 0.01);
}
#[test]
fn test_resize() {
let mut cache = LruModelCache::new(5);
for i in 0..5 {
cache.put(
format!("item{}", i),
Arc::new(Aspect::new(format!("urn:test:1.0.0#Item{}", i))),
);
}
assert_eq!(cache.len(), 5);
cache.resize(3);
assert_eq!(cache.capacity(), 3);
assert_eq!(cache.len(), 3);
assert!(!cache.contains("item0"));
assert!(!cache.contains("item1"));
assert!(cache.contains("item2"));
assert!(cache.contains("item3"));
assert!(cache.contains("item4"));
}
#[test]
fn test_statistics() {
let mut cache = LruModelCache::new(10);
cache.put(
"test".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#Test".to_string())),
);
cache.get("test");
cache.get("test");
cache.get("nonexistent");
let stats = cache.statistics();
assert_eq!(stats.size, 1);
assert_eq!(stats.capacity, 10);
assert_eq!(stats.hits, 2);
assert_eq!(stats.misses, 1);
assert_eq!(stats.total_accesses(), 3);
assert_eq!(stats.fill_percentage(), 10.0);
}
#[test]
fn test_keys() {
let mut cache = LruModelCache::new(5);
cache.put(
"a".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#A".to_string())),
);
cache.put(
"b".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#B".to_string())),
);
cache.put(
"c".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#C".to_string())),
);
let keys = cache.keys();
assert_eq!(keys.len(), 3);
assert!(keys.contains(&"a".to_string()));
assert!(keys.contains(&"b".to_string()));
assert!(keys.contains(&"c".to_string()));
}
#[test]
fn test_ttl_cache_creation() {
let cache: TtlCache<Aspect> = TtlCache::new(10, std::time::Duration::from_secs(60));
assert_eq!(cache.capacity(), 10);
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
assert_eq!(cache.ttl().as_secs(), 60);
}
#[test]
fn test_ttl_cache_put_and_get() {
let mut cache = TtlCache::new(5, std::time::Duration::from_secs(60));
let aspect = Arc::new(Aspect::new("urn:test:1.0.0#Test".to_string()));
cache.put("test".to_string(), Arc::clone(&aspect));
assert_eq!(cache.len(), 1);
let retrieved = cache.get("test");
assert!(retrieved.is_some());
}
#[test]
fn test_ttl_cache_expiration() {
let mut cache = TtlCache::new(5, std::time::Duration::from_millis(50));
let aspect = Arc::new(Aspect::new("urn:test:1.0.0#Test".to_string()));
cache.put("test".to_string(), Arc::clone(&aspect));
assert_eq!(cache.len(), 1);
assert!(cache.get("test").is_some());
std::thread::sleep(std::time::Duration::from_millis(100));
assert!(cache.get("test").is_none());
assert_eq!(cache.expirations(), 1);
}
#[test]
fn test_ttl_cache_evict_expired() {
let mut cache = TtlCache::new(5, std::time::Duration::from_millis(50));
for i in 0..3 {
cache.put(
format!("item{}", i),
Arc::new(Aspect::new(format!("urn:test:1.0.0#Item{}", i))),
);
}
assert_eq!(cache.len(), 3);
std::thread::sleep(std::time::Duration::from_millis(100));
let evicted = cache.evict_expired();
assert_eq!(evicted, 3);
assert_eq!(cache.len(), 0);
}
#[test]
fn test_ttl_cache_statistics() {
let mut cache = TtlCache::new(10, std::time::Duration::from_secs(60));
cache.put(
"test".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#Test".to_string())),
);
cache.get("test");
cache.get("test");
cache.get("nonexistent");
let stats = cache.statistics();
assert_eq!(stats.size, 1);
assert_eq!(stats.capacity, 10);
assert_eq!(stats.hits, 2);
assert_eq!(stats.misses, 1);
assert_eq!(stats.total_accesses(), 3);
assert_eq!(stats.ttl_seconds, 60);
}
#[test]
fn test_ttl_cache_clear() {
let mut cache = TtlCache::new(5, std::time::Duration::from_secs(60));
cache.put(
"a".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#A".to_string())),
);
cache.put(
"b".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#B".to_string())),
);
assert_eq!(cache.len(), 2);
cache.clear();
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
}
#[test]
fn test_ttl_cache_lru_eviction() {
let mut cache = TtlCache::new(3, std::time::Duration::from_secs(60));
cache.put(
"a".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#A".to_string())),
);
cache.put(
"b".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#B".to_string())),
);
cache.put(
"c".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#C".to_string())),
);
assert_eq!(cache.len(), 3);
cache.put(
"d".to_string(),
Arc::new(Aspect::new("urn:test:1.0.0#D".to_string())),
);
assert_eq!(cache.len(), 3);
}
}