use std::error::Error;
use std::fmt::{Display, Formatter};
use std::sync::{Arc, RwLock, RwLockReadGuard};
use crate::util::{HashCodeValue, Utf16String};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ElementNameKind {
Html,
Xml,
Text,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ElementNameError {
InvalidElementName,
EmptyCompleteElementNames,
}
impl ElementNameError {
#[must_use]
pub const fn class_name(&self) -> &'static str {
match self {
Self::InvalidElementName => "java.lang.IllegalArgumentException",
Self::EmptyCompleteElementNames => "java.lang.ArrayIndexOutOfBoundsException",
}
}
}
impl Display for ElementNameError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidElementName => formatter.write_str("Element name cannot be null"),
Self::EmptyCompleteElementNames => {
formatter.write_str("Index 0 out of bounds for length 0")
}
}
}
}
impl Error for ElementNameError {}
pub struct ElementName {
kind: ElementNameKind,
prefix: Option<Utf16String>,
element_name: Utf16String,
complete_element_names: Arc<RwLock<Vec<Option<Utf16String>>>>,
hash_code: i32,
}
impl ElementName {
pub(super) fn new(
kind: ElementNameKind,
prefix: Option<Utf16String>,
element_name: Option<Utf16String>,
complete_element_names: Vec<Option<Utf16String>>,
) -> Result<Self, ElementNameError> {
let element_name = element_name.ok_or(ElementNameError::InvalidElementName)?;
if !element_name.is_empty()
&& element_name
.as_utf16()
.iter()
.all(|character| *character <= 0x20)
{
return Err(ElementNameError::InvalidElementName);
}
let hash_code = arrays_hash_code(&complete_element_names);
Ok(Self {
kind,
prefix,
element_name,
complete_element_names: Arc::new(RwLock::new(complete_element_names)),
hash_code,
})
}
#[must_use]
pub const fn kind(&self) -> ElementNameKind {
self.kind
}
#[must_use]
pub const fn get_element_name(&self) -> &Utf16String {
&self.element_name
}
#[must_use]
pub const fn is_prefixed(&self) -> bool {
self.prefix.is_some()
}
#[must_use]
pub const fn get_prefix(&self) -> Option<&Utf16String> {
self.prefix.as_ref()
}
#[must_use]
pub fn get_complete_element_names(&self) -> Arc<RwLock<Vec<Option<Utf16String>>>> {
Arc::clone(&self.complete_element_names)
}
#[must_use]
pub const fn hash_code(&self) -> i32 {
self.hash_code
}
pub fn to_utf16_string(&self) -> Result<Utf16String, ElementNameError> {
let names = read_recovering_poison(&self.complete_element_names);
let Some(first) = names.first() else {
return Err(ElementNameError::EmptyCompleteElementNames);
};
let mut result = Vec::new();
result.push(u16::from(b'{'));
append_nullable(&mut result, first.as_ref());
for name in names.iter().skip(1) {
result.push(u16::from(b','));
append_nullable(&mut result, name.as_ref());
}
result.push(u16::from(b'}'));
Ok(Utf16String::from_utf16(result))
}
}
impl PartialEq for ElementName {
fn eq(&self, other: &Self) -> bool {
if std::ptr::eq(self, other) {
return true;
}
if self.hash_code != other.hash_code {
return false;
}
let own_names = read_recovering_poison(&self.complete_element_names);
let other_names = read_recovering_poison(&other.complete_element_names);
*own_names == *other_names
}
}
impl Eq for ElementName {}
fn arrays_hash_code(names: &[Option<Utf16String>]) -> i32 {
names.iter().fold(1_i32, |result, name| {
result
.wrapping_mul(31)
.wrapping_add(name.as_ref().map_or(0, HashCodeValue::hash_code))
})
}
fn append_nullable(target: &mut Vec<u16>, value: Option<&Utf16String>) {
match value {
Some(value) => target.extend_from_slice(value.as_utf16()),
None => target.extend("null".encode_utf16()),
}
}
fn read_recovering_poison<T>(lock: &RwLock<T>) -> RwLockReadGuard<'_, T> {
lock.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}