UnmodifiableList.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 net.morimekta.collect.collectors.UnmodifiableListCollector;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;
import java.util.RandomAccess;
import java.util.function.Predicate;
import java.util.stream.Collector;
import static java.lang.Math.max;
import static java.util.Objects.requireNonNull;
/**
* Simple unmodifiable list implementation backed by an object array.
*
* @param <E> The list item type.
*/
public final class UnmodifiableList<E> extends UnmodifiableCollection<E> implements List<E>, RandomAccess {
// -------- Collectors --------
/**
* @param <E> The list item type.
* @return Collector for making an unmodifiable list.
*/
public static <E> Collector<E, ?, UnmodifiableList<E>> toList() {
return new UnmodifiableListCollector<>();
}
// -------- Constructors --------
/**
* @param elements Elements to make unmodifiable list of.
* @param <E> The list item type.
* @return The unmodifiable list.
*/
@SafeVarargs
public static <E> UnmodifiableList<E> asList(E... elements) {
checkNotNull(elements);
if (elements.length == 0) {
return listOf();
}
return new UnmodifiableList<>(Arrays.copyOf(elements, elements.length));
}
/**
* @param iterable Iterable to make unmodifiable list of.
* @param <E> The list item type.
* @return The unmodifiable list.
*/
@SuppressWarnings("unchecked")
public static <E> UnmodifiableList<E> asList(Iterable<? extends E> iterable) {
requireNonNull(iterable, "iterable == null");
if (iterable instanceof UnmodifiableCollection) {
return ((UnmodifiableCollection<E>) iterable).asList();
} else if (iterable instanceof Collection) {
return asList((Collection<E>) iterable);
}
return asList(iterable.iterator());
}
/**
* @param iterator Iterator to make unmodifiable list of.
* @param <E> The list item type.
* @return The unmodifiable list.
*/
public static <E> UnmodifiableList<E> asList(Iterator<? extends E> iterator) {
requireNonNull(iterator, "iterator == null");
if (!iterator.hasNext()) {
return listOf();
}
return UnmodifiableList.<E>newBuilder().addAll(iterator).build();
}
/**
* @param enumeration Enumeration to make unmodifiable list of.
* @param <E> The list item type.
* @return The unmodifiable list.
*/
public static <E> UnmodifiableList<E> asList(Enumeration<? extends E> enumeration) {
requireNonNull(enumeration, "enumeration == null");
if (!enumeration.hasMoreElements()) {
return listOf();
}
return UnmodifiableList.<E>newBuilder().addAll(enumeration).build();
}
/**
* @param collection Collection to make unmodifiable list of.
* @param <E> The list item type.
* @return The unmodifiable list.
*/
@SuppressWarnings("unchecked")
public static <E> UnmodifiableList<E> asList(Collection<? extends E> collection) {
checkNotNull(collection);
if (collection.size() == 0) {
return listOf();
}
if (collection instanceof UnmodifiableCollection) {
return ((UnmodifiableCollection<E>) collection).asList();
}
return construct((E[]) collection.toArray());
}
/**
* @param <E> The list item type.
* @return The unmodifiable empty list.
*/
@SuppressWarnings("unchecked")
public static <E> UnmodifiableList<E> listOf() {
return (UnmodifiableList<E>) EMPTY;
}
/**
* @param e The list item.
* @param <E> The list item type.
* @return The unmodifiable singleton list.
*/
public static <E> UnmodifiableList<E> listOf(E e) {
return construct(e);
}
/**
* @param e1 The first list item.
* @param e2 The second list item.
* @param <E> The list item type.
* @return The unmodifiable list.
*/
public static <E> UnmodifiableList<E> listOf(E e1, E e2) {
return construct(e1, e2);
}
/**
* @param e1 The first list item.
* @param e2 The second list item.
* @param e3 The third list item.
* @param <E> The list item type.
* @return The unmodifiable list.
*/
public static <E> UnmodifiableList<E> listOf(E e1, E e2, E e3) {
return construct(e1, e2, e3);
}
/**
* @param e1 The first list item.
* @param e2 The second list item.
* @param e3 The third list item.
* @param e4 The fourth list item.
* @param <E> The list item type.
* @return The unmodifiable list.
*/
public static <E> UnmodifiableList<E> listOf(E e1, E e2, E e3, E e4) {
return construct(e1, e2, e3, e4);
}
/**
* @param e1 The first list item.
* @param e2 The second list item.
* @param e3 The third list item.
* @param e4 The fourth list item.
* @param e5 The fifth list item.
* @param <E> The list item type.
* @return The unmodifiable list.
*/
public static <E> UnmodifiableList<E> listOf(E e1, E e2, E e3, E e4, E e5) {
return construct(e1, e2, e3, e4, e5);
}
/**
* @param e1 The first list item.
* @param e2 The second list item.
* @param e3 The third list item.
* @param e4 The fourth list item.
* @param e5 The fifth list item.
* @param e6 The sixth list item.
* @param <E> The list item type.
* @return The unmodifiable list.
*/
public static <E> UnmodifiableList<E> listOf(E e1, E e2, E e3, E e4, E e5, E e6) {
return construct(e1, e2, e3, e4, e5, e6);
}
/**
* @param e1 The first list item.
* @param e2 The second list item.
* @param e3 The third list item.
* @param e4 The fourth list item.
* @param e5 The fifth list item.
* @param e6 The sixth list item.
* @param e7 The seventh list item.
* @param <E> The list item type.
* @return The unmodifiable list.
*/
public static <E> UnmodifiableList<E> listOf(E e1, E e2, E e3, E e4, E e5, E e6, E e7) {
return construct(e1, e2, e3, e4, e5, e6, e7);
}
/**
* @param e1 The first list item.
* @param e2 The second list item.
* @param e3 The third list item.
* @param e4 The fourth list item.
* @param e5 The fifth list item.
* @param e6 The sixth list item.
* @param e7 The seventh list item.
* @param e8 The eighth list item.
* @param <E> The list item type.
* @return The unmodifiable list.
*/
public static <E> UnmodifiableList<E> listOf(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8) {
return construct(e1, e2, e3, e4, e5, e6, e7, e8);
}
/**
* @param e1 The first list item.
* @param e2 The second list item.
* @param e3 The third list item.
* @param e4 The fourth list item.
* @param e5 The fifth list item.
* @param e6 The sixth list item.
* @param e7 The seventh list item.
* @param e8 The eighth list item.
* @param e9 The ninth list item.
* @param <E> The list item type.
* @return The unmodifiable list.
*/
public static <E> UnmodifiableList<E> listOf(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9) {
return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9);
}
/**
* @param e1 The first list item.
* @param e2 The second list item.
* @param e3 The third list item.
* @param e4 The fourth list item.
* @param e5 The fifth list item.
* @param e6 The sixth list item.
* @param e7 The seventh list item.
* @param e8 The eighth list item.
* @param e9 The ninth list item.
* @param e10 The tenth list item.
* @param more Most list items.
* @param <E> The list item type.
* @return The unmodifiable list.
*/
@SafeVarargs
public static <E> UnmodifiableList<E> listOf(
E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10, E... more) {
Object[] array = new Object[10 + more.length];
array[0] = e1;
array[1] = e2;
array[2] = e3;
array[3] = e4;
array[4] = e5;
array[5] = e6;
array[6] = e7;
array[7] = e8;
array[8] = e9;
array[9] = e10;
System.arraycopy(more, 0, array, 10, more.length);
checkNotNull(array);
return new UnmodifiableList<>(array);
}
/**
* @param <E> The list item type.
* @return Builder to make an unmodifiable list.
*/
public static <E> Builder<E> newBuilder() {
return new Builder<>(4);
}
/**
* @param initialCapacity Initial capacity of the builder.
* @param <E> The list item type.
* @return Builder to make an unmodifiable list.
*/
public static <E> Builder<E> newBuilder(int initialCapacity) {
return new Builder<>(max(1, initialCapacity));
}
// -------- Object --------
@Override
public int hashCode() {
if (hashCode == null) {
int hash = Objects.hash(getClass(), length);
for (int i = 0; i < length; ++i) {
hash ^= Objects.hash(i, array[offset + i]);
}
hashCode = hash;
}
return hashCode;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof List)) {
return false;
}
if (length != ((List<?>) obj).size()) {
return false;
}
if (obj instanceof UnmodifiableList) {
UnmodifiableList<?> other = (UnmodifiableList<?>) obj;
if (offset == other.offset && array == other.array) {
return true;
}
for (int i = 0; i < length; ++i) {
if (!array[offset + i].equals(other.array[other.offset + i])) {
return false;
}
}
return true;
} else if (obj instanceof RandomAccess) {
List<?> other = (List<?>) obj;
for (int i = 0; i < length; ++i) {
if (!array[offset + i].equals(other.get(i))) {
return false;
}
}
return true;
} else {
List<?> other = (List<?>) obj;
int i = offset;
for (Object o : other) {
if (!array[i++].equals(o)) {
return false;
}
}
return true;
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("[");
for (int i = offset; i < (offset + length); ++i) {
if (i > offset) {
builder.append(", ");
}
builder.append(array[i].toString());
}
return builder.append("]").toString();
}
// -------- List --------
@Override
public boolean addAll(int i, Collection<? extends E> collection) {
throw new UnsupportedOperationException("Operation not allowed");
}
@Override
@SuppressWarnings("unchecked")
public E get(int i) {
return (E) array[offset + i];
}
@Override
public E set(int i, E e) {
throw new UnsupportedOperationException("Operation not allowed");
}
@Override
public void add(int i, E e) {
throw new UnsupportedOperationException("Operation not allowed");
}
@Override
public E remove(int i) {
throw new UnsupportedOperationException("Operation not allowed");
}
@Override
public int indexOf(Object item) {
requireNonNull(item, "item == null");
for (int i = offset; i < (offset + length); ++i) {
if (item.equals(array[i])) {
return i - offset;
}
}
return -1;
}
@Override
public int lastIndexOf(Object item) {
requireNonNull(item, "item == null");
for (int i = (offset + length) - 1; i >= offset; --i) {
if (item.equals(array[i])) {
return i - offset;
}
}
return -1;
}
@Override
public ListIterator<E> listIterator() {
return new IteratorImpl(0);
}
@Override
public ListIterator<E> listIterator(int i) {
if (i < 0 || i > length) {
throw new IndexOutOfBoundsException("ListIterator index " + i + " out of bounds for length " + array.length);
}
return new IteratorImpl(i);
}
@Override
public List<E> subList(int start, int end) {
if (start == 0 && end == length) {
return this;
}
if (start < 0 || start > length) {
throw new IndexOutOfBoundsException("Start index " + start + " out of bounds for length " + length);
}
if (end < start || end > length) {
throw new IndexOutOfBoundsException("End index " + end + " out of bounds for length " + length + ", start " + start);
}
int len = end - start;
if (len == 0) {
return listOf();
}
return new UnmodifiableList<>(offset + start, len, array);
}
// -------- UnmodifiableCollection --------
@Override
public UnmodifiableList<E> asList() {
return this;
}
/**
* Get list with only the items matching the predicate.
*
* @param predicate The predicate to keep item in response.
* @return The filtered list.
*/
@Override
public UnmodifiableList<E> filtered(Predicate<? super E> predicate) {
return (UnmodifiableList<E>) super.filtered(predicate);
}
@Override
public UnmodifiableList<E> reversed() {
if (length < 2) {
return this;
}
return new UnmodifiableList<>(reversedArray());
}
// -------- Builder --------
/**
* Builder to make an unmodifiable list.
*
* @param <E> The list item type.
*/
public final static class Builder<E>
implements ListBuilder<E>, UnmodifiableCollectionBuilder<E, UnmodifiableList<E>, Builder<E>> {
private Object[] array;
private int size;
Builder(int initialCapacity) {
this.array = new Object[initialCapacity];
this.size = 0;
}
@Override
public Builder<E> add(E e) {
requireNonNull(e, "null item");
ensureCapacity(size + 1);
array[size++] = e;
return this;
}
@Override
public Builder<E> addAll(Collection<? extends E> collection) {
checkNotNull(collection);
if (collection.isEmpty()) {
return this;
}
ensureCapacity(size + collection.size());
for (Object o : collection) {
array[size++] = o;
}
return this;
}
@Override
@SafeVarargs
public final Builder<E> addAll(E... items) {
checkNotNull(items);
if (items.length == 0) {
return this;
}
ensureCapacity(size + items.length);
System.arraycopy(items, 0, array, size, items.length);
size += items.length;
return this;
}
@Override
public UnmodifiableList<E> build() {
if (size == 0) {
return listOf();
}
return new UnmodifiableList<>(0, size, array);
}
private void ensureCapacity(int capacity) {
if (array.length < capacity) {
Object[] old = array;
array = new Object[array.length * 2];
System.arraycopy(old, 0, array, 0, size);
}
}
}
// -------- Private --------
UnmodifiableList(Object[] array) {
super(0, array.length, array);
}
UnmodifiableList(int offset, int length, Object[] array) {
super(offset, length, array);
}
@Override
UnmodifiableList<E> newInstance(Object[] array, int length) {
if (length == 0) {
return listOf();
}
return new UnmodifiableList<>(0, length, array);
}
@SafeVarargs
static <E> UnmodifiableList<E> construct(E... array) {
checkNotNull(array);
return new UnmodifiableList<>(array);
}
static final UnmodifiableList<Object> EMPTY = new UnmodifiableList<>(new Object[0]);
}