use std::collections::HashMap;
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::sync::{Mutex, MutexGuard};
use crate::util::Utf16String;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum IdentifierSequencesError {
NullId,
MissingPrevious(Utf16String),
}
impl IdentifierSequencesError {
#[must_use]
pub const fn class_name(&self) -> &'static str {
match self {
Self::NullId => "java.lang.IllegalArgumentException",
Self::MissingPrevious(_) => "org.thymeleaf.exceptions.TemplateProcessingException",
}
}
}
impl Display for IdentifierSequencesError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::NullId => formatter.write_str("ID cannot be null"),
Self::MissingPrevious(id) => write!(
formatter,
"Cannot obtain previous ID count for ID \"{}\"",
id.to_string_lossy()
),
}
}
}
impl Error for IdentifierSequencesError {}
pub struct IdentifierSequences {
id_counts: Mutex<HashMap<Utf16String, i32>>,
}
impl IdentifierSequences {
#[must_use]
pub fn new() -> Self {
Self {
id_counts: Mutex::new(HashMap::with_capacity(1)),
}
}
pub fn get_and_increment_id_seq(
&self,
id: Option<&Utf16String>,
) -> Result<i32, IdentifierSequencesError> {
let id = id.ok_or(IdentifierSequencesError::NullId)?;
let mut id_counts = lock_recovering_poison(&self.id_counts);
let count = id_counts.get(id).copied().unwrap_or(1);
id_counts.insert(id.clone(), count.wrapping_add(1));
Ok(count)
}
pub fn get_next_id_seq(
&self,
id: Option<&Utf16String>,
) -> Result<i32, IdentifierSequencesError> {
let id = id.ok_or(IdentifierSequencesError::NullId)?;
Ok(lock_recovering_poison(&self.id_counts)
.get(id)
.copied()
.unwrap_or(1))
}
pub fn get_previous_id_seq(
&self,
id: Option<&Utf16String>,
) -> Result<i32, IdentifierSequencesError> {
let id = id.ok_or(IdentifierSequencesError::NullId)?;
lock_recovering_poison(&self.id_counts)
.get(id)
.copied()
.map(|count| count.wrapping_sub(1))
.ok_or_else(|| IdentifierSequencesError::MissingPrevious(id.clone()))
}
}
impl Default for IdentifierSequences {
fn default() -> Self {
Self::new()
}
}
fn lock_recovering_poison<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
mutex
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
#[cfg(test)]
mod tests {
use super::{IdentifierSequences, IdentifierSequencesError, lock_recovering_poison};
use crate::util::Utf16String;
#[test]
fn preserves_java_int_wrap_and_exact_error_categories() {
let sequences = IdentifierSequences::new();
let maximum = Utf16String::from_rust_str("max");
let missing = Utf16String::from_rust_str("missing");
lock_recovering_poison(&sequences.id_counts).insert(maximum.clone(), i32::MAX);
assert_eq!(
sequences.get_and_increment_id_seq(Some(&maximum)),
Ok(i32::MAX)
);
assert_eq!(sequences.get_next_id_seq(Some(&maximum)), Ok(i32::MIN));
assert_eq!(sequences.get_previous_id_seq(Some(&maximum)), Ok(i32::MAX));
assert_eq!(
sequences.get_previous_id_seq(Some(&missing)),
Err(IdentifierSequencesError::MissingPrevious(missing))
);
assert_eq!(
sequences.get_next_id_seq(None),
Err(IdentifierSequencesError::NullId)
);
}
}