/* * 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.geotoolkit.util.collection; import java.util.List; import java.util.Iterator; import java.util.ListIterator; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.NoSuchElementException; import org.apache.sis.util.resources.Errors; import org.apache.sis.util.collection.CheckedContainer; import static org.apache.sis.util.ArgumentChecks.ensureNonNull; /** * A {@linkplain Collections#checkedList(List, Class) checked} and * {@linkplain Collections#synchronizedList(List) synchronized} {@link ArrayList}. * The type checks are performed at run-time in addition to the compile-time checks. * * <p>Using this class is similar to wrapping an {@link ArrayList} using the methods provided * in the standard {@link Collections} class, except for the following advantages:</p> * * <ul> * <li>Avoid the two levels of indirection (for type check and synchronization).</li> * <li>Checks for write permission.</li> * <li>Overrideable methods for controlling the synchronization lock, * type checks and write permission checks.</li> * </ul> * * The synchronization is provided mostly in order to prevent damages * to the list in case of concurrent access. It does <strong>not</strong> prevent * {@link java.util.ConcurrentModificationException} to be thrown during iterations, * unless the whole iteration is synchronized on this list {@linkplain #getLock() lock}. * For real concurrency, see the {@link java.util.concurrent} package instead. * * {@note The above is the reason why the name of this class emphases the <cite>checked</cite> * aspect rather than the <cite>synchronized</cite> aspect of the list.} * * @param <E> The type of elements in the list. * * @author Martin Desruisseaux (Geomatys) * @since 2.1 * @version 3.22 * @module * * @see Collections#checkedList(List, Class) * @see Collections#synchronizedList(List) */ public class CheckedArrayList<E> extends ArrayList<E> implements CheckedContainer<E>, Cloneable { /** * Serial version UID for compatibility with different versions. */ private static final long serialVersionUID = -8265578982723471814L; /** * The element type. */ private final Class<E> type; /** * Constructs a list of the specified type. * * @param type The element type (can not be null). */ public CheckedArrayList(final Class<E> type) { super(); this.type = type; ensureNonNull("type", type); } /** * Constructs a list of the specified type and initial capacity. * * @param type The element type (should not be null). * @param capacity The initial capacity. */ public CheckedArrayList(final Class<E> type, final int capacity) { super(capacity); this.type = type; ensureNonNull("type", type); } /** * Returns the element type given at construction time. */ @Override public Class<E> getElementType() { return type; } /** * Ensures that the given element can be added to this list. * The default implementation ensures that the object is {@code null} or assignable * to the type specified at construction time. Subclasses can override this method * if they need to perform additional checks. * * {@section Synchronization} * This method is invoked <em>before</em> to get the synchronization {@linkplain #getLock() lock}. * This is different than the {@link #checkWritePermission()} method, which is invoked inside the * synchronized block. * * @param element the object to check, or {@code null}. * @throws IllegalArgumentException if the specified element can not be added to this list. */ protected void ensureValid(final E element) throws IllegalArgumentException { if (element != null && !type.isInstance(element)) { throw new IllegalArgumentException(Errors.format( Errors.Keys.IllegalArgumentClass_3, "element", type, element.getClass())); } } /** * Ensures that all elements of the given collection can be added to this list. * * @param collection the collection to check, or {@code null}. * @throws IllegalArgumentException if at least one element can not be added to this list. */ private void ensureValidCollection(final Collection<? extends E> collection) throws IllegalArgumentException { for (final E element : collection) { ensureValid(element); } } /** * Checks if changes in this list are allowed. This method is automatically invoked * after this list got the {@linkplain #getLock() lock} and before any operation that * may change the content. If the write operation is allowed, then this method shall * returns normally. Otherwise an {@link UnsupportedOperationException} is thrown. * * <p>The default implementation does nothing significant (see below), thus allowing this list to * be modified. Subclasses can override this method if they want to control write permissions.</p> * * {@note Actually the current implementation contains an <code>assert</code> statement * ensuring that the thread holds the lock. This is an implementation details that may * change in any future version of the SIS library. Nevertheless methods that override * this one are encouraged to invoke <code>super.checkWritePermission()</code>.} * * @throws UnsupportedOperationException if this list is unmodifiable. */ protected void checkWritePermission() throws UnsupportedOperationException { assert Thread.holdsLock(getLock()); } /** * Returns the synchronization lock. The default implementation returns {@code this}. * * {@section Note for subclass implementors} * Subclasses that override this method must be careful to update the lock reference * (if needed) when this list is {@linkplain #clone() cloned}. * * @return The synchronization lock. */ protected Object getLock() { return this; } /** * A synchronized iterator with a check for write permission prior element removal. * This class wraps the iterator provided by {@link ArrayList#iterator()}, and is * also the base class for the wrapper around {@link ArrayList#listIterator()}. * * @see CheckedArrayList#iterator() */ private class Iter<I extends Iterator<E>> implements Iterator<E> { /** The {@link ArrayList} iterator. */ protected final I iterator; /** Creates a new wrapper for the given {@link ArrayList} iterator. */ Iter(final I iterator) { this.iterator = iterator; } /** Returns {@code true} if there is more elements in the iteration. */ @Override public final boolean hasNext() { synchronized (getLock()) { return iterator.hasNext(); } } /** Returns the next element in the iteration. */ @Override public final E next() throws NoSuchElementException { synchronized (getLock()) { return iterator.next(); } } /** Removes the previous element if the enclosing {@link CheckedArrayList} allows write operations. */ @Override public final void remove() throws UnsupportedOperationException { synchronized (getLock()) { checkWritePermission(); iterator.remove(); } } } /** * A synchronized list iterator with a check for write permission prior element removal. * This class wraps the iterator provided by {@link ArrayList#listIterator()}. * * @see CheckedArrayList#listIterator() * @see CheckedArrayList#listIterator(int) */ private class ListIter extends Iter<ListIterator<E>> implements ListIterator<E> { /** Creates a new wrapper for the given {@link ArrayList} list iterator. */ ListIter(final ListIterator<E> iterator) { super(iterator); } /** Returns the index of the element to be returned by {@link #next()}. */ @Override public int nextIndex() { synchronized (getLock()) { return iterator.nextIndex(); } } /** Returns the index of the element to be returned by {@link #previous()}. */ @Override public int previousIndex() { synchronized (getLock()) { return iterator.previousIndex(); } } /** Returns {@code true} if there is elements before current position. */ @Override public boolean hasPrevious() { synchronized (getLock()) { return iterator.hasPrevious(); } } /** Returns the previous element in the iteration. */ @Override public E previous() throws NoSuchElementException { synchronized (getLock()) { return iterator.previous(); } } /** See the {@link CheckedArrayList#set(int, Object)} method contract. */ @Override public void set(final E element) throws IllegalArgumentException, UnsupportedOperationException { ensureValid(element); synchronized (getLock()) { checkWritePermission(); iterator.set(element); } } /** See the {@link CheckedArrayList#add(Object)} method contract. */ @Override public void add(final E element) throws IllegalArgumentException, UnsupportedOperationException { ensureValid(element); synchronized (getLock()) { checkWritePermission(); iterator.add(element); } } } /** * Returns an iterator over the elements in this list. * The returned iterator will support {@linkplain Iterator#remove() element removal} * only if the {@link #checkWritePermission()} method does not throw exception. */ @Override public Iterator<E> iterator() { synchronized (getLock()) { return new Iter<>(super.iterator()); } } /** * Returns an iterator over the elements in this list. * The returned iterator will support {@linkplain ListIterator#remove() element removal}, * {@linkplain ListIterator#add(Object) addition} or {@linkplain ListIterator#set(Object) * modification} only if the {@link #checkWritePermission()} method does not throw exception. */ @Override public ListIterator<E> listIterator() { synchronized (getLock()) { return new ListIter(super.listIterator()); } } /** * Returns an iterator over the elements in this list, starting at the given index. * The returned iterator will support {@linkplain ListIterator#remove() element removal}, * {@linkplain ListIterator#add(Object) addition} or {@linkplain ListIterator#set(Object) * modification} only if the {@link #checkWritePermission()} method does not throw exception. */ @Override public ListIterator<E> listIterator(final int index) { synchronized (getLock()) { return new ListIter(super.listIterator(index)); } } /** * Returns the number of elements in this list. */ @Override public int size() { synchronized (getLock()) { return super.size(); } } /** * Returns {@code true} if this list contains no elements. */ @Override public boolean isEmpty() { synchronized (getLock()) { return super.isEmpty(); } } /** * Returns {@code true} if this list contains the specified element. */ @Override public boolean contains(final Object o) { synchronized (getLock()) { return super.contains(o); } } /** * Returns the index of the first occurrence of the specified element in this list, * or -1 if none. */ @Override public int indexOf(Object o) { synchronized (getLock()) { return super.indexOf(o); } } /** * Returns the index of the last occurrence of the specified element in this list, * or -1 if none. */ @Override public int lastIndexOf(Object o) { synchronized (getLock()) { return super.lastIndexOf(o); } } /** * Returns the element at the specified position in this list. */ @Override public E get(int index) { synchronized (getLock()) { return super.get(index); } } /** * Replaces the element at the specified position in this list with the specified element. * * @param index index of element to replace. * @param element element to be stored at the specified position. * @return the element previously at the specified position. * @throws IndexOutOfBoundsException if index out of range. * @throws IllegalArgumentException if the specified element is not of the expected type. * @throws UnsupportedOperationException if this collection is unmodifiable. */ @Override public E set(final int index, final E element) throws IllegalArgumentException, UnsupportedOperationException { ensureValid(element); synchronized (getLock()) { checkWritePermission(); return super.set(index, element); } } /** * Appends the specified element to the end of this list. * * @param element element to be appended to this list. * @return always {@code true}. * @throws IllegalArgumentException if the specified element is not of the expected type. * @throws UnsupportedOperationException if this collection is unmodifiable. */ @Override public boolean add(final E element) throws IllegalArgumentException, UnsupportedOperationException { ensureValid(element); synchronized (getLock()) { checkWritePermission(); return super.add(element); } } /** * Inserts the specified element at the specified position in this list. * * @param index index at which the specified element is to be inserted. * @param element element to be inserted. * @throws IndexOutOfBoundsException if index out of range. * @throws IllegalArgumentException if the specified element is not of the expected type. * @throws UnsupportedOperationException if this collection is unmodifiable. */ @Override public void add(final int index, final E element) throws IllegalArgumentException, UnsupportedOperationException { ensureValid(element); synchronized (getLock()) { checkWritePermission(); super.add(index, element); } } /** * Appends all of the elements in the specified collection to the end of this list, * in the order that they are returned by the specified Collection's Iterator. * * @param collection the elements to be inserted into this list. * @return {@code true} if this list changed as a result of the call. * @throws IllegalArgumentException if at least one element is not of the expected type. * @throws UnsupportedOperationException if this collection is unmodifiable. */ @Override public boolean addAll(final Collection<? extends E> collection) throws IllegalArgumentException, UnsupportedOperationException { ensureValidCollection(collection); synchronized (getLock()) { checkWritePermission(); return super.addAll(collection); } } /** * Inserts all of the elements in the specified collection into this list, * starting at the specified position. * * @param index index at which to insert first element fromm the specified collection. * @param collection elements to be inserted into this list. * @return {@code true} if this list changed as a result of the call. * @throws IllegalArgumentException if at least one element is not of the expected type. * @throws UnsupportedOperationException if this collection is unmodifiable. */ @Override public boolean addAll(final int index, final Collection<? extends E> collection) throws IllegalArgumentException, UnsupportedOperationException { ensureValidCollection(collection); synchronized (getLock()) { checkWritePermission(); return super.addAll(index, collection); } } /** * Removes the element at the specified position in this list. * * @throws UnsupportedOperationException if this collection is unmodifiable. */ @Override public E remove(int index) throws UnsupportedOperationException { synchronized (getLock()) { checkWritePermission(); return super.remove(index); } } /** * Removes the first occurrence of the specified element from this list. * * @throws UnsupportedOperationException if this collection is unmodifiable. */ @Override public boolean remove(Object o) throws UnsupportedOperationException { synchronized (getLock()) { checkWritePermission(); return super.remove(o); } } /** * Removes all of this list's elements that are also contained in the specified collection. * * @throws UnsupportedOperationException if this collection is unmodifiable. */ @Override public boolean removeAll(Collection<?> c) throws UnsupportedOperationException { synchronized (getLock()) { checkWritePermission(); return super.removeAll(c); } } /** * Retains only the elements in this list that are contained in the specified collection. * * @throws UnsupportedOperationException if this collection is unmodifiable. */ @Override public boolean retainAll(Collection<?> c) throws UnsupportedOperationException { synchronized (getLock()) { checkWritePermission(); return super.retainAll(c); } } /** * Trims the capacity to the list's current size. */ @Override public void trimToSize() { synchronized (getLock()) { super.trimToSize(); } } /** * Increases the capacity, if necessary, to ensure that it can hold the given number * of elements. */ @Override public void ensureCapacity(final int minCapacity) { synchronized (getLock()) { super.ensureCapacity(minCapacity); } } /** * Removes all of the elements from this list. * * @throws UnsupportedOperationException if this collection is unmodifiable. */ @Override public void clear() throws UnsupportedOperationException { synchronized (getLock()) { checkWritePermission(); super.clear(); } } /** * Returns an array containing all of the elements in this list. */ @Override public Object[] toArray() { synchronized (getLock()) { return super.toArray(); } } /** * Returns an array containing all of the elements in this list in proper sequence. * * @param <T> The type of array elements. */ @Override public <T> T[] toArray(T[] a) { synchronized (getLock()) { return super.toArray(a); } } /** * Returns a string representation of this list. */ @Override public String toString() { synchronized (getLock()) { return super.toString(); } } /** * Compares the specified object with this list for equality. */ @Override public boolean equals(Object o) { synchronized (getLock()) { return super.equals(o); } } /** * Returns the hash code value for this list. */ @Override public int hashCode() { synchronized (getLock()) { return super.hashCode(); } } /** * Returns a shallow copy of this list. * * @return A shallow copy of this list. */ @Override @SuppressWarnings("unchecked") public CheckedArrayList<E> clone() { synchronized (getLock()) { return (CheckedArrayList<E>) super.clone(); } } }