use std::fmt::{Display, Formatter};
use std::io;
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
use crate::model::{IComment, IModelVisitor, ITemplateEvent};
use crate::util::{CharSequenceValue, TemplateWriter, TextUtilsError, Utf16String};
use super::{AbstractTextualTemplateEvent, IEngineTemplateEvent, ITemplateHandler};
const COMMENT_PREFIX: &str = "<!--";
const COMMENT_SUFFIX: &str = "-->";
pub struct Comment {
textual_event: AbstractTextualTemplateEvent,
prefix: Utf16String,
suffix: Utf16String,
computed_comment: RwLock<Option<Utf16String>>,
}
impl Comment {
#[must_use]
pub fn new(content: Option<Arc<dyn CharSequenceValue>>) -> Self {
Self::with_boundaries(
Utf16String::from_rust_str(COMMENT_PREFIX),
content,
Utf16String::from_rust_str(COMMENT_SUFFIX),
)
}
#[must_use]
pub fn with_boundaries(
prefix: Utf16String,
content: Option<Arc<dyn CharSequenceValue>>,
suffix: Utf16String,
) -> Self {
Self {
textual_event: AbstractTextualTemplateEvent::new(content),
prefix,
suffix,
computed_comment: RwLock::new(None),
}
}
#[must_use]
pub fn with_location(
content: Option<Arc<dyn CharSequenceValue>>,
template_name: Option<Utf16String>,
line: i32,
col: i32,
) -> Self {
Self::with_boundaries_and_location(
Utf16String::from_rust_str(COMMENT_PREFIX),
content,
Utf16String::from_rust_str(COMMENT_SUFFIX),
template_name,
line,
col,
)
}
#[must_use]
pub fn with_boundaries_and_location(
prefix: Utf16String,
content: Option<Arc<dyn CharSequenceValue>>,
suffix: Utf16String,
template_name: Option<Utf16String>,
line: i32,
col: i32,
) -> Self {
Self {
textual_event: AbstractTextualTemplateEvent::with_location(
content,
template_name,
line,
col,
),
prefix,
suffix,
computed_comment: RwLock::new(None),
}
}
fn compute_comment(&self) -> Result<Utf16String, TextUtilsError> {
if let Some(value) = read_lock(&self.computed_comment).as_ref() {
return Ok(value.clone());
}
let content = self
.textual_event
.get_content_text()?
.ok_or(TextUtilsError::NullPointer)?;
let mut result = Vec::with_capacity(self.prefix.len() + content.len() + self.suffix.len());
result.extend_from_slice(self.prefix.as_utf16());
result.extend_from_slice(content.as_utf16());
result.extend_from_slice(self.suffix.as_utf16());
let result = Utf16String::from_utf16(result);
*write_lock(&self.computed_comment) = Some(result.clone());
Ok(result)
}
pub(crate) const fn prefix(&self) -> &Utf16String {
&self.prefix
}
pub(crate) const fn suffix(&self) -> &Utf16String {
&self.suffix
}
}
impl CharSequenceValue for Comment {
fn sequence_class_name(&self) -> &str {
"org.thymeleaf.engine.Comment"
}
fn length(&self) -> Result<i32, TextUtilsError> {
Ok((self.prefix.len() as i32)
.wrapping_add(self.textual_event.get_content_length()?)
.wrapping_add(self.suffix.len() as i32))
}
fn char_at(&self, index: i32) -> Result<u16, TextUtilsError> {
let prefix_length = self.prefix.len() as i32;
if index < prefix_length {
return self.prefix.char_at(index);
}
let content_end = prefix_length.wrapping_add(self.textual_event.get_content_length()?);
if index >= content_end {
return self.suffix.char_at(index.wrapping_sub(content_end));
}
self.textual_event
.char_at_content(index.wrapping_sub(prefix_length))
}
fn as_utf16_string(&self) -> Option<&Utf16String> {
None
}
fn to_utf16_string(&self) -> Result<Utf16String, TextUtilsError> {
self.compute_comment()
}
fn sub_sequence(&self, start: i32, end: i32) -> Result<Utf16String, TextUtilsError> {
let prefix_length = self.prefix.len() as i32;
let content_end = prefix_length.wrapping_add(self.textual_event.get_content_length()?);
if start >= prefix_length && end < content_end {
return self.textual_event.content_sub_sequence(
start.wrapping_sub(prefix_length),
end.wrapping_sub(prefix_length),
);
}
self.compute_comment()?.sub_sequence(start, end)
}
}
impl IComment for Comment {
fn as_engine_comment(&self) -> Option<&Self> {
Some(self)
}
fn get_comment(&self) -> Result<Option<Utf16String>, TextUtilsError> {
self.compute_comment().map(Some)
}
fn get_content(&self) -> Result<Option<Utf16String>, TextUtilsError> {
self.textual_event.get_content_text()
}
}
impl ITemplateEvent for Comment {
fn has_location(&self) -> bool {
self.textual_event.as_template_event().has_location()
}
fn get_template_name(&self) -> Option<&Utf16String> {
self.textual_event.as_template_event().get_template_name()
}
fn get_line(&self) -> i32 {
self.textual_event.as_template_event().get_line()
}
fn get_col(&self) -> i32 {
self.textual_event.as_template_event().get_col()
}
fn accept(&self, visitor: &mut dyn IModelVisitor) {
visitor.visit_comment(self);
}
fn be_handled(
self: Arc<Self>,
handler: &mut dyn ITemplateHandler,
) -> Result<(), Box<dyn crate::exceptions::TemplateEngineException>> {
handler.handle_comment(self)
}
fn write(&self, writer: &mut dyn TemplateWriter) -> io::Result<()> {
writer.write_utf16(self.prefix.as_utf16())?;
self.textual_event.write_content(writer)?;
writer.write_utf16(self.suffix.as_utf16())
}
}
impl IEngineTemplateEvent for Comment {}
impl Display for Comment {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
let comment = self.compute_comment().map_err(|_| std::fmt::Error)?;
formatter.write_str(&comment.to_string_lossy())
}
}
fn read_lock<T>(lock: &RwLock<T>) -> RwLockReadGuard<'_, T> {
lock.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn write_lock<T>(lock: &RwLock<T>) -> RwLockWriteGuard<'_, T> {
lock.write()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}