use std::io;
use std::sync::{Arc, RwLock};
use crate::util::{CharSequenceValue, TemplateWriter, TextUtilsError, Utf16String};
use super::{AbstractTemplateEvent, engine_event_utils::compute_inlineable};
pub struct AbstractTextualTemplateEvent {
template_event: AbstractTemplateEvent,
content: Option<Arc<dyn CharSequenceValue>>,
content_string: Option<Utf16String>,
content_length: i32,
computed_content_string: RwLock<Option<Utf16String>>,
computed_content_length: RwLock<i32>,
computed_whitespace: RwLock<Option<bool>>,
computed_inlineable: RwLock<Option<bool>>,
}
impl AbstractTextualTemplateEvent {
#[must_use]
pub fn new(content: Option<Arc<dyn CharSequenceValue>>) -> Self {
Self::with_location(content, None, -1, -1)
}
#[must_use]
pub fn with_location(
content: Option<Arc<dyn CharSequenceValue>>,
template_name: Option<Utf16String>,
line: i32,
col: i32,
) -> Self {
let content_string = content
.as_ref()
.and_then(|value| value.as_utf16_string())
.cloned();
let content_length = content_string
.as_ref()
.map_or(-1, |value| value.len() as i32);
Self {
template_event: AbstractTemplateEvent::with_location(template_name, line, col),
content,
content_string,
content_length,
computed_content_string: RwLock::new(None),
computed_content_length: RwLock::new(-1),
computed_whitespace: RwLock::new(None),
computed_inlineable: RwLock::new(None),
}
}
#[must_use]
pub const fn as_template_event(&self) -> &AbstractTemplateEvent {
&self.template_event
}
pub fn get_content_text(&self) -> Result<Option<Utf16String>, TextUtilsError> {
if self.content_string.is_some() || self.content.is_none() {
return Ok(self.content_string.clone());
}
if let Some(value) = read_lock(&self.computed_content_string).as_ref() {
return Ok(Some(value.clone()));
}
let value = sequence(self)?.to_utf16_string()?;
*write_lock(&self.computed_content_string) = Some(value.clone());
Ok(Some(value))
}
pub fn get_content_length(&self) -> Result<i32, TextUtilsError> {
if self.content_length >= 0 || self.content.is_none() {
return Ok(self.content_length);
}
let cached = *read_lock(&self.computed_content_length);
if cached >= 0 {
return Ok(cached);
}
let length = sequence(self)?.length()?;
*write_lock(&self.computed_content_length) = length;
Ok(length)
}
pub fn char_at_content(&self, index: i32) -> Result<u16, TextUtilsError> {
if let Some(value) = self.content_string.as_ref() {
return value.char_at(index);
}
if let Some(value) = read_lock(&self.computed_content_string).as_ref() {
return value.char_at(index);
}
sequence(self)?.char_at(index)
}
pub fn content_sub_sequence(
&self,
start: i32,
end: i32,
) -> Result<Utf16String, TextUtilsError> {
if let Some(value) = self.content_string.as_ref() {
return value.sub_sequence(start, end);
}
if let Some(value) = read_lock(&self.computed_content_string).as_ref() {
return value.sub_sequence(start, end);
}
sequence(self)?.sub_sequence(start, end)
}
pub fn is_whitespace(&self) -> Result<bool, TextUtilsError> {
if let Some(value) = *read_lock(&self.computed_whitespace) {
return Ok(value);
}
let mut remaining = self.get_content_length()?;
let result = if remaining == 0 {
false
} else {
let mut whitespace = true;
while remaining != 0 {
remaining -= 1;
let character = self.char_at_content(remaining)?;
if character != u16::from(b' ')
&& character != u16::from(b'\n')
&& !is_whitespace(character)
{
whitespace = false;
break;
}
}
whitespace
};
*write_lock(&self.computed_whitespace) = Some(result);
Ok(result)
}
pub fn is_inlineable(&self) -> Result<bool, TextUtilsError> {
if let Some(value) = *read_lock(&self.computed_inlineable) {
return Ok(value);
}
let text = self
.get_content_text()?
.unwrap_or_else(|| Utf16String::from_utf16(Vec::new()));
let result = compute_inlineable(&text)?;
*write_lock(&self.computed_inlineable) = Some(result);
Ok(result)
}
pub fn write_content(&self, writer: &mut dyn TemplateWriter) -> io::Result<()> {
if let Some(content) = self.content_string.as_ref() {
return writer.write_utf16(content.as_utf16());
}
if let Some(content) = read_lock(&self.computed_content_string).as_ref() {
return writer.write_utf16(content.as_utf16());
}
if let Some(content) = self.content.as_ref()
&& let Some(result) = content.write_direct(writer)
{
return result;
}
let content = self
.get_content_text()
.map_err(|error| io::Error::other(error.to_string()))?;
if let Some(content) = content {
writer.write_utf16(content.as_utf16())?;
}
Ok(())
}
}
fn sequence(
event: &AbstractTextualTemplateEvent,
) -> Result<&dyn CharSequenceValue, TextUtilsError> {
event.content.as_deref().ok_or(TextUtilsError::NullPointer)
}
fn is_whitespace(character: u16) -> bool {
matches!(
char::from_u32(u32::from(character)),
Some(
'\u{0009}'
| '\u{000A}'
| '\u{000B}'
| '\u{000C}'
| '\u{000D}'
| '\u{001C}'
| '\u{001D}'
| '\u{001E}'
| '\u{001F}'
| '\u{1680}'
| '\u{2000}'..='\u{2006}'
| '\u{2008}'..='\u{200A}'
| '\u{2028}'
| '\u{2029}'
| '\u{205F}'
| '\u{3000}',
)
)
}
fn read_lock<T>(lock: &RwLock<T>) -> std::sync::RwLockReadGuard<'_, T> {
lock.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn write_lock<T>(lock: &RwLock<T>) -> std::sync::RwLockWriteGuard<'_, T> {
lock.write()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}