UnmodifiableCollection.java
/*
* Copyright (C) 2018 Stein Eldar Johnsen
*
* Licensed 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 net.morimekta.collect;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import static java.util.Objects.requireNonNull;
import static net.morimekta.collect.UnmodifiableList.listOf;
/**
* Simple unmodifiable collection base class. Note that these are called
* 'unmodifiable', not 'immutable', as whether they are truly immutable
* relies on the containing items being immutable classes too. But in
* that case it <b>is</b> immutable.
*
* @param <E> The element type of the collection.
*/
public abstract class UnmodifiableCollection<E> implements Collection<E> {
/**
* Get the collection as an unmodifiable collection. This will only instantiate
* if the incoming collection is not unmodifiable. Convenience mostly to be able
* to access methods defined here.
*
* @param collection The incoming collection.
* @param <E> The collection item type.
* @return The unmodifiable collection.
*/
public static <E> UnmodifiableCollection<E> unmodifiable(Collection<E> collection) {
if (collection instanceof UnmodifiableCollection) {
return (UnmodifiableCollection<E>) collection;
}
return UnmodifiableList.asList(collection);
}
/**
* Get the collection as an unmodifiable random access list.
*
* @return The unmodifiable list.
*/
public UnmodifiableList<E> asList() {
if (asList == null) {
if (length == 0) {
asList = listOf();
} else {
asList = new UnmodifiableList<>(offset, length, array);
}
}
return asList;
}
/**
* Return a list with the local content appended with the content of the
* provided collection.
*
* @param toBeAppended Collection content to be appended.
* @return The resulting list.
*/
public UnmodifiableList<E> append(Collection<? extends E> toBeAppended) {
if (toBeAppended.isEmpty()) {
return asList();
}
if (isEmpty()) {
return UnmodifiableList.asList(toBeAppended);
}
Object[] other = toBeAppended.toArray();
Object[] both = new Object[length + other.length];
System.arraycopy(array, offset, both, 0, length);
System.arraycopy(other, 0, both, length, other.length);
return new UnmodifiableList<>(both);
}
/**
* Return a list with the local content appended with the provided
* items.
*
* @param toBeAppended Content items to be appended.
* @return The resulting list.
*/
public UnmodifiableList<E> append(E... toBeAppended) {
checkNotNull(toBeAppended);
if (toBeAppended.length == 0) {
return asList();
}
if (isEmpty()) {
return UnmodifiableList.asList(toBeAppended);
}
Object[] both = new Object[length + toBeAppended.length];
System.arraycopy(array, offset, both, 0, length);
System.arraycopy(toBeAppended, 0, both, length, toBeAppended.length);
return new UnmodifiableList<>(both);
}
/**
* Return a list with the local content appended to the content of the
* provided collection.
*
* @param item Entry content to be appended.
* @return The resulting list.
*/
public UnmodifiableList<E> append(E item) {
requireNonNull(item, "item == null");
if (isEmpty()) {
return listOf(item);
}
Object[] both = new Object[length + 1];
System.arraycopy(array, offset, both, 0, length);
both[length] = item;
return new UnmodifiableList<>(both);
}
/**
* Map all values in the collection to some other type.
*
* @param mapper The mapper function to use.
* @param <M> The mapped to type.
* @return The list of mapped items.
*/
public <M> UnmodifiableList<M> map(Function<? super E, M> mapper) {
if (isEmpty()) {
return listOf();
}
UnmodifiableList.Builder<M> builder = UnmodifiableList.newBuilder(size());
for (E value : this) {
builder.add(mapper.apply(value));
}
return builder.build();
}
/**
* Filter the collection, but keep the same type and sorting / ordering.
*
* @param predicate The predicate to filter by.
* @return The filtered collection.
*/
public UnmodifiableCollection<E> filtered(Predicate<? super E> predicate) {
requireNonNull(predicate, "predicate == null");
if (isEmpty()) {
return this;
}
Object[] out = new Object[length];
int outLength = 0;
for (E value : this) {
if (predicate.test(value)) {
out[outLength++] = value;
}
}
if (outLength == length) {
return this;
}
return newInstance(out, outLength);
}
/**
* Make a list with the same items in the reversed order.
*
* @return The reversed list.
*/
public abstract UnmodifiableCollection<E> reversed();
/**
* Get the content of the collection sorted using natural order.
*
* @return The sorted list.
*/
@SuppressWarnings("unchecked")
public UnmodifiableList<E> sorted() {
if (length == 0) {
return listOf();
}
if (this instanceof UnmodifiableList && length == 1) {
return (UnmodifiableList<E>) this;
}
E[] tmp = (E[]) toArray();
Arrays.sort(tmp);
return new UnmodifiableList<>(tmp);
}
/**
* Get the content of the collection sorted by the given comparator.
*
* @param comparator Comparator to sort by. If null will sort using natural order.
* @return The sorted list.
*/
@SuppressWarnings("unchecked")
public UnmodifiableList<E> sortedBy(Comparator<? super E> comparator) {
if (length == 0) {
return listOf();
}
if (this instanceof UnmodifiableList && length == 1) {
return (UnmodifiableList<E>) this;
}
E[] tmp = (E[]) toArray();
Arrays.sort(tmp, comparator);
return new UnmodifiableList<>(tmp);
}
/**
* Get the content of the collection as a set ordered by the given comparator.
*
* @param comparator Comparator to sort by. If null will sort using natural order.
* @return The ordered set.
*/
@SuppressWarnings("unchecked")
public UnmodifiableSortedSet<E> orderedSetBy(Comparator<? super E> comparator) {
return UnmodifiableSortedSet.construct((Comparator<Object>) comparator, toArray());
}
// -------- Collection --------
@Override
public Object[] toArray() {
Object[] out = new Object[length];
if (length > 0) {
System.arraycopy(array, offset, out, 0, length);
}
return out;
}
@Override
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] ts) {
Object[] out = (Object[]) Array.newInstance(ts.getClass().getComponentType(), length);
if (length > 0) {
System.arraycopy(array, offset, out, 0, length);
}
return (T[]) out;
}
@Override
public <T> T[] toArray(IntFunction<T[]> generator) {
T[] out = generator.apply(length);
if (length > 0) {
System.arraycopy(array, offset, out, 0, length);
}
return out;
}
@Override
public int size() {
return length;
}
@Override
public boolean isEmpty() {
return length == 0;
}
@Override
public boolean contains(Object o) {
if (o == null) {
return false;
}
for (int i = offset; i < (offset + length); ++i) {
if (array[i].equals(o)) {
return true;
}
}
return false;
}
@Override
public Iterator<E> iterator() {
return new IteratorImpl(offset);
}
@Override
public boolean containsAll(Collection<?> collection) {
if (isEmpty() && !collection.isEmpty()) {
return false;
}
for (Object o : collection) {
if (!contains(o)) {
return false;
}
}
return true;
}
// -------- Private --------
static final Object[] EMPTY_ARRAY = new Object[0];
transient final Object[] array;
transient final int length;
transient final int offset;
transient Integer hashCode;
private transient UnmodifiableList<E> asList;
UnmodifiableCollection(int offset, int length, Object[] array) {
if (offset < 0 || length < 0) {
throw new IllegalArgumentException(String.format("offset: %d length: %s", offset, length));
}
if (offset + length > array.length) {
throw new IllegalArgumentException(
String.format("offset: %d + length: %d > array: %d",
offset,
length,
array.length));
}
this.offset = offset;
this.length = length;
this.array = array;
}
abstract UnmodifiableCollection<E> newInstance(Object[] array, int length);
Object[] reversedArray() {
if (array.length < 2 && length == array.length && offset == 0) {
return array;
}
Object[] out = new Object[length];
for (int i = 0, o = offset + length - 1; i < length; ++i, --o) {
out[i] = this.array[o];
}
return out;
}
// -------- ITERATOR --------
class IteratorImpl implements ListIterator<E> {
private int next;
IteratorImpl(int index) {
this.next = index;
}
@Override
public boolean hasNext() {
return next < (offset + length);
}
@Override
@SuppressWarnings("unchecked")
public E next() {
if (next >= (offset + length)) {
throw new IndexOutOfBoundsException("Index " + (next - offset) + " out of bounds for length " + length);
}
++next;
return (E) array[next - 1];
}
@Override
public boolean hasPrevious() {
return (next - offset) > 0;
}
@Override
@SuppressWarnings("unchecked")
public E previous() {
if ((next - offset - 1) < 0) {
throw new IndexOutOfBoundsException("Index -1 out of bounds for length " + length);
}
--next;
return (E) array[next];
}
@Override
public int nextIndex() {
return next - offset;
}
@Override
public int previousIndex() {
return next - offset - 1;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Operation not allowed");
}
@Override
public void set(E e) {
throw new UnsupportedOperationException("Operation not allowed");
}
@Override
public void add(E e) {
throw new UnsupportedOperationException("Operation not allowed");
}
}
// -------- UTILITIES --------
static void checkNotNull(Object[] array) {
if (array == null) {
throw new NullPointerException("null array");
}
for (int i = 0; i < array.length; ++i) {
if (array[i] == null) {
throw new NullPointerException("null item at pos " + i);
}
}
}
static void checkNotNull(Collection<?> collection) {
if (collection == null) {
throw new NullPointerException("null collection");
}
int i = 0;
for (Object o : collection) {
if (o == null) {
throw new NullPointerException("null item at pos " + i);
}
++i;
}
}
// -------- UNSUPPORTED --------
@Override
public boolean add(E e) {
throw new UnsupportedOperationException("Operation not allowed");
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException("Operation not allowed");
}
@Override
public boolean addAll(Collection<? extends E> collection) {
throw new UnsupportedOperationException("Operation not allowed");
}
@Override
public boolean removeAll(Collection<?> collection) {
throw new UnsupportedOperationException("Operation not allowed");
}
@Override
public boolean removeIf(Predicate<? super E> filter) {
throw new UnsupportedOperationException("Operation not allowed");
}
@Override
public boolean retainAll(Collection<?> collection) {
throw new UnsupportedOperationException("Operation not allowed");
}
@Override
public void clear() {
throw new UnsupportedOperationException("Operation not allowed");
}
}