use std::any::Any;
use std::cmp::Ordering;
use std::error::Error;
use std::fmt::{Debug, Formatter};
use std::sync::Arc;
use crate::util::{EvaluationValue, NumberValue, Utf16String, double_string};
use super::LiteralValue;
pub trait TemplateObject: Any + Send + Sync {
fn class_name(&self) -> &str;
fn to_utf16_string(&self) -> Utf16String;
fn as_any(&self) -> &dyn Any;
fn template_equals(&self, other: &dyn TemplateObject) -> bool {
std::ptr::eq(self.as_any(), other.as_any())
}
fn template_compare_to(
&self,
_other: &dyn TemplateObject,
) -> Option<Result<Ordering, TemplateObjectComparisonError>> {
None
}
fn iterable_values(&self) -> Option<Vec<Arc<TemplateValue>>> {
None
}
fn serializable_properties(&self) -> Option<Vec<(Utf16String, Option<Arc<TemplateValue>>)>> {
None
}
fn serializable_value(&self) -> Option<Option<Arc<TemplateValue>>> {
None
}
fn get_property(
&self,
_property_name: &Utf16String,
) -> Option<Result<Option<Arc<TemplateValue>>, TemplateObjectPropertyError>> {
None
}
fn invoke_method(
&self,
_method_name: &Utf16String,
_arguments: &[Option<Arc<TemplateValue>>],
) -> Option<Result<Option<Arc<TemplateValue>>, TemplateObjectMethodError>> {
None
}
fn resolve_lazy_context_variable(&self) -> Option<Option<Arc<TemplateValue>>> {
None
}
}
pub type TemplateObjectComparisonError = Box<dyn Error + Send + Sync>;
pub type TemplateObjectPropertyError = Box<dyn Error + Send + Sync>;
pub type TemplateObjectMethodError = Box<dyn Error + Send + Sync>;
#[derive(Clone)]
pub enum TemplateValue {
Null,
Boolean(bool),
Number(NumberValue),
Character(u16),
String(Arc<Utf16String>),
Bytes(Arc<Vec<i8>>),
List(Arc<Vec<Arc<TemplateValue>>>),
Map(Arc<Vec<(Arc<TemplateValue>, Arc<TemplateValue>)>>),
Literal(Arc<LiteralValue>),
NoOp,
Object(Arc<dyn TemplateObject>),
SafeHtml(Arc<Utf16String>),
}
impl TemplateValue {
#[must_use]
pub fn string(value: Utf16String) -> Self {
Self::String(Arc::new(value))
}
#[must_use]
pub fn safe_html(value: Utf16String) -> Self {
Self::SafeHtml(Arc::new(value))
}
#[must_use]
pub fn to_evaluation_value(&self) -> EvaluationValue {
match self {
Self::Null => EvaluationValue::Null,
Self::Boolean(value) => EvaluationValue::Boolean(*value),
Self::Number(value) => EvaluationValue::Number(value.clone()),
Self::Character(value) => EvaluationValue::Character(*value),
Self::String(value) | Self::SafeHtml(value) => {
EvaluationValue::String(value.as_ref().clone())
}
Self::Literal(value) => EvaluationValue::LiteralValue(Arc::clone(value)),
Self::Bytes(_) => EvaluationValue::Other("[B".to_owned()),
Self::List(_) => EvaluationValue::Other("java.util.List".to_owned()),
Self::Map(_) => EvaluationValue::Other("java.util.Map".to_owned()),
Self::Object(value) => EvaluationValue::Other(value.class_name().to_owned()),
Self::NoOp => {
EvaluationValue::Other("org.thymeleaf.standard.expression.NoOpToken".to_owned())
}
}
}
#[must_use]
pub fn to_utf16_string(&self) -> Option<Utf16String> {
let text = match self {
Self::Null => return Some(Utf16String::from_rust_str("null")),
Self::Boolean(value) => value.to_string(),
Self::Number(NumberValue::BigDecimal(value)) => value.to_string(),
Self::Number(NumberValue::BigInteger(value)) => value.to_string(),
Self::Number(NumberValue::Byte(value)) => value.to_string(),
Self::Number(NumberValue::Short(value)) => value.to_string(),
Self::Number(NumberValue::Integer(value)) => value.to_string(),
Self::Number(NumberValue::Long(value)) => value.to_string(),
Self::Number(NumberValue::Float(value)) => value.to_string(),
Self::Number(NumberValue::Double(value)) => double_string(*value),
Self::Number(NumberValue::Other { double_value, .. }) => double_value.to_string(),
Self::Character(value) => {
return Some(Utf16String::from_utf16(vec![*value]));
}
Self::String(value) | Self::SafeHtml(value) => return Some(value.as_ref().clone()),
Self::Bytes(value) => format!("[B@{:x}", Arc::as_ptr(value) as usize),
Self::List(values) => {
let mut units = vec![b'[' as u16];
for (index, value) in values.iter().enumerate() {
if index != 0 {
units.extend_from_slice(&[b',' as u16, b' ' as u16]);
}
units.extend_from_slice(
value
.to_utf16_string()
.unwrap_or_else(|| Utf16String::from_rust_str("null"))
.as_utf16(),
);
}
units.push(b']' as u16);
return Some(Utf16String::from_utf16(units));
}
Self::Map(entries) => {
let mut units = vec![b'{' as u16];
for (index, (key, value)) in entries.iter().enumerate() {
if index != 0 {
units.extend_from_slice(&[b',' as u16, b' ' as u16]);
}
units.extend_from_slice(
key.to_utf16_string()
.unwrap_or_else(|| Utf16String::from_rust_str("null"))
.as_utf16(),
);
units.push(b'=' as u16);
units.extend_from_slice(
value
.to_utf16_string()
.unwrap_or_else(|| Utf16String::from_rust_str("null"))
.as_utf16(),
);
}
units.push(b'}' as u16);
return Some(Utf16String::from_utf16(units));
}
Self::Literal(value) => return value.get_value().cloned(),
Self::NoOp => return Some(Utf16String::from_rust_str("_")),
Self::Object(value) => return Some(value.to_utf16_string()),
};
Some(Utf16String::from_rust_str(&text))
}
#[must_use]
pub fn template_equals(&self, other: &Self) -> bool {
match (self, other) {
(Self::Null, Self::Null) => true,
(Self::Boolean(left), Self::Boolean(right)) => left == right,
(Self::Number(left), Self::Number(right)) => number_equals(left, right),
(Self::Character(left), Self::Character(right)) => left == right,
(Self::String(left), Self::String(right))
| (Self::SafeHtml(left), Self::SafeHtml(right))
| (Self::String(left), Self::SafeHtml(right))
| (Self::SafeHtml(left), Self::String(right)) => left == right,
(Self::Bytes(left), Self::Bytes(right)) => Arc::ptr_eq(left, right),
(Self::List(left), Self::List(right)) => {
left.len() == right.len()
&& left
.iter()
.zip(right.iter())
.all(|(left, right)| left.template_equals(right))
}
(Self::Map(left), Self::Map(right)) => map_equals(left, right),
(Self::Object(left), Self::Object(right)) => {
left.class_name() == right.class_name() && left.template_equals(right.as_ref())
}
(Self::Literal(left), Self::Literal(right)) => Arc::ptr_eq(left, right),
(Self::NoOp, Self::NoOp) => true,
_ => false,
}
}
pub fn template_compare_to(
&self,
other: &Self,
) -> Option<Result<Ordering, TemplateObjectComparisonError>> {
match (self, other) {
(Self::Boolean(left), Self::Boolean(right)) => Some(Ok(left.cmp(right))),
(Self::Character(left), Self::Character(right)) => Some(Ok(left.cmp(right))),
(Self::String(left), Self::String(right))
| (Self::SafeHtml(left), Self::SafeHtml(right))
| (Self::String(left), Self::SafeHtml(right))
| (Self::SafeHtml(left), Self::String(right)) => {
Some(Ok(left.as_utf16().cmp(right.as_utf16())))
}
(Self::Number(left), Self::Number(right)) => number_compare(left, right).map(Ok),
(Self::Object(left), Self::Object(right))
if left.class_name() == right.class_name() =>
{
left.template_compare_to(right.as_ref())
}
_ => None,
}
}
#[must_use]
pub fn class_name(&self) -> &str {
match self {
Self::Null => "null",
Self::Boolean(_) => "java.lang.Boolean",
Self::Number(number) => number_class_name(number),
Self::Character(_) => "java.lang.Character",
Self::String(_) | Self::SafeHtml(_) => "java.lang.String",
Self::Bytes(_) => "[B",
Self::List(_) => "java.util.List",
Self::Map(_) => "java.util.Map",
Self::Literal(_) => "org.thymeleaf.standard.expression.LiteralValue",
Self::NoOp => "org.thymeleaf.standard.expression.NoOpToken",
Self::Object(object) => object.class_name(),
}
}
}
impl super::ConversionObject for TemplateValue {
fn to_utf16_string(
&self,
) -> Result<super::Utf16StringConversionResult<'_>, super::StandardConversionError> {
Ok(match self {
Self::String(value) | Self::SafeHtml(value) => {
super::Utf16StringConversionResult::Borrowed(value)
}
_ => match self.to_utf16_string() {
Some(value) => super::Utf16StringConversionResult::Owned(value),
None => super::Utf16StringConversionResult::Null,
},
})
}
}
fn number_equals(left: &NumberValue, right: &NumberValue) -> bool {
match (left, right) {
(NumberValue::BigDecimal(left), NumberValue::BigDecimal(right)) => left == right,
(NumberValue::BigInteger(left), NumberValue::BigInteger(right)) => left == right,
(NumberValue::Byte(left), NumberValue::Byte(right)) => left == right,
(NumberValue::Short(left), NumberValue::Short(right)) => left == right,
(NumberValue::Integer(left), NumberValue::Integer(right)) => left == right,
(NumberValue::Long(left), NumberValue::Long(right)) => left == right,
(NumberValue::Float(left), NumberValue::Float(right)) => {
normalized_f32_bits(*left) == normalized_f32_bits(*right)
}
(NumberValue::Double(left), NumberValue::Double(right)) => {
normalized_f64_bits(*left) == normalized_f64_bits(*right)
}
(
NumberValue::Other {
class_name: left_class,
double_value: left,
},
NumberValue::Other {
class_name: right_class,
double_value: right,
},
) => left_class == right_class && normalized_f64_bits(*left) == normalized_f64_bits(*right),
_ => false,
}
}
fn number_compare(left: &NumberValue, right: &NumberValue) -> Option<Ordering> {
match (left, right) {
(NumberValue::BigDecimal(left), NumberValue::BigDecimal(right)) => {
Some(left.compare_java(right))
}
(NumberValue::BigInteger(left), NumberValue::BigInteger(right)) => Some(left.cmp(right)),
(NumberValue::Byte(left), NumberValue::Byte(right)) => Some(left.cmp(right)),
(NumberValue::Short(left), NumberValue::Short(right)) => Some(left.cmp(right)),
(NumberValue::Integer(left), NumberValue::Integer(right)) => Some(left.cmp(right)),
(NumberValue::Long(left), NumberValue::Long(right)) => Some(left.cmp(right)),
(NumberValue::Float(left), NumberValue::Float(right)) => Some(f32_cmp(*left, *right)),
(NumberValue::Double(left), NumberValue::Double(right)) => Some(f64_cmp(*left, *right)),
_ => None,
}
}
fn normalized_f32_bits(value: f32) -> u32 {
if value.is_nan() {
f32::NAN.to_bits()
} else {
value.to_bits()
}
}
fn normalized_f64_bits(value: f64) -> u64 {
if value.is_nan() {
f64::NAN.to_bits()
} else {
value.to_bits()
}
}
fn f32_cmp(left: f32, right: f32) -> Ordering {
if left < right {
return Ordering::Less;
}
if left > right {
return Ordering::Greater;
}
i32::from_ne_bytes(normalized_f32_bits(left).to_ne_bytes()).cmp(&i32::from_ne_bytes(
normalized_f32_bits(right).to_ne_bytes(),
))
}
fn f64_cmp(left: f64, right: f64) -> Ordering {
if left < right {
return Ordering::Less;
}
if left > right {
return Ordering::Greater;
}
i64::from_ne_bytes(normalized_f64_bits(left).to_ne_bytes()).cmp(&i64::from_ne_bytes(
normalized_f64_bits(right).to_ne_bytes(),
))
}
fn map_equals(
left: &[(Arc<TemplateValue>, Arc<TemplateValue>)],
right: &[(Arc<TemplateValue>, Arc<TemplateValue>)],
) -> bool {
if left.len() != right.len() {
return false;
}
let mut matched = vec![false; right.len()];
left.iter().all(|(left_key, left_value)| {
right
.iter()
.enumerate()
.find(|(index, (right_key, right_value))| {
!matched[*index]
&& left_key.template_equals(right_key)
&& left_value.template_equals(right_value)
})
.is_some_and(|(index, _)| {
matched[index] = true;
true
})
})
}
impl Debug for TemplateValue {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Null => formatter.write_str("Null"),
Self::Boolean(value) => formatter.debug_tuple("Boolean").field(value).finish(),
Self::Number(value) => formatter.debug_tuple("Number").field(value).finish(),
Self::Character(value) => formatter.debug_tuple("Character").field(value).finish(),
Self::String(value) => formatter.debug_tuple("String").field(value).finish(),
Self::Bytes(value) => formatter.debug_tuple("Bytes").field(value).finish(),
Self::List(value) => formatter.debug_tuple("List").field(value).finish(),
Self::Map(value) => formatter.debug_tuple("Map").field(value).finish(),
Self::Literal(value) => formatter.debug_tuple("Literal").field(value).finish(),
Self::NoOp => formatter.write_str("NoOp"),
Self::Object(value) => formatter
.debug_struct("Object")
.field("class_name", &value.class_name())
.finish_non_exhaustive(),
Self::SafeHtml(value) => formatter.debug_tuple("SafeHtml").field(value).finish(),
}
}
}
fn number_class_name(number: &NumberValue) -> &str {
match number {
NumberValue::BigDecimal(_) => "java.math.BigDecimal",
NumberValue::BigInteger(_) => "java.math.BigInteger",
NumberValue::Byte(_) => "java.lang.Byte",
NumberValue::Short(_) => "java.lang.Short",
NumberValue::Integer(_) => "java.lang.Integer",
NumberValue::Long(_) => "java.lang.Long",
NumberValue::Float(_) => "java.lang.Float",
NumberValue::Double(_) => "java.lang.Double",
NumberValue::Other { class_name, .. } => class_name,
}
}