use std::fmt::{Display, Formatter};
use std::io;
use std::sync::{Arc, Mutex};
use crate::exceptions::TemplateEngineException;
use crate::model::{IModel, IModelError, IModelVisitor, ITemplateEvent};
use crate::util::{FastStringWriter, TemplateWriter};
use crate::{IEngineConfiguration, TemplateMode};
use super::{
ITemplateHandler, TemplateData, model::Model, template_flow_controller::TemplateFlowController,
};
#[derive(Clone)]
pub struct TemplateModel {
configuration: Arc<dyn IEngineConfiguration>,
template_data: Arc<TemplateData>,
queue: Vec<Arc<dyn ITemplateEvent>>,
}
impl TemplateModel {
pub(crate) fn new(
configuration: Arc<dyn IEngineConfiguration>,
template_data: Arc<TemplateData>,
queue: Vec<Arc<dyn ITemplateEvent>>,
) -> Result<Self, IModelError> {
if queue.len() < 2
|| !queue.first().is_some_and(|event| event.is_template_start())
|| !queue.last().is_some_and(|event| event.is_template_end())
{
return Err(IModelError::TemplateBoundaryInsertion);
}
Ok(Self {
configuration,
template_data,
queue,
})
}
#[must_use]
pub fn get_template_data(&self) -> &TemplateData {
self.template_data.as_ref()
}
pub(crate) fn process(
&self,
handler: &mut dyn ITemplateHandler,
) -> Result<(), Box<dyn TemplateEngineException>> {
for event in &self.queue {
Arc::clone(event).be_handled(handler)?;
}
Ok(())
}
pub(crate) fn process_throttled(
&self,
handler: &mut dyn ITemplateHandler,
offset: usize,
controller: Option<&Arc<Mutex<TemplateFlowController>>>,
) -> Result<usize, Box<dyn TemplateEngineException>> {
if controller.is_none() {
self.process(handler)?;
return Ok(self.queue.len());
}
if offset >= self.queue.len() {
return Ok(0);
}
let controller = controller.expect("controller was checked");
let mut processed = 0;
for event in self.queue.iter().skip(offset) {
if controller
.lock()
.expect("template flow controller lock poisoned")
.stop_processing
{
break;
}
Arc::clone(event).be_handled(handler)?;
processed += 1;
}
Ok(processed)
}
fn immutable_model_error<T>() -> Result<T, IModelError> {
Err(IModelError::ImmutableModel)
}
}
impl IModel for TemplateModel {
fn get_template_data(&self) -> Option<&TemplateData> {
Some(self.template_data.as_ref())
}
fn get_template_data_arc(&self) -> Option<Arc<TemplateData>> {
Some(Arc::clone(&self.template_data))
}
fn get_configuration(&self) -> &dyn IEngineConfiguration {
self.configuration.as_ref()
}
fn get_template_mode(&self) -> TemplateMode {
self.template_data
.get_template_mode()
.expect("TemplateModel requires non-null template mode")
}
fn size(&self) -> usize {
self.queue.len()
}
fn get(&self, pos: usize) -> Arc<dyn ITemplateEvent> {
Arc::clone(&self.queue[pos])
}
fn add(&mut self, _event: Option<Arc<dyn ITemplateEvent>>) -> Result<(), IModelError> {
Self::immutable_model_error()
}
fn insert(
&mut self,
_pos: usize,
_event: Option<Arc<dyn ITemplateEvent>>,
) -> Result<(), IModelError> {
Self::immutable_model_error()
}
fn replace(
&mut self,
_pos: usize,
_event: Option<Arc<dyn ITemplateEvent>>,
) -> Result<(), IModelError> {
Self::immutable_model_error()
}
fn add_model(&mut self, _model: Option<&dyn IModel>) -> Result<(), IModelError> {
Self::immutable_model_error()
}
fn insert_model(
&mut self,
_pos: usize,
_model: Option<&dyn IModel>,
) -> Result<(), IModelError> {
Self::immutable_model_error()
}
fn remove(&mut self, _pos: usize) -> Result<(), IModelError> {
Self::immutable_model_error()
}
fn reset(&mut self) -> Result<(), IModelError> {
Self::immutable_model_error()
}
fn clone_model(&self) -> Box<dyn IModel> {
let mut model = Model::new(Arc::clone(&self.configuration), self.get_template_mode());
model.queue = self.queue[1..self.queue.len() - 1].to_vec();
Box::new(model)
}
fn accept(&self, visitor: &mut dyn IModelVisitor) {
for event in &self.queue {
event.accept(visitor);
}
}
fn write(&self, writer: &mut dyn TemplateWriter) -> io::Result<()> {
for event in &self.queue {
event.write(writer)?;
}
Ok(())
}
}
impl Display for TemplateModel {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
let mut writer = FastStringWriter::new();
self.write(&mut writer).map_err(|_| std::fmt::Error)?;
formatter.write_str(&writer.to_string().to_string_lossy())
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use super::TemplateModel;
use crate::engine::{TemplateData, TemplateEnd, TemplateStart};
use crate::model::{IModel, IModelError, ITemplateEvent};
use crate::{ITemplateEngine, TemplateEngine, TemplateMode};
fn golden(key: &str) -> &str {
include_str!("../../tests/fixtures/model_golden.txt")
.lines()
.find_map(|line| line.strip_prefix(&format!("{key}=")))
.expect("Java Golden record")
}
fn template_model() -> TemplateModel {
let engine = TemplateEngine::new();
let configuration = engine.get_configuration().expect("configuration");
let template_data = Arc::new(TemplateData::new(
Some(crate::util::Utf16String::from_rust_str("template")),
None,
None,
Some(TemplateMode::HTML),
None,
));
let queue: Vec<Arc<dyn ITemplateEvent>> =
vec![TemplateStart::instance(), TemplateEnd::instance()];
TemplateModel::new(configuration, template_data, queue).expect("bounded template model")
}
#[test]
fn immutable_contract_and_mutable_clone_match_java_golden() {
let mut model = template_model();
assert_eq!(
format!(
"{},{},{},{}",
model.size(),
model.get_template_mode(),
[
model.add(None),
model.insert(0, None),
model.replace(0, None),
model.add_model(None),
model.insert_model(0, None),
model.remove(0),
model.reset(),
]
.iter()
.all(|result| matches!(result, Err(IModelError::ImmutableModel))),
model.clone_model().size(),
),
golden("templateModel")
);
assert_eq!(
format!(
"UnsupportedOperationException:{}",
IModelError::ImmutableModel
),
golden("immutable")
);
}
}