blob: 00fd659d8eb9e915fec008911d9391f4991740c5 [file] [log] [blame]
/*
* 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.sis.internal.util;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.Objects;
import java.util.RandomAccess;
import java.lang.reflect.Array;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.collection.CheckedContainer;
/**
* An unmodifiable view of an array. Invoking
*
* {@preformat java
* List<?> list = UnmodifiableArrayList.wrap(array);
* }
*
* is equivalent to
*
* {@preformat java
* List<?> list = Collections.unmodifiableList(Arrays.asList(array));
* }
*
* except that this class uses one less level of indirection (which may be significant since
* unmodifiable lists are extensively used in SIS) and implements the {@link CheckedContainer}
* interface.
*
* <div class="section">WARNING! Type safety hole</div>
* The {@link #getElementType()} return type is {@code Class<E>}, but its implementation actually
* returns {@code Class<? extends E>}. This contract violation is possible because Java arrays are
* covariant (at the contrary of collections). In order to avoid such contract violation, callers
* <strong>must</strong> ensure that the type of array elements in exactly {@code E}, not a subtype
* of {@code E}. This class has no way to verify that condition. This class is not in the public API
* for this reason.
*
* <p>Note that the public API, {@link org.apache.sis.util.collection.Containers#unmodifiableList(Object[])},
* returns {@code List<? extends E>}, which is okay.</p>
*
* @author Martin Desruisseaux (IRD)
* @version 0.3
*
* @param <E> the type of elements in the list.
*
* @since 0.3
* @module
*/
public class UnmodifiableArrayList<E> extends AbstractList<E> implements RandomAccess, CheckedContainer<E>, Serializable {
/**
* For compatibility with different versions.
*/
private static final long serialVersionUID = 13882164775184042L;
/**
* The wrapped array.
*/
final E[] array;
/**
* Creates a new instance wrapping the given array. A direct reference to the given array is
* retained (i.e. the array is <strong>not</strong> cloned). Consequently the given array
* shall not be modified after construction if this list is intended to be immutable.
*
* <p>This constructor is for sub-classing only. Users should invoke the {@link #wrap(Object[])}
* static method instead.</p>
*
* <p>The argument type is intentionally {@code E[]} instead than {@code E...} in order to force
* the caller to instantiate the array explicitly, in order to make sure that the array type is
* the intended one.</p>
*
* <div class="section">WARNING! Type safety hole</div>
* Callers <strong>must</strong> ensure that the type of array elements in exactly {@code E},
* not a subtype of {@code E}. See class javadoc for more information.
*
* @param array the array to wrap.
*/
protected UnmodifiableArrayList(final E[] array) { // NOT "E..." - see javadoc.
this.array = Objects.requireNonNull(array);
}
/**
* Creates a new instance wrapping the given array. A direct reference to the given array is
* retained (i.e. the array is <strong>not</strong> cloned). Consequently the given array
* shall not be modified after construction if the returned list is intended to be immutable.
*
* <div class="section">WARNING! Type safety hole</div>
* Callers <strong>must</strong> ensure that the type of array elements in exactly {@code E},
* not a subtype of {@code E}. If the caller is okay with {@code List<? extends E>}, then (s)he
* should use {@link org.apache.sis.util.collection.Containers#unmodifiableList(Object[])} instead.
* See class javadoc for more information.
*
* <p>The argument type is intentionally {@code E[]} instead than {@code E...} in order to force
* the caller to instantiate the array explicitly, in order to make sure that the array type is
* the intended one.</p>
*
* @param <E> the type of elements in the list.
* @param array the array to wrap, or {@code null} if none.
* @return the given array wrapped in an unmodifiable list, or {@code null} if the given array was null.
*/
public static <E> UnmodifiableArrayList<E> wrap(final E[] array) { // NOT "E..." - see javadoc.
return (array != null) ? new UnmodifiableArrayList<>(array) : null;
}
/**
* Creates a new instance wrapping a subregion of the given array. A direct reference to the
* given array is retained (i.e. the array is <strong>not</strong> cloned). Consequently the
* specified sub-region of the given array shall not be modified after construction if the
* returned list is intended to be immutable.
*
* <p>This method does not check the validity of the given index.
* The check must be done by the caller.</p>
*
* <div class="section">WARNING! Type safety hole</div>
* Callers <strong>must</strong> ensure that the type of array elements in exactly {@code E},
* not a subtype of {@code E}. If the caller is okay with {@code List<? extends E>}, then (s)he
* should use {@link org.apache.sis.util.collection.Containers#unmodifiableList(Object[])} instead.
* See class javadoc for more information.
*
* @param <E> the type of elements in the list.
* @param array the array to wrap.
* @param lower low endpoint (inclusive) of the sublist.
* @param upper high endpoint (exclusive) of the sublist.
* @return the given array wrapped in an unmodifiable list.
*/
public static <E> UnmodifiableArrayList<E> wrap(final E[] array, final int lower, final int upper) {
if (lower == 0 && upper == array.length) {
return new UnmodifiableArrayList<>(array);
}
return new UnmodifiableArrayList.SubList<>(array, lower, upper - lower);
}
/**
* Returns the element type of the wrapped array. The default implementation returns
* <code>array.getClass().{@linkplain Class#getComponentType() getComponentType()}</code>.
*
* @return the type of elements in the list.
*/
@Override
public Class<E> getElementType() {
// No @SuppressWarnings because this cast is really unsafe. See class javadoc.
return (Class<E>) array.getClass().getComponentType();
}
/**
* Returns the index of the first valid element.
* To be overridden by {@link SubList} only.
*/
int lower() {
return 0;
}
/**
* Returns the list size.
*
* @return the size of this list.
*/
@Override
public int size() {
return array.length;
}
/**
* Returns the size of the array backing this list. This is the length of the array
* given to the constructor. It is equal to {@link #size()} except if this instance
* is a {@linkplain #subList(int,int) sublist}, in which case the value returned by
* this method is greater than {@code size()}.
*
* <p>This method can be used as a hint for choosing a {@code UnmodifiableArrayList}
* instance to keep when there is a choice between many equal instances. Note that a
* greater value is not necessarily more memory consuming, since the backing array
* may be shared by many sublists.</p>
*
* @return the length of the backing array.
*/
public final int arraySize() {
return array.length;
}
/**
* Returns the element at the specified index.
*
* @param index the index of the element to get.
* @return the element at the given index.
*/
@Override
public E get(final int index) {
return array[index];
}
/**
* Returns the index in this list of the first occurrence of the specified element,
* or -1 if the list does not contain the element.
*
* @param object the element to search for.
* @return the index of the first occurrence of the given object, or {@code -1}.
*/
@Override
public int indexOf(final Object object) {
final int lower = lower();
final int upper = lower + size();
if (object == null) {
for (int i=lower; i<upper; i++) {
if (array[i] == null) {
return i - lower;
}
}
} else {
for (int i=lower; i<upper; i++) {
if (object.equals(array[i])) {
return i - lower;
}
}
}
return -1;
}
/**
* Returns the index in this list of the last occurrence of the specified element,
* or -1 if the list does not contain the element.
*
* @param object the element to search for.
* @return the index of the last occurrence of the given object, or {@code -1}.
*/
@Override
public int lastIndexOf(final Object object) {
final int lower = lower();
int i = lower + size();
if (object == null) {
while (--i >= lower) {
if (array[i] == null) {
break;
}
}
} else {
while (--i >= lower) {
if (object.equals(array[i])) {
break;
}
}
}
return i - lower;
}
/**
* Returns {@code true} if this list contains the specified element.
*
* @param object the element to check for existence.
* @return {@code true} if this collection contains the given element.
*/
@Override
public boolean contains(final Object object) {
final int lower = lower();
int i = lower + size();
if (object == null) {
while (--i >= lower) {
if (array[i] == null) {
return true;
}
}
} else {
while (--i >= lower) {
if (object.equals(array[i])) {
return true;
}
}
}
return false;
}
/**
* Returns a view of the portion of this list between the specified
* {@code lower}, inclusive, and {@code upper}, exclusive.
*
* @param lower low endpoint (inclusive) of the sublist.
* @param upper high endpoint (exclusive) of the sublist.
* @return a view of the specified range within this list.
* @throws IndexOutOfBoundsException if the lower or upper value are out of bounds.
*
* @see #wrap(Object[], int, int)
*/
@Override
public UnmodifiableArrayList<E> subList(final int lower, final int upper)
throws IndexOutOfBoundsException
{
ArgumentChecks.ensureValidIndexRange(size(), lower, upper);
return new SubList<>(array, lower + lower(), upper - lower);
}
/**
* A view over a portion of {@link UnmodifiableArrayList}.
*
* @param <E> the type of elements in the list.
*/
private static final class SubList<E> extends UnmodifiableArrayList<E> {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = 33065197642139916L;
/**
* Index of the first element and size of this list.
*/
private final int lower, size;
/**
* Creates a new sublist.
*
* <p><b>WARNING! Type safety hole</b></p>
* Callers <strong>must</strong> ensure that the type of array elements in exactly {@code E},
* not a subtype of {@code E}. See {@link UnmodifiableArrayList} class javadoc for more information.
*/
SubList(final E[] array, final int lower, final int size) {
super(array);
this.lower = lower;
this.size = size;
}
/**
* Returns the index of the first element.
*/
@Override
int lower() {
return lower;
}
/**
* Returns the size of this list.
*/
@Override
public int size() {
return size;
}
/**
* Returns the element at the given index.
*/
@Override
public E get(final int index) {
ArgumentChecks.ensureValidIndex(size, index);
return super.get(index + lower);
}
/**
* Returns a copy of the backing array section viewed by this sublist.
*/
@Override
public E[] toArray() {
return Arrays.copyOfRange(array, lower, lower + size);
}
}
/**
* Returns a copy of the backing array. Note that the array type is {@code E[]} rather than {@code Object[]}.
* This is not what {@code ArrayList} does, but is not forbidden by {@link java.util.List#toArray()} javadoc
* neither.
*
* @return a copy of the wrapped array.
*/
@Override
public E[] toArray() {
return array.clone();
}
/**
* Copies the backing array in the given one if the list fits in the given array.
* If the list does not fit in the given array, returns the collection in a new array.
*
* @param <T> the type of array element.
* @param dest the array where to copy the elements if the list can fits in the array.
* @return the given array, or a newly created array if this list is larger than the given array.
*/
@Override
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] dest) {
final int size = size();
if (dest.length != size) {
if (dest.length < size) {
/*
* We are cheating here since the array component may not be assignable to T.
* But if this assumption is wrong, then the call to System.arraycopy(…) later
* will thrown an ArrayStoreException, which is the exception type required by
* the Collection.toArray(T[]) javadoc.
*/
dest = (T[]) Array.newInstance(dest.getClass().getComponentType(), size);
} else {
dest[size] = null; // Required by Collection.toArray(T[]) javadoc.
}
}
System.arraycopy(array, lower(), dest, 0, size);
return dest;
}
/**
* Compares this list with the given object for equality.
*
* @param object the object to compare with this list.
* @return {@code true} if the given object is equal to this list.
*/
@Override
public boolean equals(final Object object) {
if (object != this) {
if (!(object instanceof UnmodifiableArrayList<?>)) {
return super.equals(object);
}
final UnmodifiableArrayList<?> that = (UnmodifiableArrayList<?>) object;
int size = this.size();
if (size != that.size()) {
return false;
}
int i = this.lower();
int j = that.lower();
while (--size >= 0) {
if (!Objects.equals(this.array[i++], that.array[j++])) {
return false;
}
}
}
return true;
}
}