use std::collections::{BTreeSet, HashSet};
use std::hash::{BuildHasher, Hash};
use std::ptr;
use indexmap::IndexSet;
use thiserror::Error;
use super::{Validate, ValidateError};
pub trait SetView<T> {
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn contains(&self, element: &T) -> bool;
fn iter(&self) -> Box<dyn Iterator<Item = &T> + '_>;
}
impl<T, S> SetView<T> for HashSet<T, S>
where
T: Eq + Hash,
S: BuildHasher,
{
fn len(&self) -> usize {
HashSet::len(self)
}
fn contains(&self, element: &T) -> bool {
HashSet::contains(self, element)
}
fn iter(&self) -> Box<dyn Iterator<Item = &T> + '_> {
Box::new(HashSet::iter(self))
}
}
impl<T, S> SetView<T> for IndexSet<T, S>
where
T: Eq + Hash,
S: BuildHasher,
{
fn len(&self) -> usize {
IndexSet::len(self)
}
fn contains(&self, element: &T) -> bool {
IndexSet::contains(self, element)
}
fn iter(&self) -> Box<dyn Iterator<Item = &T> + '_> {
Box::new(IndexSet::iter(self))
}
}
impl<T> SetView<T> for BTreeSet<T>
where
T: Ord,
{
fn len(&self) -> usize {
BTreeSet::len(self)
}
fn contains(&self, element: &T) -> bool {
BTreeSet::contains(self, element)
}
fn iter(&self) -> Box<dyn Iterator<Item = &T> + '_> {
Box::new(BTreeSet::iter(self))
}
}
pub enum SetTarget<'a, T> {
Set(&'a dyn SetView<T>),
Array(&'a [T]),
Iterable(Box<dyn Iterator<Item = T> + 'a>),
PrimitiveArray(&'a str),
Unsupported(&'a str),
}
enum SetStorage<'a, T> {
Borrowed(&'a dyn SetView<T>),
Owned(IndexSet<T>),
}
pub struct SetValue<'a, T> {
storage: SetStorage<'a, T>,
}
impl<'a, T> SetValue<'a, T> {
fn borrowed(target: &'a dyn SetView<T>) -> Self {
Self {
storage: SetStorage::Borrowed(target),
}
}
fn owned(target: IndexSet<T>) -> Self {
Self {
storage: SetStorage::Owned(target),
}
}
#[must_use]
pub fn len(&self) -> usize {
match &self.storage {
SetStorage::Borrowed(target) => target.len(),
SetStorage::Owned(target) => target.len(),
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub fn contains(&self, element: &T) -> bool
where
T: Eq + Hash,
{
match &self.storage {
SetStorage::Borrowed(target) => target.contains(element),
SetStorage::Owned(target) => target.contains(element),
}
}
pub fn iter(&self) -> Box<dyn Iterator<Item = &T> + '_>
where
T: Eq + Hash,
{
match &self.storage {
SetStorage::Borrowed(target) => target.iter(),
SetStorage::Owned(target) => Box::new(target.iter()),
}
}
#[must_use]
pub fn is_borrowed_from(&self, target: &dyn SetView<T>) -> bool {
match self.storage {
SetStorage::Borrowed(source) => ptr::eq(source, target),
SetStorage::Owned(_) => false,
}
}
}
impl<T> SetView<T> for SetValue<'_, T>
where
T: Eq + Hash,
{
fn len(&self) -> usize {
SetValue::len(self)
}
fn contains(&self, element: &T) -> bool {
SetValue::contains(self, element)
}
fn iter(&self) -> Box<dyn Iterator<Item = &T> + '_> {
SetValue::iter(self)
}
}
#[derive(Debug, Error, PartialEq, Eq)]
pub enum SetUtilsError {
#[error(transparent)]
Validation(#[from] ValidateError),
#[error("Cannot convert object of class \"{class_name}\" to a set")]
CannotConvert {
class_name: String,
},
#[error("class \"{class_name}\" cannot be cast to class \"[Ljava.lang.Object;\"")]
ClassCast {
class_name: String,
},
}
pub struct SetUtils;
impl SetUtils {
pub fn to_set<'a, T>(target: Option<SetTarget<'a, T>>) -> Result<SetValue<'a, T>, SetUtilsError>
where
T: Clone + Eq + Hash,
{
Validate::not_null(target.as_ref(), Some("Cannot convert null to set"))?;
match target.expect("validated target") {
SetTarget::Set(target) => Ok(SetValue::borrowed(target)),
SetTarget::Array(target) => Ok(SetValue::owned(target.iter().cloned().collect())),
SetTarget::Iterable(target) => Ok(SetValue::owned(target.collect())),
SetTarget::PrimitiveArray(class_name) => Err(SetUtilsError::ClassCast {
class_name: class_name.to_owned(),
}),
SetTarget::Unsupported(class_name) => Err(SetUtilsError::CannotConvert {
class_name: class_name.to_owned(),
}),
}
}
pub fn size<T>(target: Option<&dyn SetView<T>>) -> Result<i32, ValidateError> {
Validate::not_null(target, Some("Cannot get set size of null"))?;
Ok(set_size(target.expect("validated target").len()))
}
#[must_use]
pub fn is_empty<T>(target: Option<&dyn SetView<T>>) -> bool {
target.is_none_or(SetView::is_empty)
}
pub fn contains<T>(
target: Option<&dyn SetView<T>>,
element: &T,
) -> Result<bool, ValidateError> {
Validate::not_null(target, Some("Cannot execute set contains: target is null"))?;
Ok(target.expect("validated target").contains(element))
}
pub fn contains_all_array<T>(
target: Option<&dyn SetView<T>>,
elements: Option<&[T]>,
) -> Result<bool, ValidateError> {
Validate::not_null(
target,
Some("Cannot execute set containsAll: target is null"),
)?;
Validate::not_null(
elements,
Some("Cannot execute set containsAll: elements is null"),
)?;
let target = target.expect("validated target");
Ok(elements
.expect("validated elements")
.iter()
.all(|element| target.contains(element)))
}
pub fn contains_all_collection<'a, T, I>(
target: Option<&dyn SetView<T>>,
elements: Option<I>,
) -> Result<bool, ValidateError>
where
T: 'a,
I: IntoIterator<Item = &'a T>,
{
Validate::not_null(target, Some("Cannot execute set contains: target is null"))?;
Validate::not_null(
elements.as_ref(),
Some("Cannot execute set containsAll: elements is null"),
)?;
let target = target.expect("validated target");
Ok(elements
.expect("validated elements")
.into_iter()
.all(|element| target.contains(element)))
}
#[must_use]
pub fn singleton_set<T>(element: T) -> SetValue<'static, T>
where
T: Eq + Hash,
{
let mut target = IndexSet::with_capacity(1);
target.insert(element);
SetValue::owned(target)
}
}
fn set_size(size: usize) -> i32 {
i32::try_from(size).unwrap_or(i32::MAX)
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use indexmap::IndexSet;
use super::{SetTarget, SetUtils, SetUtilsError, SetValue, SetView};
use crate::util::ValidateError;
fn ordered_set() -> IndexSet<Option<String>> {
IndexSet::from([Some("one".to_owned()), Some("two".to_owned()), None])
}
#[test]
fn converts_set_array_and_iterable_with_java_identity_and_order() {
let source = ordered_set();
let source_view: &dyn SetView<Option<String>> = &source;
let borrowed = SetUtils::to_set(Some(SetTarget::Set(source_view))).unwrap();
assert!(borrowed.is_borrowed_from(source_view));
assert_eq!(
borrowed.iter().cloned().collect::<Vec<_>>(),
source.iter().cloned().collect::<Vec<_>>()
);
let array = [
Some("two".to_owned()),
Some("one".to_owned()),
Some("two".to_owned()),
None,
];
let converted = SetUtils::to_set(Some(SetTarget::Array(&array))).unwrap();
assert_eq!(
converted.iter().cloned().collect::<Vec<_>>(),
vec![Some("two".to_owned()), Some("one".to_owned()), None]
);
let iterable = vec![
Some("a".to_owned()),
Some("a".to_owned()),
Some("b".to_owned()),
];
let converted =
SetUtils::to_set(Some(SetTarget::Iterable(Box::new(iterable.into_iter())))).unwrap();
assert_eq!(
converted.iter().cloned().collect::<Vec<_>>(),
vec![Some("a".to_owned()), Some("b".to_owned())]
);
}
#[test]
fn preserves_conversion_errors() {
assert_eq!(
SetUtils::to_set(None::<SetTarget<'_, Option<String>>>)
.err()
.expect("null error"),
SetUtilsError::Validation(ValidateError::IllegalArgument {
message: Some("Cannot convert null to set".to_owned())
})
);
assert_eq!(
SetUtils::to_set(Some(SetTarget::<Option<String>>::Unsupported(
"java.lang.Integer",
)))
.err()
.expect("unsupported error"),
SetUtilsError::CannotConvert {
class_name: "java.lang.Integer".to_owned()
}
);
assert_eq!(
SetUtils::to_set(Some(SetTarget::<Option<String>>::PrimitiveArray("[I")))
.err()
.expect("primitive array error"),
SetUtilsError::ClassCast {
class_name: "[I".to_owned()
}
);
}
#[test]
fn preserves_size_empty_contains_and_validation() {
let source = ordered_set();
let source_view: &dyn SetView<Option<String>> = &source;
let empty = HashSet::<Option<String>>::new();
let empty_view: &dyn SetView<Option<String>> = ∅
assert_eq!(SetUtils::size(Some(source_view)), Ok(3));
assert_eq!(
SetUtils::size(None::<&dyn SetView<Option<String>>>),
Err(ValidateError::IllegalArgument {
message: Some("Cannot get set size of null".to_owned())
})
);
assert!(!SetUtils::is_empty(Some(source_view)));
assert!(SetUtils::is_empty(Some(empty_view)));
assert!(SetUtils::is_empty(None::<&dyn SetView<Option<String>>>));
assert_eq!(super::set_size(usize::MAX), i32::MAX);
assert_eq!(
SetUtils::contains(Some(source_view), &Some("one".to_owned())),
Ok(true)
);
assert_eq!(
SetUtils::contains(Some(source_view), &Some("missing".to_owned())),
Ok(false)
);
assert_eq!(SetUtils::contains(Some(source_view), &None), Ok(true));
assert!(
SetUtils::contains(
None::<&dyn SetView<Option<String>>>,
&Some("one".to_owned())
)
.is_err()
);
}
#[test]
fn preserves_contains_all_overloads_and_validation_order() {
let source = ordered_set();
let source_view: &dyn SetView<Option<String>> = &source;
let present = [Some("one".to_owned()), None];
let missing = [Some("one".to_owned()), Some("missing".to_owned())];
assert_eq!(
SetUtils::contains_all_array(Some(source_view), Some(&present)),
Ok(true)
);
assert_eq!(
SetUtils::contains_all_array(Some(source_view), Some(&missing)),
Ok(false)
);
assert_eq!(
SetUtils::contains_all_array(Some(source_view), Some(&[])),
Ok(true)
);
assert_eq!(
SetUtils::contains_all_collection(Some(source_view), Some(present.iter())),
Ok(true)
);
assert_eq!(
SetUtils::contains_all_collection(Some(source_view), Some(missing.iter())),
Ok(false)
);
let array_target_error = SetUtils::contains_all_array(
None::<&dyn SetView<Option<String>>>,
None::<&[Option<String>]>,
)
.unwrap_err();
assert_eq!(
array_target_error.get_message(),
Some("Cannot execute set containsAll: target is null")
);
let collection_target_error = SetUtils::contains_all_collection(
None::<&dyn SetView<Option<String>>>,
None::<std::slice::Iter<'_, Option<String>>>,
)
.unwrap_err();
assert_eq!(
collection_target_error.get_message(),
Some("Cannot execute set contains: target is null")
);
assert!(
SetUtils::contains_all_array(Some(source_view), None::<&[Option<String>]>).is_err()
);
assert!(
SetUtils::contains_all_collection(
Some(source_view),
None::<std::slice::Iter<'_, Option<String>>>
)
.is_err()
);
}
#[test]
fn singleton_is_read_only_and_accepts_java_null_equivalent() {
let singleton = SetUtils::singleton_set(None::<String>);
assert_eq!(singleton.len(), 1);
assert!(singleton.contains(&None));
assert_eq!(singleton.iter().cloned().collect::<Vec<_>>(), vec![None]);
assert!(!singleton.is_empty());
assert!(!singleton.is_borrowed_from(&ordered_set()));
let singleton_view: &dyn SetView<Option<String>> = &singleton;
assert_eq!(singleton_view.len(), 1);
assert!(singleton_view.contains(&None));
assert_eq!(
singleton_view.iter().cloned().collect::<Vec<_>>(),
vec![None]
);
let _: &SetValue<'_, Option<String>> = &singleton;
}
#[test]
fn supports_hash_and_tree_set_views_and_borrowed_java_set_operations() {
let hash = HashSet::from([Some("two".to_owned()), Some("one".to_owned())]);
let hash_view: &dyn SetView<Option<String>> = &hash;
assert!(hash_view.contains(&Some("one".to_owned())));
assert_eq!(hash_view.iter().count(), 2);
let tree =
std::collections::BTreeSet::from([Some("two".to_owned()), Some("one".to_owned())]);
let tree_view: &dyn SetView<Option<String>> = &tree;
assert_eq!(tree_view.len(), 2);
assert!(tree_view.contains(&Some("two".to_owned())));
let borrowed = SetUtils::to_set(Some(SetTarget::Set(tree_view))).unwrap();
assert_eq!(borrowed.len(), 2);
assert!(!borrowed.is_empty());
assert!(borrowed.contains(&Some("one".to_owned())));
}
}