use crate::exceptions::TemplateProcessingException;
use crate::expression::TemplateValue;
use crate::temporal::TemporalValue;
use crate::util::{DateValue, ResourceLoaderUtils, TemplateWriter, Utf16String};
use super::IStandardJavaScriptSerializer;
#[derive(Clone, Copy, Debug)]
pub struct StandardJavaScriptSerializer {
delegate: JavaScriptSerializerDelegate,
}
impl StandardJavaScriptSerializer {
#[must_use]
pub fn new(use_jackson_if_available: bool) -> Self {
let delegate = if use_jackson_if_available {
if ResourceLoaderUtils::is_class_present("tools.jackson.databind.ObjectMapper") {
JavaScriptSerializerDelegate::Jackson3(Jackson3StandardJavaScriptSerializer)
} else {
JavaScriptSerializerDelegate::Jackson2(JacksonStandardJavaScriptSerializer)
}
} else {
JavaScriptSerializerDelegate::Default(DefaultStandardJavaScriptSerializer)
};
Self { delegate }
}
#[must_use]
pub const fn is_use_jackson_if_available(&self) -> bool {
matches!(
self.delegate,
JavaScriptSerializerDelegate::Jackson2(_) | JavaScriptSerializerDelegate::Jackson3(_)
)
}
}
impl IStandardJavaScriptSerializer for StandardJavaScriptSerializer {
fn serialize_value(
&self,
object: Option<&TemplateValue>,
writer: &mut dyn TemplateWriter,
) -> Result<(), TemplateProcessingException> {
self.delegate.serialize_value(object, writer).map_err(|error| {
TemplateProcessingException::with_cause(
Some(
"An exception was raised while trying to serialize object to JavaScript using the default serializer"
.to_owned(),
),
error,
)
})
}
}
#[derive(Clone, Copy, Debug)]
enum JavaScriptSerializerDelegate {
Jackson2(JacksonStandardJavaScriptSerializer),
Jackson3(Jackson3StandardJavaScriptSerializer),
Default(DefaultStandardJavaScriptSerializer),
}
impl JavaScriptSerializerDelegate {
fn serialize_value(
&self,
object: Option<&TemplateValue>,
writer: &mut dyn TemplateWriter,
) -> std::io::Result<()> {
match self {
Self::Jackson2(serializer) => serializer.serialize_value(object, writer),
Self::Jackson3(serializer) => serializer.serialize_value(object, writer),
Self::Default(serializer) => serializer.serialize_value(object, writer),
}
}
}
#[derive(Clone, Copy, Debug)]
struct JacksonStandardJavaScriptSerializer;
impl JacksonStandardJavaScriptSerializer {
fn serialize_value(
&self,
object: Option<&TemplateValue>,
writer: &mut dyn TemplateWriter,
) -> std::io::Result<()> {
write_value(object, writer, true)
}
}
#[derive(Clone, Copy, Debug)]
struct Jackson3StandardJavaScriptSerializer;
impl Jackson3StandardJavaScriptSerializer {
fn serialize_value(
&self,
object: Option<&TemplateValue>,
writer: &mut dyn TemplateWriter,
) -> std::io::Result<()> {
write_value(object, writer, true)
}
}
struct JacksonThymeleafISO8601DateFormat;
impl JacksonThymeleafISO8601DateFormat {
fn format(date: &DateValue) -> Utf16String {
crate::util::DateUtils::format_iso(Some(date))
.expect("non-null DateValue always has an ISO representation")
}
#[expect(dead_code, reason = "保留 Java 只写日期格式器的拒绝解析合同")]
fn parse(
_source: &Utf16String,
_position: i32,
) -> Result<DateValue, TemplateProcessingException> {
Err(TemplateProcessingException::new(Some(
"JacksonThymeleafISO8601DateFormat should never be asked for a 'parse' operation"
.to_owned(),
)))
}
}
struct JacksonThymeleafCharacterEscapes;
impl JacksonThymeleafCharacterEscapes {
#[expect(
dead_code,
reason = "保留 Jackson CharacterEscapes 对照方法和转义表语义"
)]
fn get_escape_codes_for_ascii() -> [i32; 128] {
let mut escapes = [0_i32; 128];
let mut control = 0;
while control < 0x20 {
escapes[control] = -1;
control += 1;
}
escapes[usize::from(b'"')] = i32::from(b'"');
escapes[usize::from(b'\\')] = i32::from(b'\\');
escapes[usize::from(b'/')] = -2;
escapes[usize::from(b'&')] = -2;
escapes
}
fn get_escape_sequence(character: u16) -> Option<&'static str> {
match character {
0x002F => Some("\\/"),
0x0026 => Some("\\u0026"),
_ => None,
}
}
fn custom_escape(unit: u16, previous: u16, escape_all_slashes: bool) -> Option<&'static str> {
match unit {
0x002F if escape_all_slashes || previous == u16::from(b'<') => {
Self::get_escape_sequence(unit)
}
0x0026 => Self::get_escape_sequence(unit),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug)]
struct DefaultStandardJavaScriptSerializer;
impl DefaultStandardJavaScriptSerializer {
fn serialize_value(
&self,
object: Option<&TemplateValue>,
writer: &mut dyn TemplateWriter,
) -> std::io::Result<()> {
write_value(object, writer, false)
}
}
fn write_value(
object: Option<&TemplateValue>,
writer: &mut dyn TemplateWriter,
escape_all_slashes: bool,
) -> std::io::Result<()> {
let Some(object) = object else {
return write_ascii(writer, "null");
};
match object {
TemplateValue::Null => write_ascii(writer, "null"),
TemplateValue::Boolean(value) => write_ascii(writer, if *value { "true" } else { "false" }),
TemplateValue::Number(_) => write_utf16_string(
writer,
&object
.to_utf16_string()
.unwrap_or_else(|| Utf16String::from_rust_str("null")),
),
TemplateValue::Character(unit) => write_json_string(writer, &[*unit], escape_all_slashes),
TemplateValue::String(value) | TemplateValue::SafeHtml(value) => {
write_json_string(writer, value.as_utf16(), escape_all_slashes)
}
TemplateValue::Bytes(bytes) => {
writer.write_utf16(&[u16::from(b'[')])?;
for (index, value) in bytes.iter().enumerate() {
if index != 0 {
writer.write_utf16(&[u16::from(b',')])?;
}
write_ascii(writer, &value.to_string())?;
}
writer.write_utf16(&[u16::from(b']')])
}
TemplateValue::List(values) => {
writer.write_utf16(&[u16::from(b'[')])?;
for (index, value) in values.iter().enumerate() {
if index != 0 {
writer.write_utf16(&[u16::from(b',')])?;
}
write_value(Some(value.as_ref()), writer, escape_all_slashes)?;
}
writer.write_utf16(&[u16::from(b']')])
}
TemplateValue::Map(entries) => {
writer.write_utf16(&[u16::from(b'{')])?;
for (index, (key, value)) in entries.iter().enumerate() {
if index != 0 {
writer.write_utf16(&[u16::from(b',')])?;
}
write_value(Some(key.as_ref()), writer, escape_all_slashes)?;
writer.write_utf16(&[u16::from(b':')])?;
write_value(Some(value.as_ref()), writer, escape_all_slashes)?;
}
writer.write_utf16(&[u16::from(b'}')])
}
TemplateValue::Literal(value) => {
let text = value
.get_value()
.cloned()
.unwrap_or_else(|| Utf16String::from_rust_str("null"));
write_json_string(writer, text.as_utf16(), escape_all_slashes)
}
TemplateValue::NoOp => write_json_string(writer, &[u16::from(b'_')], escape_all_slashes),
TemplateValue::Object(value) => {
if let Some(date) = value.as_any().downcast_ref::<DateValue>() {
let formatted = JacksonThymeleafISO8601DateFormat::format(date);
write_json_string(writer, formatted.as_utf16(), escape_all_slashes)
} else if let Some(temporal) = value.as_any().downcast_ref::<TemporalValue>() {
let formatted = temporal.to_javascript_iso_string();
write_json_string(writer, formatted.as_utf16(), escape_all_slashes)
} else if let Some(serializable) = value.serializable_value() {
write_value(serializable.as_deref(), writer, escape_all_slashes)
} else if let Some(properties) = value.serializable_properties() {
writer.write_utf16(&[u16::from(b'{')])?;
for (index, (name, property_value)) in properties.iter().enumerate() {
if index != 0 {
writer.write_utf16(&[u16::from(b',')])?;
}
write_json_string(writer, name.as_utf16(), escape_all_slashes)?;
writer.write_utf16(&[u16::from(b':')])?;
write_value(property_value.as_deref(), writer, escape_all_slashes)?;
}
writer.write_utf16(&[u16::from(b'}')])
} else {
write_json_string(
writer,
value.to_utf16_string().as_utf16(),
escape_all_slashes,
)
}
}
}
}
fn write_json_string(
writer: &mut dyn TemplateWriter,
input: &[u16],
escape_all_slashes: bool,
) -> std::io::Result<()> {
writer.write_utf16(&[u16::from(b'"')])?;
let mut previous = 0_u16;
for &unit in input {
match unit {
0x0008 => write_ascii(writer, "\\b")?,
0x0009 => write_ascii(writer, "\\t")?,
0x000A => write_ascii(writer, "\\n")?,
0x000C => write_ascii(writer, "\\f")?,
0x000D => write_ascii(writer, "\\r")?,
0x0022 => write_ascii(writer, "\\\"")?,
0x005C => write_ascii(writer, "\\\\")?,
0x002F | 0x0026 => {
if let Some(escaped) = JacksonThymeleafCharacterEscapes::custom_escape(
unit,
previous,
escape_all_slashes,
) {
write_ascii(writer, escaped)?;
} else {
writer.write_utf16(&[unit])?;
}
}
0x0000..=0x001F | 0x007F..=0x009F | 0x0080..=0xFFFF => {
write_ascii(writer, &format!("\\u{unit:04X}"))?;
}
_ => writer.write_utf16(&[unit])?,
}
previous = unit;
}
writer.write_utf16(&[u16::from(b'"')])
}
fn write_ascii(writer: &mut dyn TemplateWriter, input: &str) -> std::io::Result<()> {
writer.write_utf16(&input.encode_utf16().collect::<Vec<_>>())
}
fn write_utf16_string(writer: &mut dyn TemplateWriter, input: &Utf16String) -> std::io::Result<()> {
writer.write_utf16(input.as_utf16())
}