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 AttributeNameKind {
Html,
Xml,
Text,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum AttributeNameError {
InvalidAttributeName,
EmptyCompleteAttributeNames,
NullFirstCompleteAttributeName,
}
impl AttributeNameError {
#[must_use]
pub const fn class_name(&self) -> &'static str {
match self {
Self::InvalidAttributeName => "java.lang.IllegalArgumentException",
Self::EmptyCompleteAttributeNames => "java.lang.ArrayIndexOutOfBoundsException",
Self::NullFirstCompleteAttributeName => "java.lang.NullPointerException",
}
}
}
impl Display for AttributeNameError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidAttributeName => {
formatter.write_str("Attribute name cannot be null or empty")
}
Self::EmptyCompleteAttributeNames => {
formatter.write_str("Index 0 out of bounds for length 0")
}
Self::NullFirstCompleteAttributeName => {
formatter.write_str("Cannot invoke \"String.equals(Object)\" because the first complete attribute name is null")
}
}
}
}
impl Error for AttributeNameError {}
pub struct AttributeName {
kind: AttributeNameKind,
prefix: Option<Utf16String>,
attribute_name: Utf16String,
complete_attribute_names: Arc<RwLock<Vec<Option<Utf16String>>>>,
hash_code: i32,
}
impl AttributeName {
pub(super) fn new(
kind: AttributeNameKind,
prefix: Option<Utf16String>,
attribute_name: Option<Utf16String>,
complete_attribute_names: Vec<Option<Utf16String>>,
) -> Result<Self, AttributeNameError> {
let attribute_name = attribute_name.ok_or(AttributeNameError::InvalidAttributeName)?;
if attribute_name.is_empty()
|| attribute_name
.as_utf16()
.iter()
.all(|character| *character <= 0x20)
{
return Err(AttributeNameError::InvalidAttributeName);
}
let hash_code = arrays_hash_code(&complete_attribute_names);
Ok(Self {
kind,
prefix,
attribute_name,
complete_attribute_names: Arc::new(RwLock::new(complete_attribute_names)),
hash_code,
})
}
#[must_use]
pub const fn kind(&self) -> AttributeNameKind {
self.kind
}
#[must_use]
pub const fn get_attribute_name(&self) -> &Utf16String {
&self.attribute_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_attribute_names(&self) -> Arc<RwLock<Vec<Option<Utf16String>>>> {
Arc::clone(&self.complete_attribute_names)
}
#[must_use]
pub const fn hash_code(&self) -> i32 {
self.hash_code
}
pub fn equals_java(&self, other: &Self) -> Result<bool, AttributeNameError> {
if std::ptr::eq(self, other) {
return Ok(true);
}
if self.kind != other.kind || self.hash_code != other.hash_code {
return Ok(false);
}
let own_names = read_recovering_poison(&self.complete_attribute_names);
let other_names = read_recovering_poison(&other.complete_attribute_names);
let own_first = own_names
.first()
.ok_or(AttributeNameError::EmptyCompleteAttributeNames)?
.as_ref()
.ok_or(AttributeNameError::NullFirstCompleteAttributeName)?;
let other_first = other_names
.first()
.ok_or(AttributeNameError::EmptyCompleteAttributeNames)?;
Ok(other_first.as_ref().is_some_and(|value| value == own_first))
}
pub fn to_utf16_string(&self) -> Result<Utf16String, AttributeNameError> {
let names = read_recovering_poison(&self.complete_attribute_names);
let Some(first) = names.first() else {
return Err(AttributeNameError::EmptyCompleteAttributeNames);
};
let mut result = vec![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))
}
}
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)
}