use crate::Options;
use jsonld_core::{Context, HashMap};
use jsonld_syntax::Print;
use parking_lot::Mutex;
use std::{
fmt::Write as _,
hash::{BuildHasher, Hash, Hasher},
sync::Arc,
};
struct HashWriter<'a, H: Hasher>(&'a mut H);
impl<H: Hasher> std::fmt::Write for HashWriter<'_, H> {
#[inline]
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.0.write(s.as_bytes());
Ok(())
}
}
struct CacheEntry<T, B> {
active: Context<T, B>,
local: jsonld_syntax::context::Context,
base_url: Option<T>,
options: Options,
result: Arc<Context<T, B>>,
}
impl<T: PartialEq, B: PartialEq> CacheEntry<T, B> {
fn matches(&self, active: &Context<T, B>, local: &jsonld_syntax::context::Context, base_url: Option<&T>, options: Options) -> bool {
self.active.definitions_arc_ptr() == active.definitions_arc_ptr()
&& self.active.previous_context_arc_ptr() == active.previous_context_arc_ptr()
&& self.active.original_base_url() == active.original_base_url()
&& self.active.base_iri() == active.base_iri()
&& self.active.vocabulary() == active.vocabulary()
&& self.active.default_language() == active.default_language()
&& self.active.default_base_direction() == active.default_base_direction()
&& self.base_url.as_ref() == base_url
&& self.options == options
&& self.local == *local
}
}
pub struct ProcessingCache<T, B> {
entries: Mutex<HashMap<u64, CacheEntry<T, B>>>,
}
impl<T, B> ProcessingCache<T, B> {
#[must_use]
pub fn new() -> Self {
Self {
entries: Mutex::new(HashMap::default()),
}
}
pub fn clear(&self) {
self.entries.lock().clear();
}
pub fn len(&self) -> usize {
self.entries.lock().len()
}
pub fn is_empty(&self) -> bool {
self.entries.lock().is_empty()
}
pub(crate) fn get(
&self,
key: u64,
active: &Context<T, B>,
local: &jsonld_syntax::context::Context,
base_url: Option<&T>,
options: Options,
) -> Option<Arc<Context<T, B>>>
where
T: PartialEq,
B: PartialEq,
{
let entries = self.entries.lock();
let entry = entries.get(&key)?;
entry.matches(active, local, base_url, options).then(|| Arc::clone(&entry.result))
}
pub(crate) fn insert(
&self,
key: u64,
active: Context<T, B>,
local: jsonld_syntax::context::Context,
base_url: Option<T>,
options: Options,
result: Arc<Context<T, B>>,
) {
self.entries.lock().insert(
key,
CacheEntry {
active,
local,
base_url,
options,
result,
},
);
}
}
impl<T, B> Default for ProcessingCache<T, B> {
fn default() -> Self {
Self::new()
}
}
pub(crate) fn cache_key<T, B>(active_context: &Context<T, B>, local_context: &jsonld_syntax::context::Context, base_url: Option<&T>, options: Options) -> u64
where
T: Hash,
B: Hash,
{
let mut hasher = foldhash::fast::FixedState::default().build_hasher();
active_context.definitions_arc_ptr().hash(&mut hasher);
active_context.previous_context_arc_ptr().hash(&mut hasher);
active_context.original_base_url().hash(&mut hasher);
active_context.base_iri().hash(&mut hasher);
active_context.vocabulary().hash(&mut hasher);
active_context.default_language().hash(&mut hasher);
active_context.default_base_direction().hash(&mut hasher);
let _ = write!(HashWriter(&mut hasher), "{}", local_context.pretty_print());
base_url.hash(&mut hasher);
options.processing_mode.hash(&mut hasher);
options.override_protected.hash(&mut hasher);
options.propagate.hash(&mut hasher);
options.vocab.hash(&mut hasher);
hasher.finish()
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
use iri_rs::IriBuf;
use jsonld_core::ProcessingMode;
use rdfx::BlankIdBuf;
#[test]
fn colliding_key_is_a_miss_not_a_false_hit() {
let cache: ProcessingCache<IriBuf, BlankIdBuf> = ProcessingCache::new();
let local = jsonld_syntax::context::Context::One(jsonld_syntax::ContextEntry::Null);
let active_a: Context<IriBuf, BlankIdBuf> = Context::new(Some(IriBuf::new("http://a/".to_string()).unwrap()));
let active_b: Context<IriBuf, BlankIdBuf> = Context::new(Some(IriBuf::new("http://b/".to_string()).unwrap()));
cache.insert(42, active_a.clone(), local.clone(), None, Options::default(), Arc::new(Context::new(None)));
assert!(cache.get(42, &active_b, &local, None, Options::default()).is_none());
let options_1_0 = Options {
processing_mode: ProcessingMode::JsonLd1_0,
..Default::default()
};
assert!(cache.get(42, &active_a, &local, None, options_1_0).is_none());
assert!(cache.get(42, &active_a, &local, None, Options::default()).is_some());
}
}