use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
use indexmap::IndexMap;
use crate::expression::TemplateValue;
use crate::util::{Locale, Utf16String, ValidateError};
use super::{ContextVariableEntries, IContext, IContextVariableNames};
type Variables = IndexMap<Option<Utf16String>, Arc<TemplateValue>>;
pub struct AbstractContext {
variables: Arc<RwLock<Variables>>,
variable_names: Arc<VariableNamesView>,
locale: RwLock<Locale>,
}
impl AbstractContext {
pub(super) fn new(locale: Option<Locale>, variables: ContextVariableEntries<'_>) -> Self {
let variables = variables.map_or_else(
|| IndexMap::with_capacity(10),
|entries| {
entries
.iter()
.map(|(name, value)| {
(
name.clone(),
value
.clone()
.unwrap_or_else(|| Arc::new(TemplateValue::Null)),
)
})
.collect()
},
);
let variables = Arc::new(RwLock::new(variables));
let variable_names = Arc::new(VariableNamesView {
variables: Arc::clone(&variables),
});
Self {
variables,
variable_names,
locale: RwLock::new(locale.unwrap_or_else(Locale::get_default)),
}
}
pub fn set_locale(&self, locale: Option<Locale>) -> Result<(), ValidateError> {
let locale = locale.ok_or_else(|| ValidateError::IllegalArgument {
message: Some("Locale cannot be null".to_owned()),
})?;
*write_recovering_poison(&self.locale) = locale;
Ok(())
}
pub fn set_variable(&self, name: Option<Utf16String>, value: Option<Arc<TemplateValue>>) {
write_recovering_poison(&self.variables)
.insert(name, value.unwrap_or_else(|| Arc::new(TemplateValue::Null)));
}
pub fn set_variables(&self, variables: ContextVariableEntries<'_>) {
let Some(variables) = variables else {
return;
};
let mut target = write_recovering_poison(&self.variables);
for (name, value) in variables {
target.insert(
name.clone(),
value
.clone()
.unwrap_or_else(|| Arc::new(TemplateValue::Null)),
);
}
}
pub fn remove_variable(&self, name: Option<&Utf16String>) {
write_recovering_poison(&self.variables).shift_remove(&owned_key(name));
}
pub fn clear_variables(&self) {
write_recovering_poison(&self.variables).clear();
}
}
impl IContext for AbstractContext {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn get_locale(&self) -> Locale {
read_recovering_poison(&self.locale).clone()
}
fn contains_variable(&self, name: Option<&Utf16String>) -> bool {
read_recovering_poison(&self.variables).contains_key(&owned_key(name))
}
fn get_variable_names(&self) -> Arc<dyn IContextVariableNames + '_> {
self.variable_names.clone()
}
fn get_variable(&self, name: Option<&Utf16String>) -> Option<Arc<TemplateValue>> {
read_recovering_poison(&self.variables)
.get(&owned_key(name))
.cloned()
}
}
struct VariableNamesView {
variables: Arc<RwLock<Variables>>,
}
impl IContextVariableNames for VariableNamesView {
fn len(&self) -> usize {
read_recovering_poison(&self.variables).len()
}
fn contains(&self, name: Option<&Utf16String>) -> bool {
read_recovering_poison(&self.variables).contains_key(&owned_key(name))
}
fn snapshot(&self) -> Vec<Option<Utf16String>> {
read_recovering_poison(&self.variables)
.keys()
.cloned()
.collect()
}
fn remove(&self, name: Option<&Utf16String>) -> bool {
write_recovering_poison(&self.variables)
.shift_remove(&owned_key(name))
.is_some()
}
}
fn read_recovering_poison<T>(lock: &RwLock<T>) -> RwLockReadGuard<'_, T> {
lock.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn write_recovering_poison<T>(lock: &RwLock<T>) -> RwLockWriteGuard<'_, T> {
lock.write()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn owned_key(name: Option<&Utf16String>) -> Option<Utf16String> {
name.cloned()
}