/* * Copyright 2014 The Skfiy Open Association. * * 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 org.skfiy.typhon.util; import java.util.AbstractList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.List; /** * * @author Kevin Zou <kevinz@skfiy.org> * @param <E> */ public class SortedList<E> extends AbstractList<E> implements List<E>, Cloneable, java.io.Serializable { /** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10; /** * Shared empty array instance used for empty instances. */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * The maximum size of array to allocate. Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in OutOfMemoryError: Requested array size * exceeds VM limit */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** * The array buffer into which the elements of the ArrayList are stored. The capacity of the * ArrayList is the length of this array buffer. Any empty ArrayList with elementData == * EMPTY_ELEMENTDATA will be expanded to DEFAULT_CAPACITY when the first element is added. */ private transient Object[] elementData; /** * The size of the SortedList (the number of elements it contains). * * @serial */ private int size; private Comparator<E> comparator; /** * Constructs an empty list with an initial capacity of ten. */ public SortedList() { this.elementData = EMPTY_ELEMENTDATA; } /** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list * @throws IllegalArgumentException if the specified initial capacity is negative */ public SortedList(int initialCapacity) { this(initialCapacity, null); } /** * * @param comparator */ public SortedList(Comparator<E> comparator) { this.elementData = EMPTY_ELEMENTDATA; this.comparator = comparator; } /** * * @param initialCapacity * @param comparator */ public SortedList(int initialCapacity, Comparator<E> comparator) { if (initialCapacity < 0) { throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); } this.elementData = new Object[initialCapacity]; this.comparator = comparator; } /** * * @return */ public Comparator<E> getComparator() { return comparator; } /** * * @param comparator */ public void setComparator(Comparator<E> comparator) { this.comparator = comparator; } /** * Trims the capacity of this <tt>ArrayList</tt> instance to be the list's current size. An * application can use this operation to minimize the storage of an <tt>ArrayList</tt> instance. */ public void trimToSize() { modCount++; if (size < elementData.length) { elementData = Arrays.copyOf(elementData, size); } } /** * Increases the capacity of this <tt>ArrayList</tt> instance, if necessary, to ensure that it * can hold at least the number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ public void ensureCapacity(int minCapacity) { int minExpand = (elementData != EMPTY_ELEMENTDATA) // any size if real element table ? 0 // larger than default for empty table. It's already supposed to be // at default size. : DEFAULT_CAPACITY; if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } } @Override public int size() { return size; } @Override public boolean isEmpty() { return size == 0; } /** * Returns <tt>true</tt> if this list contains the specified element. More formally, returns * <tt>true</tt> if and only if this list contains at least one element <tt>e</tt> such that * <tt>(o==null ? e==null : o.equals(e))</tt>. * * @param o element whose presence in this list is to be tested * @return <tt>true</tt> if this list contains the specified element */ @Override public boolean contains(Object o) { return indexOf(o) >= 0; } /** * 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 <tt>i</tt> * such that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>, or -1 if there is * no such index. */ @Override public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) { if (elementData[i] == null) { return i; } } } else { for (int i = 0; i < size; i++) { if (o.equals(elementData[i])) { return i; } } } return -1; } /** * Returns the index of the last occurrence of the specified element in this list, or -1 if this * list does not contain the element. More formally, returns the highest index <tt>i</tt> such * that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>, or -1 if there is * no such index. */ @Override public int lastIndexOf(Object o) { if (o == null) { for (int i = size - 1; i >= 0; i--) { if (elementData[i] == null) { return i; } } } else { for (int i = size - 1; i >= 0; i--) { if (o.equals(elementData[i])) { return i; } } } return -1; } /** * Returns an array containing all of the elements in this list in proper sequence (from first * to last element); the runtime type of the returned array is that of the specified array. If * the list fits in the specified array, it is returned therein. Otherwise, a new array is * allocated with the runtime type of the specified array and the size of this list. * * <p> * If the list fits in the specified array with room to spare (i.e., the array has more elements * than the list), the element in the array immediately following the end of the collection is * set to * <tt>null</tt>. (This is useful in determining the length of the list <i>only</i> if the * caller knows that the list does not contain any null elements.) * * @param a the array into which the elements of the list are to be stored, if it is big enough; * otherwise, a new array of the same runtime type is allocated for this purpose. * @return an array containing the elements of the list * @throws ArrayStoreException if the runtime type of the specified array is not a supertype of * the runtime type of every element in this list * @throws NullPointerException if the specified array is null */ @Override public <T> T[] toArray(T[] a) { if (a.length < size) // Make a new array of a's runtime type, but my contents: { return (T[]) Arrays.copyOf(elementData, size, a.getClass()); } System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) { a[size] = null; } return a; } // Positional Access Operations E elementData(int index) { return (E) elementData[index]; } /** * Returns the element at the specified position in this list. * * @param index index of the element to return * @return the element at the specified position in this list * @throws IndexOutOfBoundsException {@inheritDoc} */ @Override public E get(int index) { rangeCheck(index); return elementData(index); } /** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ @Override public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! int cmp; // split comparator and comparable paths Comparator<? super E> cpr = comparator; if (cpr != null) { for (int i = 0; i < size; i++) { E v = elementData(i); cmp = cpr.compare(e, v); if (cmp < 0) { System.arraycopy(elementData, i, elementData, i + 1, (size - i)); elementData[i] = e; size++; return true; } } } else { Comparable<? super E> v = (Comparable<? super E>) e; for (int i = 0; i < size; i++) { E ev = elementData(i); cmp = v.compareTo(ev); if (cmp < 0) { System.arraycopy(elementData, i, elementData, i + 1, (size - i)); elementData[i] = e; size++; return true; } } } elementData[size++] = e; return true; } /** * Removes the element at the specified position in this list. Shifts any subsequent elements to * the left (subtracts one from their indices). * * @param index the index of the element to be removed * @return the element that was removed from the list * @throws IndexOutOfBoundsException {@inheritDoc} */ @Override public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) { System.arraycopy(elementData, index + 1, elementData, index, numMoved); } elementData[--size] = null; // clear to let GC do its work return oldValue; } /** * Removes the first occurrence of the specified element from this list, if it is present. If * the list does not contain the element, it is unchanged. More formally, removes the element * with the lowest index * <tt>i</tt> such that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt> * (if such an element exists). Returns <tt>true</tt> if this list contained the specified * element (or equivalently, if this list changed as a result of the call). * * @param o element to be removed from this list, if present * @return <tt>true</tt> if this list contained the specified element */ @Override public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) { if (elementData[index] == null) { fastRemove(index); return true; } } } else { for (int index = 0; index < size; index++) { if (o.equals(elementData[index])) { fastRemove(index); return true; } } } return false; } /* * Private remove method that skips bounds checking and does not * return the value removed. */ private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) { System.arraycopy(elementData, index + 1, elementData, index, numMoved); } elementData[--size] = null; // clear to let GC do its work } /** * Removes all of the elements from this list. The list will be empty after this call returns. */ @Override public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) { elementData[i] = null; } size = 0; } /** * 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. The behavior of this operation * is undefined if the specified collection is modified while the operation is in progress. * (This implies that the behavior of this call is undefined if the specified collection is this * list, and this list is nonempty.) * * @param c collection containing elements to be added to this list * @return <tt>true</tt> if this list changed as a result of the call * @throws NullPointerException if the specified collection is null */ @Override public boolean addAll(Collection<? extends E> c) { for (Iterator<? extends E> it = c.iterator(); it.hasNext();) { add(it.next()); } return true; } /** * Removes from this list all of the elements whose index is between {@code fromIndex}, * inclusive, and {@code toIndex}, exclusive. Shifts any succeeding elements to the left * (reduces their index). This call shortens the list by {@code (toIndex - fromIndex)} elements. * (If {@code toIndex==fromIndex}, this operation has no effect.) * * @throws IndexOutOfBoundsException if {@code fromIndex} or {@code toIndex} is out of range ({@code fromIndex < 0 || * fromIndex >= size() || * toIndex > size() || * toIndex < fromIndex}) */ @Override protected void removeRange(int fromIndex, int toIndex) { modCount++; int numMoved = size - toIndex; System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved); // clear to let GC do its work int newSize = size - (toIndex - fromIndex); for (int i = newSize; i < size; i++) { elementData[i] = null; } size = newSize; } /** * Checks if the given index is in range. If not, throws an appropriate runtime exception. This * method does *not* check if the index is negative: It is always used immediately prior to an * array access, which throws an ArrayIndexOutOfBoundsException if index is negative. */ private void rangeCheck(int index) { if (index >= size) { throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } } /** * A version of rangeCheck used by add and addAll. */ private void rangeCheckForAdd(int index) { if (index > size || index < 0) { throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } } /** * Constructs an IndexOutOfBoundsException detail message. Of the many possible refactorings of * the error handling code, this "outlining" performs best with both server and client VMs. */ private String outOfBoundsMsg(int index) { return "Index: " + index + ", Size: " + size; } /** * Returns an array containing all of the elements in this list in proper sequence (from first * to last element). * * <p> * The returned array will be "safe" in that no references to it are maintained by this list. * (In other words, this method must allocate a new array). The caller is thus free to modify * the returned array. * * <p> * This method acts as bridge between array-based and collection-based APIs. * * @return an array containing all of the elements in this list in proper sequence */ @Override public Object[] toArray() { return Arrays.copyOf(elementData, size); } /** * Returns a shallow copy of this <tt>ArrayList</tt> instance. (The elements themselves are not * copied.) * * @return a clone of this <tt>ArrayList</tt> instance */ @Override public Object clone() { try { SortedList<E> v = (SortedList<E>) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(); } } private void ensureCapacityInternal(int minCapacity) { if (elementData == EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) { grow(minCapacity); } } /** * Increases the capacity to ensure that it can hold at least the number of elements specified * by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) { newCapacity = minCapacity; } if (newCapacity - MAX_ARRAY_SIZE > 0) { newCapacity = hugeCapacity(minCapacity); } // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow { throw new OutOfMemoryError(); } return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } }