use std::io::{self, Write};
use std::sync::{Arc, Mutex};
use encoding_rs::{CoderResult, Encoder, Encoding};
use crate::exceptions::TemplateOutputException;
use crate::util::{Charset, TemplateWriter};
use super::i_throttled_template_writer_control::IThrottledTemplateWriterControl;
use super::template_flow_controller::TemplateFlowController;
use super::throttled_template_writer_output_stream_adapter::ThrottledTemplateWriterOutputStreamAdapter;
use super::throttled_template_writer_writer_adapter::ThrottledTemplateWriterWriterAdapter;
#[expect(dead_code, reason = "保留 Java 私有适配器接口的完整对象级合同")]
trait IThrottledTemplateWriterAdapter {
fn is_overflown(&self) -> bool;
fn is_stopped(&self) -> bool;
fn get_written_count(&self) -> i32;
fn get_max_overflow_size(&self) -> i32;
fn get_overflow_grow_count(&self) -> i32;
fn allow(&mut self, limit: i32) -> Result<(), TemplateOutputException>;
}
impl IThrottledTemplateWriterAdapter for ThrottledTemplateWriterWriterAdapter {
fn is_overflown(&self) -> bool {
ThrottledTemplateWriterWriterAdapter::is_overflown(self)
}
fn is_stopped(&self) -> bool {
ThrottledTemplateWriterWriterAdapter::is_stopped(self)
}
fn get_written_count(&self) -> i32 {
ThrottledTemplateWriterWriterAdapter::get_written_count(self)
}
fn get_max_overflow_size(&self) -> i32 {
ThrottledTemplateWriterWriterAdapter::get_max_overflow_size(self)
}
fn get_overflow_grow_count(&self) -> i32 {
ThrottledTemplateWriterWriterAdapter::get_overflow_grow_count(self)
}
fn allow(&mut self, limit: i32) -> Result<(), TemplateOutputException> {
ThrottledTemplateWriterWriterAdapter::allow(self, limit)
}
}
impl IThrottledTemplateWriterAdapter for ThrottledTemplateWriterOutputStreamAdapter {
fn is_overflown(&self) -> bool {
ThrottledTemplateWriterOutputStreamAdapter::is_overflown(self)
}
fn is_stopped(&self) -> bool {
ThrottledTemplateWriterOutputStreamAdapter::is_stopped(self)
}
fn get_written_count(&self) -> i32 {
ThrottledTemplateWriterOutputStreamAdapter::get_written_count(self)
}
fn get_max_overflow_size(&self) -> i32 {
ThrottledTemplateWriterOutputStreamAdapter::get_max_overflow_size(self)
}
fn get_overflow_grow_count(&self) -> i32 {
ThrottledTemplateWriterOutputStreamAdapter::get_overflow_grow_count(self)
}
fn allow(&mut self, limit: i32) -> Result<(), TemplateOutputException> {
ThrottledTemplateWriterOutputStreamAdapter::allow(self, limit)
}
}
enum ThrottledTemplateWriterAdapterMode {
Characters(ThrottledTemplateWriterWriterAdapter),
Bytes {
adapter: ThrottledTemplateWriterOutputStreamAdapter,
encoder: Encoder,
pending_bytes: Vec<u8>,
},
}
pub(crate) struct ThrottledTemplateWriter {
template_name: String,
flow_controller: Arc<Mutex<TemplateFlowController>>,
adapter: Option<ThrottledTemplateWriterAdapterMode>,
flushable: bool,
}
impl ThrottledTemplateWriter {
pub(crate) fn new(
template_name: String,
flow_controller: Arc<Mutex<TemplateFlowController>>,
) -> Self {
Self {
template_name,
flow_controller,
adapter: None,
flushable: false,
}
}
pub(crate) fn set_output_writer(
&mut self,
writer: Box<dyn TemplateWriter>,
) -> Result<(), TemplateOutputException> {
if matches!(
self.adapter,
Some(ThrottledTemplateWriterAdapterMode::Bytes { .. })
) {
return Err(self.mode_error(
"The throttled processor has already been initialized to use byte-based output \
(OutputStream), but a Writer has been specified.",
));
}
if self.adapter.is_none() {
self.adapter = Some(ThrottledTemplateWriterAdapterMode::Characters(
ThrottledTemplateWriterWriterAdapter::new(
self.template_name.clone(),
Arc::clone(&self.flow_controller),
),
));
}
if let Some(ThrottledTemplateWriterAdapterMode::Characters(adapter)) = self.adapter.as_mut()
{
adapter.set_writer(writer);
}
Ok(())
}
pub(crate) fn set_output_stream(
&mut self,
output_stream: Box<dyn Write + Send>,
charset: &Charset,
max_output_in_bytes: i32,
) -> Result<(), TemplateOutputException> {
if matches!(
self.adapter,
Some(ThrottledTemplateWriterAdapterMode::Characters(_))
) {
return Err(self.mode_error(
"The throttled processor has already been initialized to use char-based output \
(Writer), but an OutputStream has been specified.",
));
}
if self.adapter.is_none() {
let increment = if max_output_in_bytes == i32::MAX {
128
} else {
(max_output_in_bytes / 8).clamp(16, 128) as usize
};
let encoding = Encoding::for_label(charset.name().as_bytes())
.expect("Charset guarantees an encoding_rs-supported canonical name");
self.adapter = Some(ThrottledTemplateWriterAdapterMode::Bytes {
adapter: ThrottledTemplateWriterOutputStreamAdapter::new(
self.template_name.clone(),
Arc::clone(&self.flow_controller),
increment,
),
encoder: encoding.new_encoder(),
pending_bytes: Vec::new(),
});
}
if let Some(ThrottledTemplateWriterAdapterMode::Bytes { adapter, .. }) =
self.adapter.as_mut()
{
adapter.set_output_stream(output_stream);
}
Ok(())
}
pub(crate) fn allow(&mut self, limit: i32) -> Result<(), TemplateOutputException> {
match self.adapter_mut()? {
ThrottledTemplateWriterAdapterMode::Characters(adapter) => adapter.allow(limit),
ThrottledTemplateWriterAdapterMode::Bytes { adapter, .. } => adapter.allow(limit),
}
}
pub(crate) fn write_utf16(&mut self, characters: &[u16]) -> io::Result<()> {
self.flushable = true;
match self.adapter_io_mut()? {
ThrottledTemplateWriterAdapterMode::Characters(adapter) => {
adapter.write_utf16(characters)
}
ThrottledTemplateWriterAdapterMode::Bytes {
encoder,
pending_bytes,
..
} => {
pending_bytes.extend(Self::encode_utf16(encoder, characters, false));
Ok(())
}
}
}
pub(crate) fn flush(&mut self) -> io::Result<()> {
match self.adapter_io_mut()? {
ThrottledTemplateWriterAdapterMode::Characters(adapter) => adapter.flush(),
ThrottledTemplateWriterAdapterMode::Bytes {
adapter,
pending_bytes,
..
} => {
if !pending_bytes.is_empty() {
adapter.write_bytes(pending_bytes)?;
pending_bytes.clear();
}
adapter.flush()
}
}
}
pub(crate) fn close(&mut self) -> io::Result<()> {
match self.adapter_io_mut()? {
ThrottledTemplateWriterAdapterMode::Characters(adapter) => adapter.close(),
ThrottledTemplateWriterAdapterMode::Bytes {
adapter,
encoder,
pending_bytes,
} => {
pending_bytes.extend(Self::encode_utf16(encoder, &[], true));
if !pending_bytes.is_empty() {
adapter.write_bytes(pending_bytes)?;
pending_bytes.clear();
}
adapter.close()
}
}
}
fn flush_if_needed(&mut self) -> io::Result<()> {
if self.flushable {
self.flush()?;
self.flushable = false;
}
Ok(())
}
fn adapter_mut(
&mut self,
) -> Result<&mut ThrottledTemplateWriterAdapterMode, TemplateOutputException> {
if self.adapter.is_none() {
return Err(self.mode_error("The throttled processor output has not been initialized."));
}
Ok(self.adapter.as_mut().expect("checked above"))
}
fn adapter_io_mut(&mut self) -> io::Result<&mut ThrottledTemplateWriterAdapterMode> {
self.adapter
.as_mut()
.ok_or_else(|| io::Error::other("Throttled processor output has not been initialized"))
}
fn mode_error(&self, message: &str) -> TemplateOutputException {
TemplateOutputException::new(
Some(message.to_owned()),
Some(self.template_name.clone()),
-1,
-1,
io::Error::other(message.to_owned()),
)
}
fn encode_utf16(encoder: &mut Encoder, characters: &[u16], last: bool) -> Vec<u8> {
let mut source_offset = 0;
let mut output = Vec::with_capacity(characters.len().saturating_mul(4).saturating_add(32));
loop {
let mut buffer = [0_u8; 1024];
let (result, read, written, _) =
encoder.encode_from_utf16(&characters[source_offset..], &mut buffer, last);
output.extend_from_slice(&buffer[..written]);
source_offset += read;
if result == CoderResult::InputEmpty {
break;
}
}
output
}
}
impl TemplateWriter for ThrottledTemplateWriter {
fn write_utf16(&mut self, characters: &[u16]) -> io::Result<()> {
Self::write_utf16(self, characters)
}
fn flush(&mut self) -> io::Result<()> {
Self::flush(self)
}
fn close(&mut self) -> io::Result<()> {
Self::close(self)
}
}
impl IThrottledTemplateWriterControl for ThrottledTemplateWriter {
fn is_overflown(&mut self) -> io::Result<bool> {
self.flush_if_needed()?;
match self.adapter.as_ref() {
Some(ThrottledTemplateWriterAdapterMode::Characters(adapter)) => {
Ok(adapter.is_overflown())
}
Some(ThrottledTemplateWriterAdapterMode::Bytes { adapter, .. }) => {
Ok(adapter.is_overflown())
}
None => Err(io::Error::other(
"Cannot invoke \"org.thymeleaf.engine.ThrottledTemplateWriter$IThrottledTemplateWriterAdapter.isOverflown()\" because \"this.adapter\" is null",
)),
}
}
fn is_stopped(&mut self) -> io::Result<bool> {
self.flush_if_needed()?;
match self.adapter.as_ref() {
Some(ThrottledTemplateWriterAdapterMode::Characters(adapter)) => {
Ok(adapter.is_stopped())
}
Some(ThrottledTemplateWriterAdapterMode::Bytes { adapter, .. }) => {
Ok(adapter.is_stopped())
}
None => Err(io::Error::other(
"Cannot invoke \"org.thymeleaf.engine.ThrottledTemplateWriter$IThrottledTemplateWriterAdapter.isStopped()\" because \"this.adapter\" is null",
)),
}
}
fn get_written_count(&self) -> i32 {
match self.adapter.as_ref() {
Some(ThrottledTemplateWriterAdapterMode::Characters(adapter)) => {
adapter.get_written_count()
}
Some(ThrottledTemplateWriterAdapterMode::Bytes { adapter, .. }) => {
adapter.get_written_count()
}
None => panic!(
"Cannot invoke \"org.thymeleaf.engine.ThrottledTemplateWriter$IThrottledTemplateWriterAdapter.getWrittenCount()\" because \"this.adapter\" is null"
),
}
}
fn get_max_overflow_size(&self) -> i32 {
match self.adapter.as_ref() {
Some(ThrottledTemplateWriterAdapterMode::Characters(adapter)) => {
adapter.get_max_overflow_size()
}
Some(ThrottledTemplateWriterAdapterMode::Bytes { adapter, .. }) => {
adapter.get_max_overflow_size()
}
None => panic!(
"Cannot invoke \"org.thymeleaf.engine.ThrottledTemplateWriter$IThrottledTemplateWriterAdapter.getMaxOverflowSize()\" because \"this.adapter\" is null"
),
}
}
fn get_overflow_grow_count(&self) -> i32 {
match self.adapter.as_ref() {
Some(ThrottledTemplateWriterAdapterMode::Characters(adapter)) => {
adapter.get_overflow_grow_count()
}
Some(ThrottledTemplateWriterAdapterMode::Bytes { adapter, .. }) => {
adapter.get_overflow_grow_count()
}
None => panic!(
"Cannot invoke \"org.thymeleaf.engine.ThrottledTemplateWriter$IThrottledTemplateWriterAdapter.getOverflowGrowCount()\" because \"this.adapter\" is null"
),
}
}
}
#[cfg(test)]
mod tests {
use std::io::{self, Write};
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::{Arc, Mutex};
use super::super::i_throttled_template_writer_control::IThrottledTemplateWriterControl;
use super::super::template_flow_controller::TemplateFlowController;
use super::ThrottledTemplateWriter;
use crate::util::{Charset, TemplateWriter};
#[test]
fn character_throttling_state_machine_matches_java_golden() {
let mut uninitialized = ThrottledTemplateWriter::new(
"template".to_owned(),
Arc::new(Mutex::new(TemplateFlowController::new())),
);
assert_java_npe_message(
"uninitializedOverflown",
uninitialized
.is_overflown()
.expect_err("Java null adapter must fail")
.to_string(),
);
assert_java_npe_message(
"uninitializedStopped",
uninitialized
.is_stopped()
.expect_err("Java null adapter must fail")
.to_string(),
);
assert_java_npe_message(
"uninitializedWritten",
panic_message(|| uninitialized.get_written_count()),
);
assert_java_npe_message(
"uninitializedMaxOverflow",
panic_message(|| uninitialized.get_max_overflow_size()),
);
assert_java_npe_message(
"uninitializedGrowCount",
panic_message(|| uninitialized.get_overflow_grow_count()),
);
let controller = Arc::new(Mutex::new(TemplateFlowController::new()));
let output = Arc::new(Mutex::new(Vec::new()));
let mut writer =
ThrottledTemplateWriter::new("template".to_owned(), Arc::clone(&controller));
writer
.set_output_writer(Box::new(RecordingWriter(Arc::clone(&output))))
.expect("character output initialization");
assert_golden("initial", &state(&mut writer, &controller, &output));
writer.allow(2).expect("allow first chunk");
writer
.write_utf16(&utf16("abcd"))
.expect("write first chunk");
assert_golden("first", &state(&mut writer, &controller, &output));
writer.allow(1).expect("allow overflow prefix");
assert_golden("second", &state(&mut writer, &controller, &output));
writer.allow(i32::MAX).expect("allow unlimited remainder");
assert_golden("unlimited", &state(&mut writer, &controller, &output));
writer.allow(0).expect("stop output");
writer
.write_utf16(&utf16("ef"))
.expect("buffer stopped output");
assert_golden("zero", &state(&mut writer, &controller, &output));
let byte_controller = Arc::new(Mutex::new(TemplateFlowController::new()));
let byte_output = Arc::new(Mutex::new(Vec::new()));
let mut byte_writer =
ThrottledTemplateWriter::new("template".to_owned(), Arc::clone(&byte_controller));
byte_writer
.set_output_stream(
Box::new(RecordingByteStream(Arc::clone(&byte_output))),
&Charset::for_name("UTF-8").expect("UTF-8 charset"),
2,
)
.expect("byte output initialization");
byte_writer.allow(2).expect("allow UTF-8 first chunk");
byte_writer
.write_utf16(&utf16("éx"))
.expect("buffer UTF-8 characters");
assert_golden(
"bytesFirst",
&byte_state(&mut byte_writer, &byte_controller, &byte_output),
);
byte_writer.allow(1).expect("allow UTF-8 overflow");
assert_golden(
"bytesSecond",
&byte_state(&mut byte_writer, &byte_controller, &byte_output),
);
let mut char_first = ThrottledTemplateWriter::new(
"template".to_owned(),
Arc::new(Mutex::new(TemplateFlowController::new())),
);
char_first
.set_output_writer(Box::new(RecordingWriter(Arc::new(Mutex::new(Vec::new())))))
.expect("character output initialization");
let char_then_bytes = char_first
.set_output_stream(
Box::new(RecordingByteStream(Arc::new(Mutex::new(Vec::new())))),
&Charset::for_name("UTF-8").expect("UTF-8 charset"),
1,
)
.expect_err("Java locks writer mode after character initialization");
assert_golden(
"charThenBytes",
&format!("TemplateOutputException:{char_then_bytes}"),
);
let mut bytes_first = ThrottledTemplateWriter::new(
"template".to_owned(),
Arc::new(Mutex::new(TemplateFlowController::new())),
);
bytes_first
.set_output_stream(
Box::new(RecordingByteStream(Arc::new(Mutex::new(Vec::new())))),
&Charset::for_name("UTF-8").expect("UTF-8 charset"),
1,
)
.expect("byte output initialization");
let bytes_then_char = bytes_first
.set_output_writer(Box::new(RecordingWriter(Arc::new(Mutex::new(Vec::new())))))
.expect_err("Java locks writer mode after byte initialization");
assert_golden(
"bytesThenChar",
&format!("TemplateOutputException:{bytes_then_char}"),
);
let mut failing = ThrottledTemplateWriter::new(
"template".to_owned(),
Arc::new(Mutex::new(TemplateFlowController::new())),
);
failing
.set_output_writer(Box::new(FailOnSecondWriteWriter { writes: 0 }))
.expect("character output initialization");
failing.allow(2).expect("allow initial output");
failing
.write_utf16(&utf16("abc"))
.expect("first underlying write succeeds");
let overflow_io = failing
.allow(1)
.expect_err("overflow flush must wrap the sink failure");
assert_golden(
"overflowIo",
&format!("TemplateOutputException:{overflow_io}"),
);
let bulk_controller = Arc::new(Mutex::new(TemplateFlowController::new()));
let bulk_output = Arc::new(Mutex::new(Vec::new()));
let mut bulk =
ThrottledTemplateWriter::new("template".to_owned(), Arc::clone(&bulk_controller));
bulk.set_output_writer(Box::new(RecordingWriter(Arc::clone(&bulk_output))))
.expect("character output initialization");
bulk.allow(0).expect("stop bulk output");
bulk.write_utf16(&vec![u16::from(b'a'); 600])
.expect("buffer first bulk segment");
bulk.write_utf16(&vec![u16::from(b'b'); 200])
.expect("buffer second bulk segment");
assert_golden("bulkBuffered", &bulk_state(&mut bulk, &bulk_output));
bulk.allow(i32::MAX).expect("drain bulk output");
assert_golden("bulkDrained", &bulk_state(&mut bulk, &bulk_output));
let mut resource_failures = ThrottledTemplateWriter::new(
"template".to_owned(),
Arc::new(Mutex::new(TemplateFlowController::new())),
);
resource_failures
.set_output_writer(Box::new(FlushCloseFailingWriter))
.expect("character output initialization");
let flush_error = resource_failures
.flush()
.expect_err("flush errors must propagate without template wrapping");
assert_golden("flushIo", &format!("IOException:{flush_error}"));
let close_error = resource_failures
.close()
.expect_err("close errors must propagate without template wrapping");
assert_golden("closeIo", &format!("IOException:{close_error}"));
let overload_controller = Arc::new(Mutex::new(TemplateFlowController::new()));
let overload_output = Arc::new(Mutex::new(Vec::new()));
let mut overloads =
ThrottledTemplateWriter::new("template".to_owned(), Arc::clone(&overload_controller));
overloads
.set_output_writer(Box::new(RecordingWriter(Arc::clone(&overload_output))))
.expect("character output initialization");
overloads.allow(i32::MAX).expect("allow unbounded output");
overloads
.write_utf16(&utf16("x"))
.expect("single Java char equivalent");
overloads
.write_utf16(&utf16("abcdef")[1..4])
.expect("String offset/len equivalent");
overloads
.write_utf16(&utf16("qrs"))
.expect("char array equivalent");
overloads
.write_utf16(&utf16("qrs")[1..3])
.expect("char array offset/len equivalent");
let overload_text =
String::from_utf16(&overload_output.lock().expect("overload output lock"))
.expect("test output must be valid UTF-16");
assert_golden(
"overloads",
&format!("{overload_text},{}", overloads.get_written_count()),
);
}
struct RecordingWriter(Arc<Mutex<Vec<u16>>>);
impl TemplateWriter for RecordingWriter {
fn write_utf16(&mut self, characters: &[u16]) -> io::Result<()> {
self.0
.lock()
.expect("recording writer lock")
.extend_from_slice(characters);
Ok(())
}
}
struct FailOnSecondWriteWriter {
writes: usize,
}
impl TemplateWriter for FailOnSecondWriteWriter {
fn write_utf16(&mut self, _characters: &[u16]) -> io::Result<()> {
self.writes += 1;
if self.writes > 1 {
return Err(io::Error::other("overflow sink failure"));
}
Ok(())
}
}
struct FlushCloseFailingWriter;
impl TemplateWriter for FlushCloseFailingWriter {
fn write_utf16(&mut self, _characters: &[u16]) -> io::Result<()> {
Ok(())
}
fn flush(&mut self) -> io::Result<()> {
Err(io::Error::other("flush sink failure"))
}
fn close(&mut self) -> io::Result<()> {
Err(io::Error::other("close sink failure"))
}
}
struct RecordingByteStream(Arc<Mutex<Vec<u8>>>);
impl Write for RecordingByteStream {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
self.0
.lock()
.expect("recording byte stream lock")
.extend_from_slice(bytes);
Ok(bytes.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
fn state(
writer: &mut ThrottledTemplateWriter,
controller: &Arc<Mutex<TemplateFlowController>>,
output: &Arc<Mutex<Vec<u16>>>,
) -> String {
let content = String::from_utf16(&output.lock().expect("output lock"))
.expect("test output must be valid UTF-16");
let control: &mut dyn IThrottledTemplateWriterControl = writer;
let written = control.get_written_count();
let overflown = control.is_overflown().expect("is overflown");
let stopped = control.is_stopped().expect("is stopped");
let controller_stopped = controller
.lock()
.expect("flow controller lock")
.stop_processing;
format!(
"{content},{written},{overflown},{stopped},{controller_stopped},{},{}",
control.get_max_overflow_size(),
control.get_overflow_grow_count()
)
}
fn assert_golden(key: &str, actual: &str) {
let expected = include_str!("../../tests/fixtures/throttled_template_writer_golden.txt")
.lines()
.find_map(|line| line.strip_prefix(&format!("{key}=")))
.expect("Java Golden record");
assert_eq!(actual, expected, "Java Golden key {key}");
}
fn assert_java_npe_message(key: &str, actual: String) {
let expected = include_str!("../../tests/fixtures/throttled_template_writer_golden.txt")
.lines()
.find_map(|line| line.strip_prefix(&format!("{key}=")))
.and_then(|record| record.split_once(':'))
.map(|(_, message)| message)
.expect("Java Golden NPE record");
assert_eq!(actual, expected, "Java Golden key {key}");
}
fn panic_message<T>(operation: impl FnOnce() -> T) -> String {
let payload = match catch_unwind(AssertUnwindSafe(operation)) {
Ok(_) => panic!("Java null adapter getter must panic"),
Err(payload) => payload,
};
match payload.downcast::<String>() {
Ok(message) => *message,
Err(payload) => match payload.downcast::<&'static str>() {
Ok(message) => (*message).to_owned(),
Err(_) => panic!("unexpected panic payload type"),
},
}
}
fn byte_state(
writer: &mut ThrottledTemplateWriter,
controller: &Arc<Mutex<TemplateFlowController>>,
output: &Arc<Mutex<Vec<u8>>>,
) -> String {
let content = output
.lock()
.expect("byte output lock")
.iter()
.map(|byte| format!("{byte:02x}"))
.collect::<String>();
let control: &mut dyn IThrottledTemplateWriterControl = writer;
let written = control.get_written_count();
let overflown = control.is_overflown().expect("is overflown");
let stopped = control.is_stopped().expect("is stopped");
let controller_stopped = controller
.lock()
.expect("flow controller lock")
.stop_processing;
format!(
"{content},{written},{overflown},{stopped},{controller_stopped},{},{}",
control.get_max_overflow_size(),
control.get_overflow_grow_count()
)
}
fn bulk_state(writer: &mut ThrottledTemplateWriter, output: &Arc<Mutex<Vec<u16>>>) -> String {
let output_len = output.lock().expect("bulk output lock").len();
let control: &mut dyn IThrottledTemplateWriterControl = writer;
let written = control.get_written_count();
let overflown = control.is_overflown().expect("is overflown");
let stopped = control.is_stopped().expect("is stopped");
format!(
"{output_len},{written},{overflown},{stopped},{},{}",
control.get_max_overflow_size(),
control.get_overflow_grow_count()
)
}
fn utf16(value: &str) -> Vec<u16> {
value.encode_utf16().collect()
}
}