org.apache.isis.commons.collections.Can Maven / Gradle / Ivy
Show all versions of isis-commons Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.isis.commons.collections;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.lang.Nullable;
import org.apache.isis.commons.internal.base._NullSafe;
import org.apache.isis.commons.internal.exceptions._Exceptions;
import lombok.NonNull;
import lombok.val;
/**
*
* Immutable {@link Iterable}, that can specifically represent 3 possible variants of
* {@link Cardinality}.
*
* Java's {@link Optional}, can be seen as a holder of element(s), that is restricted to
* cardinality ZERO or ONE. {@link Can} is the logical extension to that, allowing also a
* cardinality of MULTIPLE.
*
* Same idiomatic convention applies: References to {@link Can}
* should never be initialized to {@code null}.
*
* A {@link Can} must not contain elements equal to {@code null}.
*
* @param
* @since 2.0 {@index}
*/
public interface Can
extends ImmutableCollection, Comparable>, Serializable {
/**
* Will only ever return an empty Optional, if the elementIndex is out of bounds.
* @param elementIndex
* @return optionally this Can's element with index {@code elementIndex},
* based on whether this index is within bounds
*/
Optional get(int elementIndex);
/**
* Shortcut to {@code get(elementIndex).orElseThrow(...)}
*
* Will only ever throw, if the elementIndex is out of bounds.
* @param elementIndex
* @return this Can's element with index {@code elementIndex}
* @throws NoSuchElementException when the elementIndex is out of bounds
* @see {@link #get(int)}
*/
default T getElseFail(final int elementIndex) {
return get(elementIndex)
.orElseThrow(()->new NoSuchElementException(
"no element with elementIndex = " + elementIndex));
}
/**
* For convenience allows the argument to be {@code null} treating {@code null}
* equivalent to {@link Can#empty()}.
* @see {@link Comparable#compareTo(Object)}
*/
@Override
int compareTo(final @Nullable Can o);
/**
* @return this Can's first element or an empty Optional if no such element
*/
Optional getFirst();
/**
* Shortcut for {@code getFirst().orElseThrow(_Exceptions::noSuchElement)}
* @throws NoSuchElementException if result is empty
*/
default T getFirstOrFail() {
return getFirst().orElseThrow(_Exceptions::noSuchElement);
}
/**
* @return this Can's last element or an empty Optional if no such element
*/
Optional getLast();
/**
* Shortcut for {@code getLast().orElseThrow(_Exceptions::noSuchElement)}
* @throws NoSuchElementException if result is empty
*/
default T getLastOrFail() {
return getLast().orElseThrow(_Exceptions::noSuchElement);
}
// -- FACTORIES
/**
* Returns an empty {@code Can}.
* @param
*/
@SuppressWarnings("unchecked") // this is how the JDK does it for eg. empty lists
public static Can empty() {
return (Can) Can_Empty.INSTANCE;
}
/**
* Returns either a {@code Can} with the given {@code element} or an empty {@code Can} if the
* {@code element} is {@code null}.
* @param
* @param element
* @return non-null
*/
public static Can ofNullable(final @Nullable T element) {
if(element==null) {
return empty();
}
return Can_Singleton.of(element);
}
/**
* Returns either a {@code Can} with the given {@code element} or throws if the
* {@code element} is {@code null}.
* @param
* @param element
* @return non-null
* @throws NullPointerException if {@code element} is {@code null}
*/
public static Can ofSingleton(final @NonNull T element) {
return Can_Singleton.of(element);
}
/**
* Var-arg version of {@link Can#ofArray(Object[])}.
* @param
* @param array
* @return non-null
* @see Can#ofArray(Object[])
*/
@SafeVarargs
public static Can of(final T ... array) {
return ofArray(array);
}
/**
* Returns either a {@code Can} with all the elements from given {@code array}
* or an empty {@code Can} if the {@code array} is {@code null}. Any elements
* equal to {@code null} are ignored and will not be contained in the resulting {@code Can}.
* @param
* @param array
* @return non-null
*/
public static Can ofArray(final @Nullable T[] array) {
if(_NullSafe.size(array)==0) {
return empty();
}
val nonNullElements = Stream.of(array)
.filter(_NullSafe::isPresent)
.collect(_CanFactory.toListWithSizeUpperBound(array.length));
return _CanFactory.ofNonNullElements(nonNullElements);
}
/**
* Returns either a {@code Can} with all the elements from given {@code collection}
* or an empty {@code Can} if the {@code collection} is {@code null}. Any elements
* equal to {@code null} are ignored and will not be contained in the resulting {@code Can}.
* @param
* @param collection
* @return non-null
*/
public static Can ofCollection(final @Nullable Collection collection) {
val inputSize = _NullSafe.size(collection);
if(inputSize==0) {
return empty();
}
val nonNullElements = collection.stream()
.filter(_NullSafe::isPresent)
.collect(_CanFactory.toListWithSizeUpperBound(inputSize));
return _CanFactory.ofNonNullElements(nonNullElements);
}
/**
* Returns either a {@code Can} with all the elements from given {@code iterable}
* or an empty {@code Can} if the {@code iterable} is {@code null}. Any elements
* equal to {@code null} are ignored and will not be contained in the resulting {@code Can}.
* @param
* @param iterable
* @return non-null
*/
public static Can ofIterable(final @Nullable Iterable iterable) {
if(iterable==null) {
return empty();
}
val nonNullElements = new ArrayList();
iterable.forEach(element->{
if(element!=null) {
nonNullElements.add(element);
}
});
return _CanFactory.ofNonNullElements(nonNullElements);
}
/**
* Returns either a {@code Can} with all the elements from given {@code enumeration}
* or an empty {@code Can} if the {@code enumeration} is {@code null}. Any elements
* equal to {@code null} are ignored and will not be contained in the resulting {@code Can}.
*
* As side-effect, consumes given {@code enumeration}.
* @param
* @param enumeration
* @return non-null
*/
public static Can ofEnumeration(final @Nullable Enumeration enumeration) {
if(enumeration==null) {
return empty();
}
val nonNullElements = new ArrayList();
while(enumeration.hasMoreElements()) {
val element = enumeration.nextElement();
if(element!=null) {
nonNullElements.add(element);
}
}
return _CanFactory.ofNonNullElements(nonNullElements);
}
/**
* Returns either a {@code Can} with all the elements from given {@code stream}
* or an empty {@code Can} if the {@code stream} is {@code null}. Any elements
* equal to {@code null} are ignored and will not be contained in the resulting {@code Can}.
*
* As side-effect, consumes given {@code stream}.
* @param
* @param stream
* @return non-null
*/
public static Can ofStream(final @Nullable Stream stream) {
if(stream==null) {
return empty();
}
val nonNullElements = stream
.filter(_NullSafe::isPresent)
.collect(Collectors.toCollection(()->new ArrayList<>()));
return _CanFactory.ofNonNullElements(nonNullElements);
}
// /**
// * Returns either a {@code Can} with all the elements from given {@code instance}
// * or an empty {@code Can} if the {@code instance} is {@code null}. Any elements
// * equal to {@code null} are ignored and will not be contained in the resulting {@code Can}.
// * @param
// * @param instance
// * @return non-null
// */
// public static Can ofInstance(final @Nullable Instance instance) {
// if(instance==null || instance.isUnsatisfied()) {
// return empty();
// }
// if(instance.isResolvable()) {
// return Can_Singleton.of(instance.get());
// }
// val nonNullElements = instance.stream()
// .collect(Collectors.toCollection(()->new ArrayList<>()));
//
// return Can_Multiple.of(nonNullElements);
//
// }
// -- OPERATORS
/**
* Returns a {@code Can} with all the elements from this {@code Can}, but
* sorted based on
* {@link Comparable#compareTo(Object)} order.
* @return non-null
*/
public Can sorted(Comparator super T> comparator);
/**
* Returns a {@code Can} with all the elements from this {@code Can}, but
* duplicated elements removed, based on
* {@link Object#equals(Object)} object equality.
* @return non-null
*/
public Can distinct();
/**
* Returns a {@code Can} with all the elements from this {@code Can}, but
* duplicated elements removed, based on given {@code equality} relation.
* @return non-null
*/
public Can distinct(@NonNull BiPredicate equality);
/**
* Returns a {@code Can} with all the elements from this {@code Can}, but
* contained in reversed order.
* @return non-null
*/
public Can reverse();
/**
* Returns a {@code Can} with all the elements from this {@code Can},
* that are accepted by the given {@code predicate}. If {@code predicate}
* is {@code null} all elements are accepted.
* @param predicate - if absent accepts all
* @return non-null
*/
public Can filter(@Nullable Predicate super T> predicate);
/**
* Returns a {@code Can} with all the elements from this {@code Can}
* 'transformed' by the given {@code mapper} function. Any resulting elements
* equal to {@code null} are ignored and will not be contained in the resulting {@code Can}.
*
* @param
* @param mapper - if absent throws if this {@code Can} is non-empty
* @return non-null
*/
default Can map(final @NonNull Function super T, R> mapper) {
if(isEmpty()) {
return empty();
}
val nonNullMappedElements =
stream()
.map(mapper)
.filter(_NullSafe::isPresent)
.collect(_CanFactory.toListWithSizeUpperBound(size()));
return _CanFactory.ofNonNullElements(nonNullMappedElements);
}
default Can flatMap(final @NonNull Function super T, ? extends Can extends R>> mapper) {
if(isEmpty()) {
return empty();
}
return
stream()
.map(mapper)
.filter(_NullSafe::isPresent)
.flatMap(Can::stream)
.collect(Can.toCan());
}
// -- CONCATENATION
/**
* Returns a {@code Can} with all the elements from given {@code can} joined by
* the given {@code element}. If any of given {@code can} or {@code element} are {@code null}
* these do not contribute any elements and are ignored.
* @param
* @param can - nullable
* @param element - nullable
* @return non-null
*/
public static Can concat(final @Nullable Can can, final @Nullable T element) {
if(can==null || can.isEmpty()) {
return ofNullable(element);
}
if(element==null) {
return can;
}
// at this point: can is not empty and variant is not null
val newSize = can.size() + 1;
val union = can.stream().collect(Collectors.toCollection(()->new ArrayList<>(newSize)));
union.add(element);
return Can_Multiple.of(union);
}
// -- TRAVERSAL
/**
* Returns an iterator that skips the first {@code skip} elements, then returns a
* maximum of {@code limit} elements.
* @param skip - similar to {@link Stream#skip(long)}
* @param limit - similar to {@link Stream#limit(long)}
*/
Iterator iterator(int skip, int limit);
Iterator reverseIterator();
@Override
void forEach(@NonNull Consumer super T> action);
/**
* Similar to {@link #forEach(Consumer)}, but zipps in {@code zippedIn} to iterate through
* its elements and passes them over as the second argument to the {@code action}.
* @param
* @param zippedIn must have at least as much elements as this {@code Can}
* @param action
* @throws NoSuchElementException if {@code zippedIn} overflows
*/
void zip(Iterable zippedIn, BiConsumer super T, ? super R> action);
/**
* Similar to {@link #map(Function)}, but zipps in {@code zippedIn} to iterate through
* its elements and passes them over as the second argument to the {@code mapper}.
* @param
* @param
* @param zippedIn must have at least as much elements as this {@code Can}
* @param mapper
* @throws NoSuchElementException if {@code zippedIn} overflows
*/
Can zipMap(Iterable zippedIn, BiFunction super T, ? super Z, R> mapper);
// -- MANIPULATION
Can add(@Nullable T element);
/**
* Adds the specified element to the list if it is not already present.
* @param element
* @return same or new instance
*/
default Can addUnique(final @Nullable T element) {
if(contains(element)) {
return this;
}
return add(element);
}
Can addAll(@Nullable Can other);
/**
* Inserts the specified element at the specified position in this list
* (optional operation). Shifts the element currently at that position
* (if any) and any subsequent elements to the right (adds one to their
* indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @return new instance
* @throws ClassCastException if the class of the specified element
* prevents it from being added to this list
* @throws NullPointerException if the specified element is null
* @throws IllegalArgumentException if some property of the specified
* element prevents it from being added to this list
* @throws IndexOutOfBoundsException if the index is out of range
* (index < 0 || index > size())
*/
Can add(int index, @Nullable T element);
Can replace(int index, @Nullable T element);
/**
* Removes the element at the specified position in this list (optional
* operation). Shifts any subsequent elements to the left (subtracts one
* from their indices). Returns the element that was removed from the
* list.
*
* @param index the index of the element to be removed
* @return new instance
* @throws IndexOutOfBoundsException if the index is out of range
* (index < 0 || index >= size())
*/
Can remove(int index);
Can remove(@Nullable T element);
/**
* Given n indices, returns an equivalent of
*
* Can.of(
* this.get(indices[0]).orElse(null),
* this.get(indices[1]).orElse(null),
* ...
* this.get(indices[n-1]).orElse(null)
* )
*
* (where nulls are being ignored)
* @param indices - null-able
*/
Can pickByIndex(@Nullable int ...indices);
// -- SEARCH
/**
* Returns the index of the first occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
* More formally, returns the lowest index i such that
* (o==null ? get(i)==null : o.equals(get(i))),
* or -1 if there is no such index.
*
* @param element element to search for
* @return the index of the first occurrence of the specified element in
* this list, or -1 if this list does not contain the element
* @throws ClassCastException if the type of the specified element
* is incompatible with this list
* (optional)
*/
int indexOf(@Nullable T element);
// -- EQUALITY
/**
* @param other
* @return whether this is element-wise equal to {@code other}
*/
default boolean isEqualTo(final @Nullable Can> other) {
if(other==null) {
return false;
}
if(this.size()!=other.size()) {
return false;
}
val otherIterator = other.iterator();
for(T element: this) {
val otherElement = otherIterator.next();
if(!element.equals(otherElement)) {
return false;
}
}
return true;
}
// -- PARTIAL EQUALITY
/**
* Let {@literal n} be the number of elements in {@code other}.
* Returns whether the first {@literal n} elements of this {@code Can} are
* element-wise equal to {@code other}.
* @param other
*/
default boolean startsWith(final @Nullable Can> other) {
if(other==null
|| other.isEmpty()) {
return true;
}
if(this.size() other) {
if(other==null
|| other.isEmpty()) {
return true;
}
if(this.size()
Collector> toCan() {
return Collectors.collectingAndThen(
Collectors.toList(),
Can::ofCollection);
}
// -- CONVERSIONS
/**
* @return a serializable and immutable {@link List}, containing the elements of this Can
*/
List toList();
/**
* @return a (serializable and mutable) {@link ArrayList}, containing the elements of this Can
*/
List toArrayList();
/**
* @return a serializable and immutable {@link Set}, containing the elements of this Can
*/
Set toSet();
/**
* @return a serializable and immutable {@link Set}, containing the elements of this Can
*/
Set toSet(@NonNull Consumer onDuplicated);
//XXX to implement when needed
// Set toSortedSet();
// Set toSortedSet(Comparator comparator);
/**
* @param
* @param collectionFactory
* @return a collection, containing the elements of this Can
*/
> C toCollection(Supplier collectionFactory);
/**
* @param a the array into which the elements of this Can are to
* be stored, if it is big enough; otherwise, a new array of the
* same runtime type is allocated for this purpose.
* @return a non-null array, containing the elements of this Can
*/
default T[] toArray(final T[] a) {
return toList().toArray(a);
}
/**
* @param elementType the {@code Class} object representing the component
* type of the new array
* @return a non-null array, containing the elements of this Can
*/
T[] toArray(Class elementType);
}