use std::cmp::Ordering;
use std::collections::{LinkedList, VecDeque};
use std::ptr;
use thiserror::Error;
use super::{Validate, ValidateError};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ListTypeValue {
ArrayList,
LinkedList,
Custom {
class_name: String,
public_no_arg_constructor: bool,
},
}
impl ListTypeValue {
#[must_use]
pub fn custom(class_name: impl Into<String>, public_no_arg_constructor: bool) -> Self {
Self::Custom {
class_name: class_name.into(),
public_no_arg_constructor,
}
}
#[must_use]
pub fn class_name(&self) -> &str {
match self {
Self::ArrayList => "java.util.ArrayList",
Self::LinkedList => "java.util.LinkedList",
Self::Custom { class_name, .. } => class_name,
}
}
fn sorted_result_type(&self) -> Self {
match self {
Self::ArrayList => Self::ArrayList,
Self::LinkedList => Self::LinkedList,
Self::Custom {
class_name,
public_no_arg_constructor: true,
} => Self::custom(class_name, true),
Self::Custom {
public_no_arg_constructor: false,
..
} => Self::ArrayList,
}
}
}
#[derive(Debug, Error, PartialEq, Eq)]
pub enum ListUtilsError {
#[error(transparent)]
Validation(#[from] ValidateError),
#[error("Cannot convert object of class \"{class_name}\" to a list")]
CannotConvert {
class_name: String,
},
#[error("class \"{class_name}\" cannot be cast to class \"[Ljava.lang.Object;\"")]
ClassCast {
class_name: String,
},
#[error("natural ordering cannot compare null")]
NaturalOrderingNull,
#[error("class \"{left_class}\" cannot be compared to class \"{right_class}\"")]
NaturalOrderingClassCast {
left_class: String,
right_class: String,
},
#[error("{class_name}:{message}")]
Runtime {
class_name: String,
message: String,
},
}
impl ListUtilsError {
#[must_use]
pub fn runtime(class_name: impl Into<String>, message: impl Into<String>) -> Self {
Self::Runtime {
class_name: class_name.into(),
message: message.into(),
}
}
}
pub trait ComparableValue {
fn template_compare_to(&self, other: &Self) -> Result<Ordering, ListUtilsError>;
}
macro_rules! impl_java_comparable_ord {
($($type:ty),+ $(,)?) => {
$(
impl ComparableValue for $type {
fn template_compare_to(&self, other: &Self) -> Result<Ordering, ListUtilsError> {
Ok(self.cmp(other))
}
}
)+
};
}
impl_java_comparable_ord!(bool, i8, i16, i32, i64, u16);
impl ComparableValue for String {
fn template_compare_to(&self, other: &Self) -> Result<Ordering, ListUtilsError> {
Ok(self.encode_utf16().cmp(other.encode_utf16()))
}
}
impl ComparableValue for f32 {
fn template_compare_to(&self, other: &Self) -> Result<Ordering, ListUtilsError> {
Ok(f32_compare(*self, *other))
}
}
impl ComparableValue for f64 {
fn template_compare_to(&self, other: &Self) -> Result<Ordering, ListUtilsError> {
Ok(f64_compare(*self, *other))
}
}
impl<T> ComparableValue for Option<T>
where
T: ComparableValue,
{
fn template_compare_to(&self, other: &Self) -> Result<Ordering, ListUtilsError> {
match (self, other) {
(Some(left), Some(right)) => left.template_compare_to(right),
_ => Err(ListUtilsError::NaturalOrderingNull),
}
}
}
pub trait ComparatorValue<T> {
fn compare(&mut self, left: &T, right: &T) -> Result<Ordering, ListUtilsError>;
}
impl<T, F> ComparatorValue<T> for F
where
F: FnMut(&T, &T) -> Result<Ordering, ListUtilsError>,
{
fn compare(&mut self, left: &T, right: &T) -> Result<Ordering, ListUtilsError> {
self(left, right)
}
}
pub trait ListView<T> {
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn get(&self, index: usize) -> Option<&T>;
fn iter(&self) -> Box<dyn Iterator<Item = &T> + '_>;
fn list_type(&self) -> ListTypeValue;
fn snapshot(&self) -> Result<Vec<T>, ListUtilsError>
where
T: Clone,
{
Ok(self.iter().cloned().collect())
}
fn fill_sorted(&self, elements: Vec<T>) -> Result<ListValue<'static, T>, ListUtilsError>
where
T: 'static,
{
Ok(fill_new_list(elements, &self.list_type()))
}
}
impl<T> ListView<T> for Vec<T> {
fn len(&self) -> usize {
Vec::len(self)
}
fn get(&self, index: usize) -> Option<&T> {
self.as_slice().get(index)
}
fn iter(&self) -> Box<dyn Iterator<Item = &T> + '_> {
Box::new(self.as_slice().iter())
}
fn list_type(&self) -> ListTypeValue {
ListTypeValue::ArrayList
}
}
impl<T> ListView<T> for LinkedList<T> {
fn len(&self) -> usize {
LinkedList::len(self)
}
fn get(&self, index: usize) -> Option<&T> {
LinkedList::iter(self).nth(index)
}
fn iter(&self) -> Box<dyn Iterator<Item = &T> + '_> {
Box::new(LinkedList::iter(self))
}
fn list_type(&self) -> ListTypeValue {
ListTypeValue::LinkedList
}
}
pub enum ListTarget<'a, T> {
List(&'a dyn ListView<T>),
Array(&'a [T]),
Iterable(Box<dyn Iterator<Item = T> + 'a>),
PrimitiveArray(&'a str),
Unsupported(&'a str),
}
enum ListStorage<'a, T> {
Borrowed(&'a dyn ListView<T>),
Owned {
elements: Vec<T>,
list_type: ListTypeValue,
},
}
pub struct ListValue<'a, T> {
storage: ListStorage<'a, T>,
}
impl<'a, T> ListValue<'a, T> {
fn borrowed(target: &'a dyn ListView<T>) -> Self {
Self {
storage: ListStorage::Borrowed(target),
}
}
fn owned(elements: Vec<T>, list_type: ListTypeValue) -> Self {
Self {
storage: ListStorage::Owned {
elements,
list_type,
},
}
}
#[must_use]
pub fn len(&self) -> usize {
match &self.storage {
ListStorage::Borrowed(target) => target.len(),
ListStorage::Owned { elements, .. } => elements.len(),
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub fn get(&self, index: usize) -> Option<&T> {
match &self.storage {
ListStorage::Borrowed(target) => target.get(index),
ListStorage::Owned { elements, .. } => elements.get(index),
}
}
pub fn iter(&self) -> Box<dyn Iterator<Item = &T> + '_> {
match &self.storage {
ListStorage::Borrowed(target) => target.iter(),
ListStorage::Owned { elements, .. } => Box::new(elements.iter()),
}
}
#[must_use]
pub fn contains(&self, element: &T) -> bool
where
T: PartialEq,
{
self.iter().any(|candidate| candidate == element)
}
#[must_use]
pub fn list_type(&self) -> ListTypeValue {
match &self.storage {
ListStorage::Borrowed(target) => target.list_type(),
ListStorage::Owned { list_type, .. } => list_type.clone(),
}
}
#[must_use]
pub fn is_borrowed_from(&self, target: &dyn ListView<T>) -> bool {
match self.storage {
ListStorage::Borrowed(source) => ptr::eq(source, target),
ListStorage::Owned { .. } => false,
}
}
}
impl<T> ListView<T> for ListValue<'_, T> {
fn len(&self) -> usize {
ListValue::len(self)
}
fn get(&self, index: usize) -> Option<&T> {
ListValue::get(self, index)
}
fn iter(&self) -> Box<dyn Iterator<Item = &T> + '_> {
ListValue::iter(self)
}
fn list_type(&self) -> ListTypeValue {
ListValue::list_type(self)
}
}
pub struct ListUtils;
impl ListUtils {
pub fn to_list<'a, T>(
target: Option<ListTarget<'a, T>>,
) -> Result<ListValue<'a, T>, ListUtilsError>
where
T: Clone,
{
Validate::not_null(target.as_ref(), Some("Cannot convert null to list"))?;
match target.expect("validated target") {
ListTarget::List(target) => Ok(ListValue::borrowed(target)),
ListTarget::Array(target) => {
Ok(ListValue::owned(target.to_vec(), ListTypeValue::ArrayList))
}
ListTarget::Iterable(target) => {
Ok(ListValue::owned(target.collect(), ListTypeValue::ArrayList))
}
ListTarget::PrimitiveArray(class_name) => Err(ListUtilsError::ClassCast {
class_name: class_name.to_owned(),
}),
ListTarget::Unsupported(class_name) => Err(ListUtilsError::CannotConvert {
class_name: class_name.to_owned(),
}),
}
}
pub fn size<T>(target: Option<&dyn ListView<T>>) -> Result<i32, ValidateError> {
Validate::not_null(target, Some("Cannot get list size of null"))?;
Ok(list_size(target.expect("validated target").len()))
}
#[must_use]
pub fn is_empty<T>(target: Option<&dyn ListView<T>>) -> bool {
target.is_none_or(ListView::is_empty)
}
pub fn contains<T>(target: Option<&dyn ListView<T>>, element: &T) -> Result<bool, ValidateError>
where
T: PartialEq,
{
Validate::not_null(target, Some("Cannot execute list contains: target is null"))?;
Ok(target
.expect("validated target")
.iter()
.any(|candidate| candidate == element))
}
pub fn contains_all_array<T>(
target: Option<&dyn ListView<T>>,
elements: Option<&[T]>,
) -> Result<bool, ValidateError>
where
T: PartialEq,
{
Validate::not_null(
target,
Some("Cannot execute list containsAll: target is null"),
)?;
Validate::not_null(
elements,
Some("Cannot execute list containsAll: elements is null"),
)?;
let target = target.expect("validated target");
Ok(elements
.expect("validated elements")
.iter()
.all(|element| target.iter().any(|candidate| candidate == element)))
}
pub fn contains_all_collection<'a, T, I>(
target: Option<&dyn ListView<T>>,
elements: Option<I>,
) -> Result<bool, ValidateError>
where
T: PartialEq + 'a,
I: IntoIterator<Item = &'a T>,
{
Validate::not_null(target, Some("Cannot execute list contains: target is null"))?;
Validate::not_null(
elements.as_ref(),
Some("Cannot execute list containsAll: elements is null"),
)?;
let target = target.expect("validated target");
Ok(elements
.expect("validated elements")
.into_iter()
.all(|element| target.iter().any(|candidate| candidate == element)))
}
pub fn sort<T>(list: Option<&dyn ListView<T>>) -> Result<ListValue<'static, T>, ListUtilsError>
where
T: Clone + ComparableValue + 'static,
{
Self::sort_with_comparator(list, None)
}
pub fn sort_with_comparator<T>(
list: Option<&dyn ListView<T>>,
comparator: Option<&mut dyn ComparatorValue<T>>,
) -> Result<ListValue<'static, T>, ListUtilsError>
where
T: Clone + ComparableValue + 'static,
{
Validate::not_null(list, Some("Cannot execute list sort: list is null"))?;
let list = list.expect("validated list");
let elements = list.snapshot()?;
let sorted = match comparator {
Some(comparator) => stable_sort(elements, comparator),
None => {
let mut natural = |left: &T, right: &T| left.template_compare_to(right);
stable_sort(elements, &mut natural)
}
};
sorted.and_then(|elements| list.fill_sorted(elements))
}
pub fn sort_with_required_comparator<T>(
list: Option<&dyn ListView<T>>,
comparator: &mut dyn ComparatorValue<T>,
) -> Result<ListValue<'static, T>, ListUtilsError>
where
T: Clone + 'static,
{
Validate::not_null(list, Some("Cannot execute list sort: list is null"))?;
let list = list.expect("validated list");
let elements = list.snapshot()?;
stable_sort(elements, comparator).and_then(|sorted| list.fill_sorted(sorted))
}
}
fn fill_new_list<T>(elements: Vec<T>, list_type: &ListTypeValue) -> ListValue<'static, T> {
ListValue::owned(elements, list_type.sorted_result_type())
}
fn stable_sort<T>(
mut elements: Vec<T>,
comparator: &mut dyn ComparatorValue<T>,
) -> Result<Vec<T>, ListUtilsError> {
if elements.len() < 2 {
return Ok(elements);
}
let right = elements.split_off(elements.len() / 2);
let left = stable_sort(elements, comparator)?;
let right = stable_sort(right, comparator)?;
merge(left, right, comparator)
}
fn merge<T>(
left: Vec<T>,
right: Vec<T>,
comparator: &mut dyn ComparatorValue<T>,
) -> Result<Vec<T>, ListUtilsError> {
let capacity = left.len().saturating_add(right.len());
let mut left = VecDeque::from(left);
let mut right = VecDeque::from(right);
let mut result = Vec::with_capacity(capacity);
while let (Some(left_value), Some(right_value)) = (left.front(), right.front()) {
if comparator.compare(left_value, right_value)? == Ordering::Greater {
result.push(right.pop_front().expect("right front exists"));
} else {
result.push(left.pop_front().expect("left front exists"));
}
}
result.extend(left);
result.extend(right);
Ok(result)
}
fn list_size(size: usize) -> i32 {
i32::try_from(size).unwrap_or(i32::MAX)
}
fn f32_compare(left: f32, right: f32) -> Ordering {
if left < right {
Ordering::Less
} else if left > right {
Ordering::Greater
} else {
let left_bits = f32_bits(left);
let right_bits = f32_bits(right);
left_bits.cmp(&right_bits)
}
}
fn f32_bits(value: f32) -> i32 {
if value.is_nan() {
0x7fc0_0000_u32 as i32
} else {
value.to_bits() as i32
}
}
fn f64_compare(left: f64, right: f64) -> Ordering {
if left < right {
Ordering::Less
} else if left > right {
Ordering::Greater
} else {
let left_bits = f64_bits(left);
let right_bits = f64_bits(right);
left_bits.cmp(&right_bits)
}
}
fn f64_bits(value: f64) -> i64 {
if value.is_nan() {
0x7ff8_0000_0000_0000_u64 as i64
} else {
value.to_bits() as i64
}
}
#[cfg(test)]
mod tests {
use std::cmp::Ordering;
use std::collections::LinkedList;
use super::{
ComparableValue, ListTarget, ListTypeValue, ListUtils, ListUtilsError, ListValue, ListView,
};
use crate::util::ValidateError;
#[derive(Clone, Debug, Eq, PartialEq)]
struct Item {
key: i32,
id: &'static str,
}
struct CustomList<T> {
values: Vec<T>,
list_type: ListTypeValue,
snapshot_error: Option<(&'static str, &'static str)>,
}
struct AddFailingList<T> {
values: Vec<T>,
}
impl<T> ListView<T> for CustomList<T> {
fn len(&self) -> usize {
self.values.len()
}
fn get(&self, index: usize) -> Option<&T> {
self.values.get(index)
}
fn iter(&self) -> Box<dyn Iterator<Item = &T> + '_> {
Box::new(self.values.iter())
}
fn list_type(&self) -> ListTypeValue {
self.list_type.clone()
}
fn snapshot(&self) -> Result<Vec<T>, ListUtilsError>
where
T: Clone,
{
match self.snapshot_error {
Some((class_name, message)) => Err(ListUtilsError::runtime(class_name, message)),
None => Ok(self.values.clone()),
}
}
}
impl<T> ListView<T> for AddFailingList<T> {
fn len(&self) -> usize {
self.values.len()
}
fn get(&self, index: usize) -> Option<&T> {
self.values.get(index)
}
fn iter(&self) -> Box<dyn Iterator<Item = &T> + '_> {
Box::new(self.values.iter())
}
fn list_type(&self) -> ListTypeValue {
ListTypeValue::custom("example.AddFailingList", true)
}
fn fill_sorted(&self, _elements: Vec<T>) -> Result<ListValue<'static, T>, ListUtilsError>
where
T: 'static,
{
Err(ListUtilsError::runtime(
"java.lang.UnsupportedOperationException",
"add failed",
))
}
}
#[test]
fn converts_list_array_and_iterable_with_identity_and_order() {
let source = vec![Some("one".to_owned()), None, Some("one".to_owned())];
let view: &dyn ListView<Option<String>> = &source;
let borrowed = ListUtils::to_list(Some(ListTarget::List(view))).unwrap();
assert!(borrowed.is_borrowed_from(view));
assert_eq!(borrowed.len(), 3);
assert_eq!(borrowed.list_type(), ListTypeValue::ArrayList);
assert_eq!(borrowed.get(1), Some(&None));
assert_eq!(borrowed.get(10), None);
let array = [Some("two".to_owned()), None, Some("two".to_owned())];
let converted = ListUtils::to_list(Some(ListTarget::Array(&array))).unwrap();
assert_eq!(
converted.iter().cloned().collect::<Vec<_>>(),
array.to_vec()
);
assert!(!converted.is_borrowed_from(view));
let iterable = vec![Some("b".to_owned()), Some("a".to_owned())];
let converted = ListUtils::to_list(Some(ListTarget::Iterable(Box::new(
iterable.clone().into_iter(),
))))
.unwrap();
assert_eq!(converted.iter().cloned().collect::<Vec<_>>(), iterable);
}
#[test]
fn preserves_conversion_errors_and_runtime_error_factory() {
assert_eq!(
ListUtils::to_list(None::<ListTarget<'_, Option<String>>>)
.err()
.expect("null"),
ListUtilsError::Validation(ValidateError::IllegalArgument {
message: Some("Cannot convert null to list".to_owned())
})
);
assert_eq!(
ListUtils::to_list(Some(ListTarget::<Option<String>>::PrimitiveArray("[I")))
.err()
.expect("primitive"),
ListUtilsError::ClassCast {
class_name: "[I".to_owned()
}
);
assert_eq!(
ListUtils::to_list(Some(ListTarget::<Option<String>>::Unsupported(
"java.lang.Integer",
)))
.err()
.expect("unsupported"),
ListUtilsError::CannotConvert {
class_name: "java.lang.Integer".to_owned()
}
);
assert_eq!(
ListUtilsError::runtime("java.lang.IllegalStateException", "boom"),
ListUtilsError::Runtime {
class_name: "java.lang.IllegalStateException".to_owned(),
message: "boom".to_owned()
}
);
assert_eq!(
ListUtilsError::NaturalOrderingClassCast {
left_class: "java.lang.String".to_owned(),
right_class: "java.lang.Integer".to_owned()
}
.to_string(),
"class \"java.lang.String\" cannot be compared to class \"java.lang.Integer\""
);
}
#[test]
fn checks_size_empty_contains_and_both_contains_all_overloads() {
let source = vec![Some("one".to_owned()), None, Some("one".to_owned())];
let view: &dyn ListView<Option<String>> = &source;
let empty = Vec::<Option<String>>::new();
let empty_view: &dyn ListView<Option<String>> = ∅
assert_eq!(ListUtils::size(Some(view)), Ok(3));
assert_eq!(
ListUtils::size(None::<&dyn ListView<Option<String>>>),
Err(ValidateError::IllegalArgument {
message: Some("Cannot get list size of null".to_owned())
})
);
assert!(!ListUtils::is_empty(Some(view)));
assert!(ListUtils::is_empty(Some(empty_view)));
assert!(ListUtils::is_empty(None::<&dyn ListView<Option<String>>>));
assert_eq!(super::list_size(usize::MAX), i32::MAX);
assert_eq!(ListUtils::contains(Some(view), &None), Ok(true));
assert_eq!(
ListUtils::contains(Some(view), &Some("missing".to_owned())),
Ok(false)
);
assert!(ListUtils::contains(None::<&dyn ListView<Option<String>>>, &None).is_err());
let present = [Some("one".to_owned()), None, Some("one".to_owned())];
let missing = [Some("one".to_owned()), Some("missing".to_owned())];
assert_eq!(
ListUtils::contains_all_array(Some(view), Some(&present)),
Ok(true)
);
assert_eq!(
ListUtils::contains_all_array(Some(view), Some(&missing)),
Ok(false)
);
assert_eq!(
ListUtils::contains_all_collection(Some(view), Some(present.iter())),
Ok(true)
);
assert_eq!(
ListUtils::contains_all_collection(Some(view), Some(missing.iter())),
Ok(false)
);
let target_error = ListUtils::contains_all_array(
None::<&dyn ListView<Option<String>>>,
None::<&[Option<String>]>,
)
.unwrap_err();
assert_eq!(
target_error.get_message(),
Some("Cannot execute list containsAll: target is null")
);
let collection_target_error = ListUtils::contains_all_collection(
None::<&dyn ListView<Option<String>>>,
None::<std::slice::Iter<'_, Option<String>>>,
)
.unwrap_err();
assert_eq!(
collection_target_error.get_message(),
Some("Cannot execute list contains: target is null")
);
assert!(ListUtils::contains_all_array(Some(view), None::<&[Option<String>]>).is_err());
assert!(
ListUtils::contains_all_collection(
Some(view),
None::<std::slice::Iter<'_, Option<String>>>
)
.is_err()
);
}
#[test]
fn stable_sort_preserves_source_and_runtime_list_type_or_fallback() {
let source = LinkedList::from(["c".to_owned(), "a".to_owned(), "b".to_owned()]);
let source_view: &dyn ListView<String> = &source;
assert_eq!(source_view.get(1), Some(&"a".to_owned()));
let sorted = ListUtils::sort(Some(source_view)).unwrap();
assert_eq!(
sorted.iter().map(String::as_str).collect::<Vec<_>>(),
vec!["a", "b", "c"]
);
assert_eq!(
source.iter().map(String::as_str).collect::<Vec<_>>(),
vec!["c", "a", "b"]
);
assert_eq!(sorted.list_type(), ListTypeValue::LinkedList);
let constructible = CustomList {
values: vec!["b".to_owned(), "a".to_owned()],
list_type: ListTypeValue::custom("example.PublicList", true),
snapshot_error: None,
};
assert_eq!(constructible.len(), 2);
assert_eq!(constructible.get(0), Some(&"b".to_owned()));
assert_eq!(constructible.iter().count(), 2);
let sorted = ListUtils::sort(Some(&constructible as &dyn ListView<String>)).unwrap();
assert_eq!(
sorted.list_type(),
ListTypeValue::custom("example.PublicList", true)
);
let fallback = CustomList {
values: vec!["b".to_owned(), "a".to_owned()],
list_type: ListTypeValue::custom("example.PrivateList", false),
snapshot_error: None,
};
let sorted = ListUtils::sort(Some(&fallback as &dyn ListView<String>)).unwrap();
assert_eq!(sorted.list_type(), ListTypeValue::ArrayList);
assert_eq!(fallback.list_type.class_name(), "example.PrivateList");
let add_failing = AddFailingList {
values: vec!["b".to_owned(), "a".to_owned()],
};
assert_eq!(add_failing.len(), 2);
assert_eq!(add_failing.get(1), Some(&"a".to_owned()));
assert_eq!(
add_failing.list_type(),
ListTypeValue::custom("example.AddFailingList", true)
);
assert_eq!(
ListUtils::sort(Some(&add_failing as &dyn ListView<String>))
.err()
.expect("add failure"),
ListUtilsError::runtime("java.lang.UnsupportedOperationException", "add failed")
);
}
#[test]
fn comparator_sort_is_stable_nullable_and_supports_non_comparable_types() {
let source = vec!["c".to_owned(), "a".to_owned(), "b".to_owned()];
let view: &dyn ListView<String> = &source;
let mut descending = |left: &String, right: &String| right.template_compare_to(left);
let sorted = ListUtils::sort_with_comparator(Some(view), Some(&mut descending)).unwrap();
assert_eq!(
sorted.iter().map(String::as_str).collect::<Vec<_>>(),
vec!["c", "b", "a"]
);
let natural = ListUtils::sort_with_comparator(Some(view), None).unwrap();
assert_eq!(
natural.iter().map(String::as_str).collect::<Vec<_>>(),
vec!["a", "b", "c"]
);
let items = CustomList {
values: vec![
Item {
key: 2,
id: "first",
},
Item {
key: 1,
id: "middle",
},
Item {
key: 2,
id: "second",
},
],
list_type: ListTypeValue::ArrayList,
snapshot_error: None,
};
assert_eq!(items.len(), 3);
assert_eq!(items.get(1).map(|item| item.id), Some("middle"));
assert_eq!(items.iter().count(), 3);
assert_eq!(items.list_type(), ListTypeValue::ArrayList);
let item_view: &dyn ListView<Item> = &items;
let mut by_key = |left: &Item, right: &Item| Ok(left.key.cmp(&right.key));
let sorted =
ListUtils::sort_with_required_comparator(Some(item_view), &mut by_key).unwrap();
assert_eq!(
sorted.iter().map(|item| item.id).collect::<Vec<_>>(),
vec!["middle", "first", "second"]
);
assert!(
ListUtils::sort_with_required_comparator(None::<&dyn ListView<Item>>, &mut by_key,)
.is_err()
);
let snapshot_failing = CustomList {
values: Vec::<Item>::new(),
list_type: ListTypeValue::ArrayList,
snapshot_error: Some(("java.lang.IllegalStateException", "toArray failed")),
};
assert!(
ListUtils::sort_with_required_comparator(Some(&snapshot_failing), &mut by_key).is_err()
);
let four_items = vec![
Item { key: 1, id: "1" },
Item { key: 2, id: "2" },
Item { key: 3, id: "3" },
Item { key: 4, id: "4" },
];
let mut failing = |_left: &Item, _right: &Item| {
Err(ListUtilsError::runtime(
"java.lang.IllegalStateException",
"compare failed",
))
};
assert!(ListUtils::sort_with_required_comparator(Some(&four_items), &mut failing).is_err());
let mut comparisons = 0_u8;
let mut fail_in_right_half = |left: &Item, right: &Item| {
comparisons = comparisons.saturating_add(1);
if comparisons == 2 {
Err(ListUtilsError::runtime(
"java.lang.IllegalStateException",
"right comparison failed",
))
} else {
Ok(left.key.cmp(&right.key))
}
};
assert!(
ListUtils::sort_with_required_comparator(Some(&four_items), &mut fail_in_right_half,)
.is_err()
);
let mut comparisons = 0_u8;
let mut fail_in_outer_merge = |left: &Item, right: &Item| {
comparisons = comparisons.saturating_add(1);
if comparisons == 3 {
Err(ListUtilsError::runtime(
"java.lang.IllegalStateException",
"outer comparison failed",
))
} else {
Ok(left.key.cmp(&right.key))
}
};
assert!(
ListUtils::sort_with_required_comparator(Some(&four_items), &mut fail_in_outer_merge,)
.is_err()
);
}
#[test]
fn sorting_propagates_null_snapshot_comparator_and_empty_boundaries() {
assert_eq!(
ListUtils::sort(None::<&dyn ListView<String>>)
.err()
.expect("null sort"),
ListUtilsError::Validation(ValidateError::IllegalArgument {
message: Some("Cannot execute list sort: list is null".to_owned())
})
);
let nullable = vec![Some("a".to_owned()), None];
assert_eq!(
ListUtils::sort(Some(&nullable as &dyn ListView<Option<String>>))
.err()
.expect("null element"),
ListUtilsError::NaturalOrderingNull
);
let null_in_left_half = vec![
Some("a".to_owned()),
None,
Some("b".to_owned()),
Some("c".to_owned()),
];
let null_in_right_half = vec![
Some("a".to_owned()),
Some("b".to_owned()),
Some("c".to_owned()),
None,
];
assert!(ListUtils::sort(Some(&null_in_left_half)).is_err());
assert!(ListUtils::sort(Some(&null_in_right_half)).is_err());
let failing = CustomList {
values: vec!["b".to_owned(), "a".to_owned()],
list_type: ListTypeValue::custom("example.FailingList", true),
snapshot_error: Some(("java.lang.IllegalStateException", "toArray failed")),
};
assert_eq!(
ListUtils::sort(Some(&failing as &dyn ListView<String>))
.err()
.expect("snapshot"),
ListUtilsError::runtime("java.lang.IllegalStateException", "toArray failed")
);
let source = vec!["b".to_owned(), "a".to_owned()];
let mut comparator = |_left: &String, _right: &String| {
Err(ListUtilsError::runtime(
"java.lang.IllegalStateException",
"compare failed",
))
};
assert_eq!(
ListUtils::sort_with_comparator(
Some(&source as &dyn ListView<String>),
Some(&mut comparator),
)
.err()
.expect("comparator"),
ListUtilsError::runtime("java.lang.IllegalStateException", "compare failed")
);
let four_strings = vec![
"a".to_owned(),
"b".to_owned(),
"c".to_owned(),
"d".to_owned(),
];
let mut fail_in_left_half = |_left: &String, _right: &String| {
Err(ListUtilsError::runtime(
"java.lang.IllegalStateException",
"left comparison failed",
))
};
assert!(
ListUtils::sort_with_comparator(Some(&four_strings), Some(&mut fail_in_left_half),)
.is_err()
);
let mut comparisons = 0_u8;
let mut fail_in_right_half = |left: &String, right: &String| {
comparisons = comparisons.saturating_add(1);
if comparisons == 2 {
Err(ListUtilsError::runtime(
"java.lang.IllegalStateException",
"right comparison failed",
))
} else {
left.template_compare_to(right)
}
};
assert!(
ListUtils::sort_with_comparator(Some(&four_strings), Some(&mut fail_in_right_half),)
.is_err()
);
let empty = Vec::<String>::new();
let singleton = vec!["one".to_owned()];
assert!(
ListUtils::sort(Some(&empty as &dyn ListView<String>))
.unwrap()
.is_empty()
);
assert_eq!(
ListUtils::sort(Some(&singleton as &dyn ListView<String>))
.unwrap()
.get(0),
Some(&"one".to_owned())
);
}
#[test]
fn comparable_matches_utf16_and_float_wrapper_ordering() {
assert_eq!(
"\u{1f600}"
.to_owned()
.template_compare_to(&"\u{e000}".to_owned()),
Ok(Ordering::Less)
);
assert_eq!((-0.0_f64).template_compare_to(&0.0_f64), Ok(Ordering::Less));
assert_eq!(1.0_f64.template_compare_to(&2.0_f64), Ok(Ordering::Less));
assert_eq!(2.0_f64.template_compare_to(&1.0_f64), Ok(Ordering::Greater));
assert_eq!(
f64::NAN.template_compare_to(&f64::INFINITY),
Ok(Ordering::Greater)
);
assert_eq!((-0.0_f32).template_compare_to(&0.0_f32), Ok(Ordering::Less));
assert_eq!(1.0_f32.template_compare_to(&2.0_f32), Ok(Ordering::Less));
assert_eq!(2.0_f32.template_compare_to(&1.0_f32), Ok(Ordering::Greater));
assert_eq!(
f32::NAN.template_compare_to(&f32::INFINITY),
Ok(Ordering::Greater)
);
assert_eq!(
Some("a".to_owned()).template_compare_to(&Some("b".to_owned())),
Ok(Ordering::Less)
);
assert_eq!(false.template_compare_to(&true), Ok(Ordering::Less));
assert_eq!(1_i8.template_compare_to(&2), Ok(Ordering::Less));
assert_eq!(1_i16.template_compare_to(&2), Ok(Ordering::Less));
assert_eq!(1_i32.template_compare_to(&2), Ok(Ordering::Less));
assert_eq!(1_i64.template_compare_to(&2), Ok(Ordering::Less));
assert_eq!(1_u16.template_compare_to(&2), Ok(Ordering::Less));
}
#[test]
fn list_delegates_list_view_operations() {
let list = ListValue::owned(vec![Some("one".to_owned()), None], ListTypeValue::ArrayList);
let view: &dyn ListView<Option<String>> = &list;
assert_eq!(view.len(), 2);
assert!(!view.is_empty());
assert_eq!(view.get(1), Some(&None));
assert_eq!(view.iter().count(), 2);
assert_eq!(view.list_type(), ListTypeValue::ArrayList);
assert!(list.contains(&None));
}
}