use std::any::TypeId;
use std::sync::{Arc, OnceLock, Weak};
use indexmap::IndexMap;
use crate::exceptions::TemplateProcessingException;
use crate::expression::{ExpressionObjects, IExpressionObjects, TemplateValue};
use crate::model::IModelFactory;
use crate::util::{Locale, Utf16String, ValidateError};
use crate::{IEngineConfiguration, TemplateResolutionAttributes};
use super::{IExpressionContext, ITemplateContext, IdentifierSequences};
pub struct AbstractEngineContext {
configuration: Arc<dyn IEngineConfiguration>,
template_resolution_attributes: Option<TemplateResolutionAttributes>,
locale: Locale,
expression_context: Weak<dyn IExpressionContext>,
expression_objects: OnceLock<ExpressionObjects>,
identifier_sequences: OnceLock<IdentifierSequences>,
}
impl AbstractEngineContext {
pub fn new(
configuration: Arc<dyn IEngineConfiguration>,
template_resolution_attributes: Option<&TemplateResolutionAttributes>,
locale: Locale,
context: Weak<dyn IExpressionContext>,
) -> Result<Self, ValidateError> {
Ok(Self {
configuration,
template_resolution_attributes: template_resolution_attributes.cloned(),
locale,
expression_context: context,
expression_objects: OnceLock::new(),
identifier_sequences: OnceLock::new(),
})
}
#[must_use]
pub fn get_configuration(&self) -> &dyn IEngineConfiguration {
self.configuration.as_ref()
}
#[must_use]
pub fn get_configuration_arc(&self) -> Arc<dyn IEngineConfiguration> {
Arc::clone(&self.configuration)
}
#[must_use]
pub fn get_template_resolution_attributes(&self) -> Option<&TemplateResolutionAttributes> {
self.template_resolution_attributes.as_ref()
}
#[must_use]
pub fn get_locale(&self) -> Locale {
self.locale.clone()
}
#[must_use]
pub fn get_expression_objects(&self) -> &dyn IExpressionObjects {
self.expression_objects.get_or_init(|| {
let expression_object_factory = self.configuration.get_expression_object_factory();
ExpressionObjects::new(
Some(self.expression_context.clone()),
Some(expression_object_factory),
)
.expect("engine configuration supplies a valid expression object factory")
})
}
#[must_use]
pub fn get_model_factory(&self, template_context: &dyn ITemplateContext) -> &dyn IModelFactory {
self.configuration
.get_model_factory(template_context.get_template_mode())
}
pub fn get_message(
&self,
template_context: &dyn ITemplateContext,
origin: Option<TypeId>,
key: &Utf16String,
message_parameters: Option<&[Option<Arc<TemplateValue>>]>,
use_absent_message_representation: bool,
) -> crate::messageresolver::MessageResolutionResult<Option<Utf16String>> {
let message_resolvers = self.configuration.get_message_resolvers();
for message_resolver in &message_resolvers {
if let Some(message) = message_resolver.resolve_message(
template_context,
origin,
key,
message_parameters,
)? {
return Ok(Some(message));
}
}
if use_absent_message_representation {
for message_resolver in &message_resolvers {
if let Some(representation) = message_resolver
.create_absent_message_representation(
template_context,
origin,
key,
message_parameters,
)?
{
return Ok(Some(representation));
}
}
}
Ok(None)
}
pub fn build_link(
&self,
expression_context: &dyn IExpressionContext,
base: Option<&Utf16String>,
parameters: Option<&IndexMap<Option<Utf16String>, Option<Arc<TemplateValue>>>>,
) -> Result<Utf16String, TemplateProcessingException> {
for link_builder in self.configuration.get_link_builders() {
if let Some(link) = link_builder.build_link(expression_context, base, parameters)? {
return Ok(link);
}
}
let base = base.map_or_else(|| "null".to_owned(), Utf16String::to_string_lossy);
Err(TemplateProcessingException::new(Some(format!(
"No configured link builder instance was able to build link with base \"{base}\" and \
parameters {}",
format_parameters(parameters)
))))
}
#[must_use]
pub fn get_identifier_sequences(&self) -> &IdentifierSequences {
self.identifier_sequences
.get_or_init(IdentifierSequences::new)
}
}
fn format_parameters(
parameters: Option<&IndexMap<Option<Utf16String>, Option<Arc<TemplateValue>>>>,
) -> String {
let Some(parameters) = parameters else {
return "null".to_owned();
};
let mut result = String::from("{");
for (index, (name, value)) in parameters.iter().enumerate() {
if index != 0 {
result.push_str(", ");
}
result.push_str(
&name
.as_ref()
.map_or_else(|| "null".to_owned(), Utf16String::to_string_lossy),
);
result.push('=');
result.push_str(
&value
.as_deref()
.and_then(TemplateValue::to_utf16_string)
.map_or_else(|| "null".to_owned(), |value| value.to_string_lossy()),
);
}
result.push('}');
result
}