use std::collections::HashSet;
use std::fmt::{Debug, Formatter};
use std::hash::Hash;
use std::sync::Arc;
use thiserror::Error;
use crate::expression::ObjectArrayValue;
use super::{Validate, ValidateError};
type ArrayPredicate<T> = dyn Fn(&T) -> bool + Send + Sync;
pub trait ArrayElementValue {
fn class_name(&self) -> &str;
fn is_instance_of(&self, component_class_name: &str) -> bool {
component_class_name == "java.lang.Object" || component_class_name == self.class_name()
}
}
impl ArrayElementValue for String {
fn class_name(&self) -> &str {
"java.lang.String"
}
}
impl ArrayElementValue for i32 {
fn class_name(&self) -> &str {
"java.lang.Integer"
}
}
impl ArrayElementValue for i64 {
fn class_name(&self) -> &str {
"java.lang.Long"
}
}
impl ArrayElementValue for f64 {
fn class_name(&self) -> &str {
"java.lang.Double"
}
}
impl ArrayElementValue for f32 {
fn class_name(&self) -> &str {
"java.lang.Float"
}
}
impl ArrayElementValue for bool {
fn class_name(&self) -> &str {
"java.lang.Boolean"
}
}
#[derive(Clone)]
pub struct ArrayTypeValue<T> {
component_class_name: String,
component_predicate: Arc<ArrayPredicate<T>>,
}
impl<T> ArrayTypeValue<T> {
#[must_use]
pub fn typed(
component_class_name: impl Into<String>,
component_predicate: impl Fn(&T) -> bool + Send + Sync + 'static,
) -> Self {
Self {
component_class_name: component_class_name.into(),
component_predicate: Arc::new(component_predicate),
}
}
#[must_use]
pub fn object() -> Self {
Self::typed("java.lang.Object", |_| true)
}
#[must_use]
pub fn component_class_name(&self) -> &str {
&self.component_class_name
}
}
impl<T> Debug for ArrayTypeValue<T> {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("ArrayTypeValue")
.field("component_class_name", &self.component_class_name)
.finish_non_exhaustive()
}
}
pub enum ArrayTarget<'a, T> {
Reference(&'a ObjectArrayValue<T>),
PrimitiveArray {
class_name: &'a str,
component_class_name: &'a str,
},
Iterable(&'a [Option<T>]),
Other {
class_name: &'a str,
},
}
#[derive(Debug)]
pub enum ArrayValue<'a, T> {
Borrowed(&'a ObjectArrayValue<T>),
Owned(ObjectArrayValue<T>),
}
impl<'a, T> ArrayValue<'a, T> {
#[must_use]
pub fn as_array(&self) -> &ObjectArrayValue<T> {
match self {
Self::Borrowed(array) => array,
Self::Owned(array) => array,
}
}
#[must_use]
pub fn is_same_reference(&self, target: &ObjectArrayValue<T>) -> bool {
matches!(self, Self::Borrowed(array) if std::ptr::eq(*array, target))
}
#[must_use]
pub fn into_owned(self) -> ObjectArrayValue<T>
where
T: Clone,
{
match self {
Self::Borrowed(array) => array.clone(),
Self::Owned(array) => array,
}
}
}
#[derive(Debug, Error, Clone, Eq, PartialEq)]
pub enum ArrayUtilsError {
#[error(transparent)]
Validation(#[from] ValidateError),
#[error("{message}")]
CannotConvert {
message: String,
},
#[error("class {class_name} cannot be cast to class [Ljava.lang.Object;")]
ClassCast {
class_name: String,
},
#[error("element cannot be stored in array with component class \"{component_class_name}\"")]
ArrayStore {
component_class_name: String,
},
#[error("{length}")]
NegativeArraySize {
length: i32,
},
#[error("")]
NullPointer,
#[error("{message}")]
ArrayIndexOutOfBounds {
message: String,
},
#[error("Cannot copy array range with indexes {from} and {to}")]
InvalidRange {
from: i32,
to: i32,
},
}
impl ArrayUtilsError {
#[must_use]
pub const fn class_name(&self) -> &'static str {
match self {
Self::Validation(error) => error.class_name(),
Self::CannotConvert { .. } | Self::InvalidRange { .. } => {
"java.lang.IllegalArgumentException"
}
Self::ClassCast { .. } => "java.lang.ClassCastException",
Self::ArrayStore { .. } => "java.lang.ArrayStoreException",
Self::NegativeArraySize { .. } => "java.lang.NegativeArraySizeException",
Self::NullPointer => "java.lang.NullPointerException",
Self::ArrayIndexOutOfBounds { .. } => "java.lang.ArrayIndexOutOfBoundsException",
}
}
}
pub struct ArrayUtils;
impl ArrayUtils {
pub fn to_array<'a, T>(
target: Option<ArrayTarget<'a, T>>,
) -> Result<ArrayValue<'a, T>, ArrayUtilsError>
where
T: Clone + ArrayElementValue + 'static,
{
Self::convert(None, target)
}
pub fn to_string_array<'a, T>(
target: Option<ArrayTarget<'a, T>>,
) -> Result<ArrayValue<'a, T>, ArrayUtilsError>
where
T: Clone + ArrayElementValue + 'static,
{
Self::convert(Some("java.lang.String"), target)
}
pub fn to_integer_array<'a, T>(
target: Option<ArrayTarget<'a, T>>,
) -> Result<ArrayValue<'a, T>, ArrayUtilsError>
where
T: Clone + ArrayElementValue + 'static,
{
Self::convert(Some("java.lang.Integer"), target)
}
pub fn to_long_array<'a, T>(
target: Option<ArrayTarget<'a, T>>,
) -> Result<ArrayValue<'a, T>, ArrayUtilsError>
where
T: Clone + ArrayElementValue + 'static,
{
Self::convert(Some("java.lang.Long"), target)
}
pub fn to_double_array<'a, T>(
target: Option<ArrayTarget<'a, T>>,
) -> Result<ArrayValue<'a, T>, ArrayUtilsError>
where
T: Clone + ArrayElementValue + 'static,
{
Self::convert(Some("java.lang.Double"), target)
}
pub fn to_float_array<'a, T>(
target: Option<ArrayTarget<'a, T>>,
) -> Result<ArrayValue<'a, T>, ArrayUtilsError>
where
T: Clone + ArrayElementValue + 'static,
{
Self::convert(Some("java.lang.Float"), target)
}
pub fn to_boolean_array<'a, T>(
target: Option<ArrayTarget<'a, T>>,
) -> Result<ArrayValue<'a, T>, ArrayUtilsError>
where
T: Clone + ArrayElementValue + 'static,
{
Self::convert(Some("java.lang.Boolean"), target)
}
pub fn length<T>(target: Option<&[Option<T>]>) -> Result<i32, ArrayUtilsError> {
Validate::not_null(target, Some("Cannot get array length of null"))?;
Ok(i32::try_from(target.expect("validated target").len()).unwrap_or(i32::MAX))
}
#[must_use]
pub fn is_empty<T>(target: Option<&[Option<T>]>) -> bool {
target.is_none_or(<[Option<T>]>::is_empty)
}
pub fn contains<T>(
target: Option<&[Option<T>]>,
element: &Option<T>,
) -> Result<bool, ArrayUtilsError>
where
T: PartialEq,
{
Validate::not_null(
target,
Some("Cannot execute array contains: target is null"),
)?;
Ok(target
.expect("validated target")
.iter()
.any(|target_element| element == target_element))
}
pub fn contains_all_array<T>(
target: Option<&[Option<T>]>,
elements: Option<&[Option<T>]>,
) -> Result<bool, ArrayUtilsError>
where
T: Clone + Eq + Hash,
{
Validate::not_null(
target,
Some("Cannot execute array containsAll: target is null"),
)?;
Validate::not_null(
elements,
Some("Cannot execute array containsAll: elements is null"),
)?;
Self::contains_all_collection(target, elements)
}
pub fn contains_all_collection<T>(
target: Option<&[Option<T>]>,
elements: Option<&[Option<T>]>,
) -> Result<bool, ArrayUtilsError>
where
T: Clone + Eq + Hash,
{
Validate::not_null(
target,
Some("Cannot execute array contains: target is null"),
)?;
Validate::not_null(
elements,
Some("Cannot execute array containsAll: elements is null"),
)?;
let mut remaining: HashSet<Option<T>> = elements
.expect("validated elements")
.iter()
.cloned()
.collect();
for target_element in target.expect("validated target") {
remaining.remove(target_element);
}
Ok(remaining.is_empty())
}
pub fn copy_of_with_type<T>(
original: Option<&ObjectArrayValue<T>>,
new_length: i32,
new_type: Option<&ArrayTypeValue<T>>,
) -> Result<ObjectArrayValue<T>, ArrayUtilsError>
where
T: Clone,
{
let new_type = new_type.ok_or(ArrayUtilsError::NullPointer)?;
if new_length < 0 {
return Err(ArrayUtilsError::NegativeArraySize { length: new_length });
}
let mut elements = vec![None; new_length as usize];
let original = original.ok_or(ArrayUtilsError::NullPointer)?;
let copy_length = original.len().min(elements.len());
for (index, element) in original.as_slice()[..copy_length].iter().enumerate() {
if element
.as_ref()
.is_some_and(|value| !(new_type.component_predicate)(value))
{
return Err(ArrayUtilsError::ArrayStore {
component_class_name: new_type.component_class_name.clone(),
});
}
elements[index] = element.clone();
}
Ok(ObjectArrayValue::from_parts(
new_type.component_class_name.clone(),
elements,
Arc::clone(&new_type.component_predicate),
))
}
pub fn copy_of<T>(
original: Option<&ObjectArrayValue<T>>,
new_length: i32,
) -> Result<ObjectArrayValue<T>, ArrayUtilsError>
where
T: Clone,
{
let original = original.ok_or(ArrayUtilsError::NullPointer)?;
if new_length < 0 {
return Err(ArrayUtilsError::NegativeArraySize { length: new_length });
}
let mut elements = vec![None; new_length as usize];
let copy_length = original.len().min(elements.len());
elements[..copy_length].clone_from_slice(&original.as_slice()[..copy_length]);
Ok(ObjectArrayValue::from_parts(
original.component_class_name().to_owned(),
elements,
original.component_predicate(),
))
}
pub fn copy_of_chars(
original: Option<&[u16]>,
new_length: i32,
) -> Result<Vec<u16>, ArrayUtilsError> {
if new_length < 0 {
return Err(ArrayUtilsError::NegativeArraySize { length: new_length });
}
let mut copy = vec![0; new_length as usize];
let original = original.ok_or(ArrayUtilsError::NullPointer)?;
let copy_length = original.len().min(copy.len());
copy[..copy_length].copy_from_slice(&original[..copy_length]);
Ok(copy)
}
pub fn copy_of_range(
original: Option<&[u16]>,
from: i32,
to: i32,
) -> Result<Vec<u16>, ArrayUtilsError> {
let new_length = to.wrapping_sub(from);
if new_length < 0 {
return Err(ArrayUtilsError::InvalidRange { from, to });
}
let mut copy = vec![0; new_length as usize];
let original = original.ok_or(ArrayUtilsError::NullPointer)?;
let available = i64::try_from(original.len()).unwrap_or(i64::MAX) - i64::from(from);
let copy_length = available.min(i64::from(new_length));
if from < 0 {
return Err(ArrayUtilsError::ArrayIndexOutOfBounds {
message: format!(
"arraycopy: source index {from} out of bounds for char[{}]",
original.len()
),
});
}
if copy_length < 0 {
return Err(ArrayUtilsError::ArrayIndexOutOfBounds {
message: format!("arraycopy: length {copy_length} is negative"),
});
}
let from = from as usize;
let copy_length = copy_length as usize;
if copy_length > 0 {
copy[..copy_length].copy_from_slice(&original[from..from + copy_length]);
}
Ok(copy)
}
fn convert<'a, T>(
component_class_name: Option<&'static str>,
target: Option<ArrayTarget<'a, T>>,
) -> Result<ArrayValue<'a, T>, ArrayUtilsError>
where
T: Clone + ArrayElementValue + 'static,
{
let target = target.ok_or_else(|| ArrayUtilsError::CannotConvert {
message: "Cannot convert null to array".to_owned(),
})?;
match target {
ArrayTarget::Reference(array) => {
if component_class_name.is_none()
|| component_class_name == Some(array.component_class_name())
{
return Ok(ArrayValue::Borrowed(array));
}
Err(Self::incompatible_array(
array.component_class_name(),
component_class_name,
))
}
ArrayTarget::PrimitiveArray {
class_name,
component_class_name: primitive_component,
} => {
if component_class_name.is_none() {
Err(ArrayUtilsError::ClassCast {
class_name: class_name.to_owned(),
})
} else {
Err(Self::incompatible_array(
primitive_component,
component_class_name,
))
}
}
ArrayTarget::Iterable(elements) => {
let computed = component_class_name.map_or_else(
|| {
let mut computed: Option<&str> = None;
for element in elements.iter().flatten() {
computed = match computed {
None => Some(element.class_name()),
Some("java.lang.Object") => Some("java.lang.Object"),
Some(current) if current == element.class_name() => Some(current),
Some(_) => Some("java.lang.Object"),
};
}
computed.unwrap_or("java.lang.Object").to_owned()
},
str::to_owned,
);
let predicate_component = computed.clone();
let predicate: Arc<ArrayPredicate<T>> =
Arc::new(move |value| value.is_instance_of(&predicate_component));
if let Some(value) = elements.iter().flatten().find(|value| !predicate(value)) {
let _ = value;
return Err(ArrayUtilsError::ArrayStore {
component_class_name: computed,
});
}
Ok(ArrayValue::Owned(ObjectArrayValue::from_parts(
computed,
elements.to_vec(),
predicate,
)))
}
ArrayTarget::Other { class_name } => Err(ArrayUtilsError::CannotConvert {
message: format!(
"Cannot convert object of class \"{class_name}\" to an array{}",
component_class_name.map_or("", |_| " of Class")
),
}),
}
}
fn incompatible_array(
component_class_name: &str,
requested_component_class_name: Option<&str>,
) -> ArrayUtilsError {
ArrayUtilsError::CannotConvert {
message: format!(
"Cannot convert object of class \"{component_class_name}[]\" to an array{}",
requested_component_class_name.map_or("", |_| " of Class")
),
}
}
}
#[cfg(test)]
mod tests {
use super::{ArrayElementValue, ArrayTarget, ArrayTypeValue, ArrayUtils, ArrayUtilsError};
use crate::expression::ObjectArrayValue;
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
enum Value {
Text(String),
Number(i32),
}
impl ArrayElementValue for Value {
fn class_name(&self) -> &str {
match self {
Self::Text(_) => "java.lang.String",
Self::Number(_) => "java.lang.Integer",
}
}
}
#[test]
fn conversion_preserves_identity_and_infers_exact_component_class() {
let source = ObjectArrayValue::typed(
"java.lang.String",
vec![Some(Value::Text("one".to_owned()))],
|value| matches!(value, Value::Text(_)),
)
.expect("source");
let result = ArrayUtils::to_array(Some(ArrayTarget::Reference(&source))).expect("array");
assert!(result.is_same_reference(&source));
let values = [
Some(Value::Text("one".to_owned())),
None,
Some(Value::Text("two".to_owned())),
];
let result = ArrayUtils::to_array(Some(ArrayTarget::Iterable(&values))).expect("iterable");
assert_eq!(result.as_array().component_class_name(), "java.lang.String");
assert_eq!(result.as_array().as_slice(), &values);
}
#[test]
fn typed_conversion_and_primitive_errors_keep_java_categories() {
let mixed = [
Some(Value::Text("one".to_owned())),
Some(Value::Number(2)),
Some(Value::Text("two".to_owned())),
];
let inferred =
ArrayUtils::to_array(Some(ArrayTarget::Iterable(&mixed))).expect("object array");
assert_eq!(
inferred.as_array().component_class_name(),
"java.lang.Object"
);
let error = ArrayUtils::to_string_array(Some(ArrayTarget::Iterable(&mixed)))
.expect_err("array store");
assert_eq!(error.class_name(), "java.lang.ArrayStoreException");
let error = ArrayUtils::to_array::<Value>(Some(ArrayTarget::PrimitiveArray {
class_name: "[I",
component_class_name: "int",
}))
.expect_err("class cast");
assert_eq!(error.class_name(), "java.lang.ClassCastException");
}
#[test]
fn query_and_copy_contracts_are_preserved() {
let values = [Some("one".to_owned()), None, Some("two".to_owned())];
assert_eq!(ArrayUtils::length(Some(&values)).expect("length"), 3);
assert!(ArrayUtils::contains(Some(&values), &None).expect("contains"));
assert!(
ArrayUtils::contains_all_array(Some(&values), Some(&[Some("one".to_owned()), None]))
.expect("all")
);
let source =
ObjectArrayValue::typed("java.lang.String", values.to_vec(), |_| true).expect("source");
let copied = ArrayUtils::copy_of(Some(&source), 5).expect("copy");
assert_eq!(copied.len(), 5);
assert_eq!(copied.component_class_name(), "java.lang.String");
let integer_type = ArrayTypeValue::typed("java.lang.Integer", |_: &String| false);
assert_eq!(
ArrayUtils::copy_of_with_type(Some(&source), 1, Some(&integer_type))
.expect_err("store")
.class_name(),
"java.lang.ArrayStoreException"
);
}
#[test]
fn char_copy_order_and_range_failures_match_java() {
assert_eq!(
ArrayUtils::copy_of_chars(Some(&[97, 0, 122]), 5).expect("copy"),
vec![97, 0, 122, 0, 0]
);
assert_eq!(
ArrayUtils::copy_of_range(Some(&[97, 98, 99, 100]), 2, 6).expect("range"),
vec![99, 100, 0, 0]
);
assert_eq!(
ArrayUtils::copy_of_range(Some(&[1]), 3, 1).expect_err("range"),
ArrayUtilsError::InvalidRange { from: 3, to: 1 }
);
assert_eq!(
ArrayUtils::copy_of_chars(None, -1).expect_err("negative"),
ArrayUtilsError::NegativeArraySize { length: -1 }
);
}
#[test]
fn runtime_adapters_cover_builtin_classes_types_and_owned_results() {
assert_eq!(String::new().class_name(), "java.lang.String");
assert_eq!(0_i32.class_name(), "java.lang.Integer");
assert_eq!(0_i64.class_name(), "java.lang.Long");
assert_eq!(0_f64.class_name(), "java.lang.Double");
assert_eq!(0_f32.class_name(), "java.lang.Float");
assert_eq!(false.class_name(), "java.lang.Boolean");
assert!("text".to_owned().is_instance_of("java.lang.Object"));
assert!(!0_i32.is_instance_of("java.lang.String"));
let array_type = ArrayTypeValue::<String>::object();
assert_eq!(array_type.component_class_name(), "java.lang.Object");
assert_eq!(
format!("{array_type:?}"),
"ArrayTypeValue { component_class_name: \"java.lang.Object\", .. }"
);
let source = ObjectArrayValue::object(vec![Some("one".to_owned())]);
let borrowed = ArrayUtils::to_array(Some(ArrayTarget::Reference(&source)))
.expect("borrowed")
.into_owned();
assert_eq!(borrowed.as_slice(), source.as_slice());
let values = [Some("one".to_owned())];
let owned = ArrayUtils::to_array(Some(ArrayTarget::Iterable(&values)))
.expect("owned")
.into_owned();
assert_eq!(owned.as_slice(), &values);
assert_eq!(
ArrayUtils::copy_of_with_type(Some(&source), -1, Some(&array_type))
.expect_err("negative"),
ArrayUtilsError::NegativeArraySize { length: -1 }
);
assert_eq!(
ArrayUtils::copy_of_with_type(None, 1, Some(&array_type)).expect_err("null original"),
ArrayUtilsError::NullPointer
);
}
}