use std::fmt::{Display, Formatter};
use std::ptr;
use std::sync::Arc;
use num_bigint::BigInt;
use num_traits::Zero;
use thiserror::Error;
use crate::expression::{LiteralValue, ObjectArrayValue};
use super::{BigDecimalValue, NumberValue, Utf16String, ValidateError};
const JAVA_BMP_DECIMAL_ZEROES: &[u16] = &[
0x0030, 0x0660, 0x06F0, 0x07C0, 0x0966, 0x09E6, 0x0A66, 0x0AE6, 0x0B66, 0x0BE6, 0x0C66, 0x0CE6,
0x0D66, 0x0DE6, 0x0E50, 0x0ED0, 0x0F20, 0x1040, 0x1090, 0x17E0, 0x1810, 0x1946, 0x19D0, 0x1A80,
0x1A90, 0x1B50, 0x1BB0, 0x1C40, 0x1C50, 0xA620, 0xA8D0, 0xA900, 0xA9D0, 0xA9F0, 0xAA50, 0xABF0,
0xFF10,
];
#[derive(Clone, Debug, PartialEq)]
pub enum EvaluationValue {
Null,
Boolean(bool),
Number(NumberValue),
Character(u16),
String(Utf16String),
LiteralValue(Arc<LiteralValue>),
Other(String),
}
#[derive(Debug)]
pub enum BigDecimalResult<'a> {
Borrowed(&'a BigDecimalValue),
Owned(BigDecimalValue),
}
impl<'a> BigDecimalResult<'a> {
#[must_use]
pub fn as_decimal(&self) -> &BigDecimalValue {
match self {
Self::Borrowed(value) => value,
Self::Owned(value) => value,
}
}
#[must_use]
pub fn is_borrowed_from(&self, source: &BigDecimalValue) -> bool {
matches!(self, Self::Borrowed(value) if ptr::eq(*value, source))
}
}
pub trait HashCodeValue {
fn hash_code(&self) -> i32;
}
impl HashCodeValue for i32 {
fn hash_code(&self) -> i32 {
*self
}
}
impl HashCodeValue for String {
fn hash_code(&self) -> i32 {
self.encode_utf16().fold(0_i32, |hash, unit| {
hash.wrapping_mul(31).wrapping_add(i32::from(unit))
})
}
}
impl HashCodeValue for Utf16String {
fn hash_code(&self) -> i32 {
self.as_utf16().iter().fold(0_i32, |hash, unit| {
hash.wrapping_mul(31).wrapping_add(i32::from(*unit))
})
}
}
#[derive(Clone, Debug)]
pub struct MapEntry<T> {
entry_key: Option<T>,
entry_value: Option<T>,
class_name: String,
}
impl<T: PartialEq> PartialEq for MapEntry<T> {
fn eq(&self, other: &Self) -> bool {
self.entry_key == other.entry_key && self.entry_value == other.entry_value
}
}
impl<T> MapEntry<T> {
#[must_use]
pub fn new(key: Option<T>, value: Option<T>) -> Self {
Self {
entry_key: key,
entry_value: value,
class_name: "org.thymeleaf.util.EvaluationUtils$MapEntry".to_owned(),
}
}
#[must_use]
pub fn raw(class_name: impl Into<String>, key: Option<T>, value: Option<T>) -> Self {
Self {
entry_key: key,
entry_value: value,
class_name: class_name.into(),
}
}
#[must_use]
pub fn get_key(&self) -> Option<&T> {
self.entry_key.as_ref()
}
#[must_use]
pub fn get_value(&self) -> Option<&T> {
self.entry_value.as_ref()
}
#[must_use]
pub fn class_name(&self) -> &str {
&self.class_name
}
pub fn set_value(&mut self, _value: Option<T>) -> Result<Option<T>, EvaluationError> {
Err(EvaluationError::UnsupportedOperation)
}
}
impl<T: HashCodeValue> MapEntry<T> {
#[must_use]
pub fn hash_code(&self) -> i32 {
let key_hash = self.entry_key.as_ref().map_or(0, HashCodeValue::hash_code);
let value_hash = self
.entry_value
.as_ref()
.map_or(0, HashCodeValue::hash_code);
key_hash.wrapping_mul(31).wrapping_add(value_hash)
}
}
impl<T: Display> Display for MapEntry<T> {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
match &self.entry_key {
Some(key) => write!(formatter, "{key}")?,
None => formatter.write_str("null")?,
}
formatter.write_str("=")?;
match &self.entry_value {
Some(value) => write!(formatter, "{value}"),
None => formatter.write_str("null"),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum EvaluationElement<T> {
Object(T),
Byte(i8),
Short(i16),
Integer(i32),
Long(i64),
Float(f32),
Double(f64),
Boolean(bool),
Character(u16),
MapEntry(Arc<MapEntry<T>>),
}
pub enum EvaluationTarget<'a, T> {
Iterable(&'a [Option<T>]),
Map(&'a [Arc<MapEntry<T>>]),
Bytes(&'a [i8]),
Shorts(&'a [i16]),
Integers(&'a [i32]),
Longs(&'a [i64]),
Floats(&'a [f32]),
Doubles(&'a [f64]),
Booleans(&'a [bool]),
Characters(&'a [u16]),
ReferenceArray(&'a ObjectArrayValue<T>),
Other(&'a T),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EvaluationListType {
EmptyList,
UnmodifiableRandomAccessList,
}
#[derive(Clone, Debug, PartialEq)]
pub struct EvaluationList<T> {
list_type: EvaluationListType,
elements: Vec<Option<EvaluationElement<T>>>,
}
impl<T> EvaluationList<T> {
#[must_use]
pub const fn list_type(&self) -> EvaluationListType {
self.list_type
}
#[must_use]
pub fn as_slice(&self) -> &[Option<EvaluationElement<T>>] {
&self.elements
}
#[must_use]
pub fn len(&self) -> usize {
self.elements.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.elements.is_empty()
}
}
#[derive(Debug)]
pub enum EvaluationArray<'a, T> {
Borrowed(&'a ObjectArrayValue<T>),
Owned(ObjectArrayValue<EvaluationElement<T>>),
}
impl<'a, T> EvaluationArray<'a, T> {
#[must_use]
pub fn is_borrowed_from(&self, source: &ObjectArrayValue<T>) -> bool {
matches!(self, Self::Borrowed(value) if ptr::eq(*value, source))
}
#[must_use]
pub fn as_owned_array(&self) -> Option<&ObjectArrayValue<EvaluationElement<T>>> {
match self {
Self::Borrowed(_) => None,
Self::Owned(value) => Some(value),
}
}
}
#[derive(Clone, Debug, Error, Eq, PartialEq)]
pub enum EvaluationError {
#[error(transparent)]
Validation(#[from] ValidateError),
#[error(
"Cannot invoke \"String.trim()\" because the return value of \"LiteralValue.getValue()\" is null"
)]
NullPointer,
#[error("Infinite or NaN")]
NumberFormat,
#[error("class {array_class_name} cannot be cast to class [Ljava.lang.Object;")]
ClassCast {
array_class_name: &'static str,
},
#[error("")]
UnsupportedOperation,
}
impl EvaluationError {
#[must_use]
pub const fn class_name(&self) -> &'static str {
match self {
Self::Validation(error) => error.class_name(),
Self::NullPointer => "java.lang.NullPointerException",
Self::NumberFormat => "java.lang.NumberFormatException",
Self::ClassCast { .. } => "java.lang.ClassCastException",
Self::UnsupportedOperation => "java.lang.UnsupportedOperationException",
}
}
}
pub struct EvaluationUtils;
impl EvaluationUtils {
pub fn evaluate_as_boolean(condition: &EvaluationValue) -> Result<bool, EvaluationError> {
match condition {
EvaluationValue::Null => Ok(false),
EvaluationValue::Boolean(value) => Ok(*value),
EvaluationValue::Number(number) => Ok(number_is_non_zero(number)),
EvaluationValue::Character(value) => Ok(*value != 0),
EvaluationValue::String(value) => Ok(string_is_true(value)),
EvaluationValue::LiteralValue(value) => value
.get_value()
.map(string_is_true)
.ok_or(EvaluationError::NullPointer),
EvaluationValue::Other(_) => Ok(true),
}
}
pub fn evaluate_as_number(
object: &EvaluationValue,
) -> Result<Option<BigDecimalResult<'_>>, EvaluationError> {
match object {
EvaluationValue::Number(number) => number_as_decimal(number),
EvaluationValue::String(value) if !value.is_empty() => {
Ok(parse_java_big_decimal(value).map(BigDecimalResult::Owned))
}
_ => Ok(None),
}
}
#[must_use]
pub fn evaluate_as_list<T: Clone>(value: Option<EvaluationTarget<'_, T>>) -> EvaluationList<T> {
let Some(value) = value else {
return EvaluationList {
list_type: EvaluationListType::EmptyList,
elements: Vec::new(),
};
};
let elements = match value {
EvaluationTarget::Iterable(values) => values
.iter()
.map(|value| value.clone().map(EvaluationElement::Object))
.collect(),
EvaluationTarget::Map(entries) => entries
.iter()
.map(|entry| {
Some(EvaluationElement::MapEntry(Arc::new(MapEntry::new(
entry.entry_key.clone(),
entry.entry_value.clone(),
))))
})
.collect(),
EvaluationTarget::Bytes(values) => boxed(values, EvaluationElement::Byte),
EvaluationTarget::Shorts(values) => boxed(values, EvaluationElement::Short),
EvaluationTarget::Integers(values) => boxed(values, EvaluationElement::Integer),
EvaluationTarget::Longs(values) => boxed(values, EvaluationElement::Long),
EvaluationTarget::Floats(values) => boxed(values, EvaluationElement::Float),
EvaluationTarget::Doubles(values) => boxed(values, EvaluationElement::Double),
EvaluationTarget::Booleans(values) => boxed(values, EvaluationElement::Boolean),
EvaluationTarget::Characters(values) => boxed(values, EvaluationElement::Character),
EvaluationTarget::ReferenceArray(values) => values
.as_slice()
.iter()
.map(|value| value.clone().map(EvaluationElement::Object))
.collect(),
EvaluationTarget::Other(value) => {
vec![Some(EvaluationElement::Object(value.clone()))]
}
};
EvaluationList {
list_type: EvaluationListType::UnmodifiableRandomAccessList,
elements,
}
}
pub fn evaluate_as_array<T: Clone>(
value: Option<EvaluationTarget<'_, T>>,
) -> Result<EvaluationArray<'_, T>, EvaluationError> {
let Some(value) = value else {
return Ok(EvaluationArray::Owned(ObjectArrayValue::object(vec![None])));
};
match value {
EvaluationTarget::ReferenceArray(values) => Ok(EvaluationArray::Borrowed(values)),
EvaluationTarget::Bytes(_) => Err(class_cast("[B")),
EvaluationTarget::Shorts(_) => Err(class_cast("[S")),
EvaluationTarget::Integers(_) => Err(class_cast("[I")),
EvaluationTarget::Longs(_) => Err(class_cast("[J")),
EvaluationTarget::Floats(_) => Err(class_cast("[F")),
EvaluationTarget::Doubles(_) => Err(class_cast("[D")),
EvaluationTarget::Booleans(_) => Err(class_cast("[Z")),
EvaluationTarget::Characters(_) => Err(class_cast("[C")),
EvaluationTarget::Iterable(values) => {
Ok(EvaluationArray::Owned(ObjectArrayValue::object(
values
.iter()
.map(|value| value.clone().map(EvaluationElement::Object))
.collect(),
)))
}
EvaluationTarget::Map(entries) => Ok(EvaluationArray::Owned(ObjectArrayValue::object(
entries
.iter()
.map(|entry| Some(EvaluationElement::MapEntry(Arc::clone(entry))))
.collect(),
))),
EvaluationTarget::Other(value) => {
Ok(EvaluationArray::Owned(ObjectArrayValue::object(vec![
Some(EvaluationElement::Object(value.clone())),
])))
}
}
}
}
fn number_is_non_zero(number: &NumberValue) -> bool {
match number {
NumberValue::BigDecimal(value) => !value.unscaled_value().is_zero(),
NumberValue::BigInteger(value) => !value.is_zero(),
NumberValue::Byte(value) => *value != 0,
NumberValue::Short(value) => *value != 0,
NumberValue::Integer(value) => *value != 0,
NumberValue::Long(value) => *value != 0,
NumberValue::Float(value) => f64::from(*value) != 0.0,
NumberValue::Double(value) => *value != 0.0,
NumberValue::Other { double_value, .. } => *double_value != 0.0,
}
}
fn string_is_true(value: &Utf16String) -> bool {
let trimmed = trim(value.as_utf16());
!equals_ascii_ignore_case(trimmed, b"false")
&& !equals_ascii_ignore_case(trimmed, b"off")
&& !equals_ascii_ignore_case(trimmed, b"no")
}
fn equals_ascii_ignore_case(value: &[u16], expected: &[u8]) -> bool {
if value.len() != expected.len() {
return false;
}
for (actual, expected) in value.iter().zip(expected) {
let Ok(actual) = u8::try_from(*actual) else {
return false;
};
if !actual.eq_ignore_ascii_case(expected) {
return false;
}
}
true
}
fn number_as_decimal(
number: &NumberValue,
) -> Result<Option<BigDecimalResult<'_>>, EvaluationError> {
let result = match number {
NumberValue::BigDecimal(value) => return Ok(Some(BigDecimalResult::Borrowed(value))),
NumberValue::BigInteger(value) => BigDecimalValue::from_unscaled(value.clone(), 0),
NumberValue::Byte(value) => BigDecimalValue::from_unscaled(BigInt::from(*value), 0),
NumberValue::Short(value) => BigDecimalValue::from_unscaled(BigInt::from(*value), 0),
NumberValue::Integer(value) => BigDecimalValue::from_unscaled(BigInt::from(*value), 0),
NumberValue::Long(value) => BigDecimalValue::from_unscaled(BigInt::from(*value), 0),
NumberValue::Float(value) => BigDecimalValue::from_f64_exact(f64::from(*value))
.ok_or(EvaluationError::NumberFormat)?,
NumberValue::Double(value) => {
BigDecimalValue::from_f64_exact(*value).ok_or(EvaluationError::NumberFormat)?
}
NumberValue::Other { .. } => return Ok(None),
};
Ok(Some(BigDecimalResult::Owned(result)))
}
fn parse_java_big_decimal(value: &Utf16String) -> Option<BigDecimalValue> {
let units = value.as_utf16();
let first = units[0];
if !((u16::from(b'0')..=u16::from(b'9')).contains(&first)
|| first == u16::from(b'+')
|| first == u16::from(b'-'))
{
return None;
}
let trimmed = trim(units);
let mut ascii = String::with_capacity(trimmed.len());
for unit in trimmed {
if matches!(*unit, value if value == u16::from(b'+') || value == u16::from(b'-')
|| value == u16::from(b'.') || value == u16::from(b'e') || value == u16::from(b'E'))
{
ascii.push(u8::try_from(*unit).expect("matched ASCII syntax unit") as char);
} else {
ascii.push(char::from(b'0' + decimal_digit(*unit)?));
}
}
BigDecimalValue::parse(&ascii).ok()
}
fn trim(units: &[u16]) -> &[u16] {
let mut start = 0;
while start < units.len() && units[start] <= 0x20 {
start += 1;
}
let mut end = units.len();
while end > start && units[end - 1] <= 0x20 {
end -= 1;
}
&units[start..end]
}
fn decimal_digit(unit: u16) -> Option<u8> {
for zero in JAVA_BMP_DECIMAL_ZEROES {
if let Some(offset) = unit.checked_sub(*zero)
&& offset <= 9
{
return Some(offset as u8);
}
}
None
}
fn boxed<T: Copy, U>(
values: &[T],
constructor: impl Fn(T) -> EvaluationElement<U>,
) -> Vec<Option<EvaluationElement<U>>> {
values
.iter()
.copied()
.map(|value| Some(constructor(value)))
.collect()
}
fn class_cast(array_class_name: &'static str) -> EvaluationError {
EvaluationError::ClassCast { array_class_name }
}