use std::io;
use std::sync::{Arc, Mutex, RwLock};
use crate::context::ITemplateContext;
use crate::model::IModel;
use super::{
CharSequenceValue, IWritableCharSequence, TemplateWriter, TextUtilsError, Utf16String,
};
pub struct LazyProcessingCharSequence {
context: Arc<dyn ITemplateContext>,
template_model: Arc<dyn IModel>,
resolved_text: RwLock<Option<Utf16String>>,
}
impl LazyProcessingCharSequence {
#[must_use]
pub fn new(context: Arc<dyn ITemplateContext>, template_model: Arc<dyn IModel>) -> Self {
Self {
context,
template_model,
resolved_text: RwLock::new(None),
}
}
fn resolve_text(&self) -> Result<Utf16String, TextUtilsError> {
if let Some(text) = self.read_resolved_text()? {
return Ok(text.clone());
}
let output = Arc::new(Mutex::new(Vec::new()));
let writer = SharedUtf16Writer {
output: Arc::clone(&output),
};
self.context
.get_configuration()
.get_template_manager()
.process(
self.template_model.as_ref(),
self.context.as_ref(),
Box::new(writer),
)
.map_err(processing_text_error)?;
let text = Utf16String::from_utf16(
output
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone(),
);
*self
.resolved_text
.write()
.map_err(|_| processing_lock_error())? = Some(text.clone());
Ok(text)
}
fn read_resolved_text(&self) -> Result<Option<Utf16String>, TextUtilsError> {
self.resolved_text
.read()
.map(|guard| guard.clone())
.map_err(|_| processing_lock_error())
}
}
impl CharSequenceValue for LazyProcessingCharSequence {
fn as_utf16_string(&self) -> Option<&Utf16String> {
None
}
fn sequence_class_name(&self) -> &str {
"org.thymeleaf.util.LazyProcessingCharSequence"
}
fn length(&self) -> Result<i32, TextUtilsError> {
Ok(self.resolve_text()?.len() as i32)
}
fn char_at(&self, index: i32) -> Result<u16, TextUtilsError> {
self.resolve_text()?.char_at(index)
}
fn to_utf16_string(&self) -> Result<Utf16String, TextUtilsError> {
self.resolve_text()
}
fn sub_sequence(&self, start: i32, end: i32) -> Result<Utf16String, TextUtilsError> {
self.resolve_text()?.sub_sequence(start, end)
}
fn write_direct(&self, writer: &mut dyn TemplateWriter) -> Option<io::Result<()>> {
Some(IWritableCharSequence::write(self, writer))
}
}
impl IWritableCharSequence for LazyProcessingCharSequence {
fn write(&self, writer: &mut dyn TemplateWriter) -> io::Result<()> {
if let Some(text) = self
.read_resolved_text()
.map_err(|error| io::Error::other(error.to_string()))?
{
return writer.write_utf16(text.as_utf16());
}
let output = Arc::new(Mutex::new(Vec::new()));
let processing_writer = SharedUtf16Writer {
output: Arc::clone(&output),
};
self.context
.get_configuration()
.get_template_manager()
.process(
self.template_model.as_ref(),
self.context.as_ref(),
Box::new(processing_writer),
)
.map_err(io::Error::other)?;
writer.write_utf16(
&output
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner),
)
}
}
struct SharedUtf16Writer {
output: Arc<Mutex<Vec<u16>>>,
}
impl TemplateWriter for SharedUtf16Writer {
fn write_utf16(&mut self, characters: &[u16]) -> io::Result<()> {
self.output
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.extend_from_slice(characters);
Ok(())
}
}
fn processing_text_error(error: crate::exceptions::TemplateProcessingException) -> TextUtilsError {
TextUtilsError::SequenceAccess {
class_name: "org.thymeleaf.exceptions.TemplateProcessingException".to_owned(),
message: Some(Utf16String::from_rust_str(&error.get_message())),
}
}
fn processing_lock_error() -> TextUtilsError {
TextUtilsError::SequenceAccess {
class_name: "java.lang.IllegalStateException".to_owned(),
message: Some(Utf16String::from_rust_str(
"LazyProcessingCharSequence resolved text lock is poisoned",
)),
}
}