diff options
author | Bryce McKinlay <bryce@gcc.gnu.org> | 2001-12-15 07:47:03 +0000 |
---|---|---|
committer | Bryce McKinlay <bryce@gcc.gnu.org> | 2001-12-15 07:47:03 +0000 |
commit | d9fd7154ec7908eff8bbbce75651eccf51064ac1 (patch) | |
tree | a0210bc88649e7cd6d847884e12a68146f35d955 /libjava/java | |
parent | def9790d51a51a78a700567bb677225a90bc854e (diff) | |
download | gcc-d9fd7154ec7908eff8bbbce75651eccf51064ac1.tar.gz |
Collections drop from Classpath:
2001-12-15 Bryce McKinlay <bryce@waitaki.otago.ac.nz>
* java/util/BitSet.java (and): Fix off-by-one bug, don't skip part of
the bitset.
(andNot): Likewise.
(xor): Likewise.
2001-12-15 Bryce McKinlay <bryce@waitaki.otago.ac.nz>
* java/util/LinkedList.java (LinkedListItr.add): Don't skip the next
entry.
2001-12-15 Eric Blake <ebb9@email.byu.edu>
* java/util/TreeMap.java (removeNode): Fix bug in node removal.
2001-12-15 Bryce McKinlay <bryce@waitaki.otago.ac.nz>
* java/util/AbstractCollection.java (containsAll): Use size of the
correct collection for loop bound.
* java/util/AbstractList.java (iterator.next): Increment pos after
calling get on backing list.
(listIterator.next): Likewise.
* java/util/LinkedList.java (addLastEntry): Don't increment size before
checking for size == 0.
(addFirstEntry): Rearrange to match addLastEntry.
(add): Do not increment size before inserting the new entry.
* java/util/AbstractCollection.java (addAll): Use size of the
correct collection for loop bound.
2001-12-15 Bryce McKinlay <bryce@waitaki.otago.ac.nz>
* java/util/AbstractSet.java (removeAll): Fix scoping thinko.
* java/util/HashMap.java (putAllInternal): Set size here.
* java/util/Hashtable.java (putAllInternal): New method. Copy contents
of a map efficiently without calling put() or putAll().
(Hashtable (map)): Use putAllInternal.
(clone): Likewise.
2001-12-15 Eric Blake <ebb9@email.byu.edu>
* java/util/Collections.java:
* java/util/Vector.java:
* java/util/WeakHashMap.java: Fix spelling errors.
2001-12-15 Eric Blake <ebb9@email.byu.edu>
* java/util/AbstractCollection.java (removeAllInternal),
(retainAllInternal): Add hooks for use by ArrayList.
* java/util/AbstractList.java: Minor code updates. Fix some
scoping.
* java/util/AbstractMap.java: ditto
* java/util/ArrayList.java (readObject, writeObject): ditto
(removeAllInternal, retainAllInternal): Optimize.
* java/util/Arrays.java: ditto
* java/util/Collections.java: ditto. Change order of parameters
to equals(Object, Object) to match specs.
* java/util/Dictionary.java: Improve javadoc.
(Dictionary): Add explicit constructor.
* java/util/HashMap.java: Improve javadoc. Rearrange methods to
follow order in JDK. Cleanups related to recent code migration to
AbstractMap. Fix some scoping.
(entrySet): Cache the result.
(modCount): Ensure that this is updated correctly.
* java/util/HashSet.java: Improve javadoc. Fix some scoping.
(init): Add hooks for LinkedHashSet.
(map): Use "" instead of Boolean.TRUE in backing map. Use
package-private API where possible for less overhead.
(readObject, writeObject): Fix serialization.
* java/util/Hashtable.java: Improve javadoc. Fix some scoping.
(entrySet, keySet, values): Cache the result.
(modCount): Ensure that this is updated correctly.
(contains, remove): Fix NullPointer checking to match specs.
(class Enumeration): Make more like HashIterator.
* java/util/IdentityHashMap.java: Minor code updates.
(modCount): Ensure that this is updated correctly.
(readObject, writeObject): Fix serialization.
* java/util/LinkedHashMap.java: Minor code updates. Cleanups
related to recent code migration to AbstractMap.
* java/util/LinkedHashSet.java: New file.
* java/util/LinkedList.java:
(readObject, writeObject): Fix serialization.
* java/util/Makefile.am: List recently added files.
* java/util/Stack.java: Minor code updates.
* java/util/TreeMap.java: Improve javadoc. Overhaul the class to
be more efficient. Fix some scoping. Rearrange the methods.
(nil): Ensure that this can be thread-safe, and make it a static
final. Initialize it to be more useful as a sentinal node.
(Node): Specify color in constructor.
(deleteFixup, insertFixup): Improve comments and algorithm.
(fabricateTree): Redesign with less overhead.
(lowestGreaterThan): Add parameter first to make SubMap easier.
(removeNode): Patch hole where nil was being modified. Choose
predecessor instead of successor so in-place swap works.
(class VerifyResult, verifyTree, verifySub, verifyError): Remove
this dead code after verifying the class works.
(class SubMap): Rewrite several algorithms to avoid problems with
comparing nil.
* java/util/TreeSet.java: Improve javadoc. Fix some scoping.
(clone): Fix ClassCastException when cloning subSet().
(readObject, writeObject): Fix serialization.
* java/util/WeakHashMap.java: Improve javadoc. Fix some scoping.
(NULL_KEY): Make it compare as null, for ease elsewhere.
(Class WeakEntry): Rename from Entry, to avoid shadowing
Map.Entry. Add missing toString.
(modCount): Ensure that this is updated correctly.
(clear, containsValue, keySet, putAll, values, WeakHashMap(Map)):
Add missing methods and constructor.
2001-12-15 Eric Blake <ebb9@email.byu.edu>
* java/util/ArrayList.java (checkBoundExclusive),
(checkBoundInclusive): Rename from range??clusive, to match
AbstractList.
* java/util/LinkedList.java (checkBoundsExclusive),
(checkBoundsInclusive): ditto
* java/util/Vector.java (checkBoundExclusive),
(checkBoundInclusive): Move bounds checking into common methods.
2001-12-15 Eric Blake <ebb9@email.byu.edu>
* java/util/AbstractList.java:
(modCount): Make sure it is updated in all needed places.
* java/util/ArrayList.java: Improve javadoc. Implements
RandomAccess. Add serialVersionUID. Reorder methods.
(modCount): Make sure it is updated in all needed places.
(rangeExclusive, rangeInclusive): Add common methods for bounds
check.
(isEmpty): Add missing method.
* java/util/Collections.java: (class SynchronizedList): Make
package visible.
* java/util/ConcurrentModificationException.java: Improve
javadoc.
* java/util/EmptyStackException.java: Improve javadoc.
* java/util/LinkedList.java: Improve javadoc.
(modCount): Make sure it is updated in all needed places.
(rangeExclusive, rangeInclusive): Add common methods for bounds
check.
* java/util/NoSuchElementException.java: Improve javadoc.
* java/util/Stack.java: Improve javadoc. Fix synchronization
issues.
(modCount): Make sure it is updated in all needed places.
* java/util/Vector.java: Improve javadoc. Fix synchronization
issues. Implements RandomAccess. Reorder methods.
(modCount): Make sure it is updated in all needed places.
(setSize): Fix according to specifications: this does not dictate
the backing array size.
(removeAll, retainAll): Faster implementations.
2001-12-15 Eric Blake <ebb9@email.byu.edu>
* java/util/BitSet.java: Improve javadoc.
(cardinality(), clear(), clear(int, int), flip(int)),
(flip(int, int), get(int, int), intersects(BitSet), isEmpty()),
(nextClearBit(int), nextSetBit(int), set(int, boolean)),
(set(int, int), set(int, int, boolean)): Add new JDK 1.4 methods.
(clone): Fix so subclasses clone correctly.
2001-12-15 Eric Blake <ebb9@email.byu.edu>
* java/util/AbstractCollection.java: Improve javadoc.
(AbstractCollection()): Make constructor protected.
(equals(Object, Object), hashCode(Object)): Add utility methods.
* java/util/AbstractList.java: Improve javadoc.
(AbstractList()): Make constructor protected.
(indexOf(Object)): Call listIterator(), not listIterator(int).
(iterator()): Follow Sun's requirement to not use listIterator(0).
(listIterator(int)): Make AbstractListItr anonymous.
(subList(int, int)): Add support for RandomAccess.
(SubList.add(int, Object), SubList.remove(Object)): Fix bug with
modCount tracking.
(SubList.addAll(Collection)): Add missing method.
(SubList.listIterator(int)): Fix bugs in indexing, modCount
tracking.
(class RandomAccessSubList): Add new class.
* java/util/AbstractMap.java: Improve javadoc.
(keys, values, KEYS, VALUES, ENTRIES): Consolidate common map
fields.
(AbstractMap()): Make constructor protected.
(equals(Object, Object), hashCode(Object)): Add utility methods.
(equals(Object)): Change algorithm to
entrySet().equals(m.entrySet()), as documented by Sun.
(keySet(), values()): Cache the collections.
* java/util/AbstractSequentialList.java: Improve javadoc.
(AbstractSequentialList()): Make constructor protected.
* java/util/AbstractSet.java: Improve javadoc.
(AbstractSet()): Make constructor protected.
(removeAll(Collection)): Add missing method.
* java/util/Arrays.java: Improve javadoc, rearrange method orders.
(defaultComparator): Remove, in favor of
Collections.compare(Object, Object, Comparator).
(binarySearch, equals, sort): Fix natural order comparison of
floats and doubles. Also improve Object comparison - when
comparator is null, use natural order.
(fill, sort): Add missing checks for IllegalArgumentException.
(sort, qsort): Fix sorting bugs, rework the code for more
legibility.
(mergeSort): Inline into sort(Object[], int, int, Comparator).
(class ArrayList): Rename from ListImpl, and make compatible with
JDK serialization. Add methods which more efficiently override
those of AbstractList.
* java/util/Collections: Improve javadoc.
(isSequential(List)): Add and use a method for deciding between
RandomAccess and sequential algorithms on lists.
(class Empty*, class Synchronized*, class Unmodifiable*): Make
compliant with JDK serializability.
(class Singleton*, class CopiesList, class RevereseComparator),
(class UnmodifiableMap.UnmodifiableEntrySet),
(class *RandomAccessList): New classes for serial compatibility.
(class Empty*, class Singleton*, class CopiesList): Add methods
which more efficiently override those of Abstract*.
(search): Inline into binarySearch(List, Object, Comparator).
(binarySearch): Make sequential search only do log(n) comparisons,
instead of n.
(copy(List, List)): Do bounds checking before starting.
(indexOfSubList, lastIndexOfSubList, list, replaceAll, rotate),
(swap): Add new JDK 1.4 methods.
(binarySearch, max, min, sort): Allow null comparator to represent
natural ordering.
(reverse(List)): Avoid unnecessary swap.
(shuffle(List, Random)): Do shuffle in-place for RandomAccess
lists.
(SingletonList.get): Fix logic bug.
(SingletonMap.entrySet): Make the entry immutable, and cache the
returned set.
(SynchronizedCollection, SynchronizedMap, UnmodifiableCollection),
(UnmodifiableMap): Detect null pointer in construction.
(SynchronizedMap, UnmodifiableMap): Cache collection views.
* java/util/BasicMapEntry: Improve javadoc.
From-SVN: r48035
Diffstat (limited to 'libjava/java')
24 files changed, 11216 insertions, 6122 deletions
diff --git a/libjava/java/util/AbstractCollection.java b/libjava/java/util/AbstractCollection.java index 1bb73d7d8b4..095e1c41880 100644 --- a/libjava/java/util/AbstractCollection.java +++ b/libjava/java/util/AbstractCollection.java @@ -1,5 +1,5 @@ /* AbstractCollection.java -- Abstract implementation of most of Collection - Copyright (C) 1998, 2000 Free Software Foundation, Inc. + Copyright (C) 1998, 2000, 2001 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -42,77 +42,119 @@ import java.lang.reflect.Array; * backing data structure allows for a more efficient implementation. The * precise implementation used by AbstractCollection is documented, so that * subclasses can tell which methods could be implemented more efficiently. + * <p> + * + * The programmer should provide a no-argument constructor, and one that + * accepts another Collection, as recommended by the Collection interface. + * Unfortunately, there is no way to enforce this in Java. + * + * @author Original author unknown + * @author Bryce McKinlay + * @author Eric Blake <ebb9@email.byu.edu> + * @see Collection + * @see AbstractSet + * @see AbstractList + * @since 1.2 + * @status updated to 1.4 */ public abstract class AbstractCollection implements Collection { /** + * The main constructor, for use by subclasses. + */ + protected AbstractCollection() + { + } + + /** * Return an Iterator over this collection. The iterator must provide the * hasNext and next methods and should in addition provide remove if the * collection is modifiable. + * + * @return an iterator */ public abstract Iterator iterator(); /** - * Return the number of elements in this collection. + * Return the number of elements in this collection. If there are more than + * Integer.MAX_VALUE elements, return Integer.MAX_VALUE. + * + * @return the size */ public abstract int size(); /** - * Add an object to the collection. This implementation always throws an - * UnsupportedOperationException - it should be overridden if the collection - * is to be modifiable. + * Add an object to the collection (optional operation). This implementation + * always throws an UnsupportedOperationException - it should be + * overridden if the collection is to be modifiable. If the collection + * does not accept duplicates, simply return false. Collections may specify + * limitations on what may be added. * * @param o the object to add * @return true if the add operation caused the Collection to change - * @exception UnsupportedOperationException if the add operation is not - * supported on this collection + * @throws UnsupportedOperationException if the add operation is not + * supported on this collection + * @throws NullPointerException if the collection does not support null + * @throws ClassCastException if the object is of the wrong type + * @throws IllegalArgumentException if some aspect of the object prevents + * it from being added */ public boolean add(Object o) { - throw new java.lang.UnsupportedOperationException(); + throw new UnsupportedOperationException(); } /** - * Add all the elements of a given collection to this collection. This - * implementation obtains an Iterator over the given collection and iterates - * over it, adding each element with the add(Object) method (thus this method - * will fail with an UnsupportedOperationException if the add method does). + * Add all the elements of a given collection to this collection (optional + * operation). This implementation obtains an Iterator over the given + * collection and iterates over it, adding each element with the + * add(Object) method (thus this method will fail with an + * UnsupportedOperationException if the add method does). The behavior is + * unspecified if the specified collection is modified during the iteration, + * including the special case of trying addAll(this) on a non-empty + * collection. * * @param c the collection to add the elements of to this collection * @return true if the add operation caused the Collection to change - * @exception UnsupportedOperationException if the add operation is not - * supported on this collection + * @throws UnsupportedOperationException if the add operation is not + * supported on this collection + * @throws NullPointerException if this collection does not support null, + * or if the specified collection is null + * @throws ClassCastException if an object in c is of the wrong type + * @throws IllegalArgumentException if some aspect of an object in c prevents + * it from being added + * @see #add(Object) */ public boolean addAll(Collection c) { Iterator itr = c.iterator(); - int size = c.size(); boolean modified = false; - for (int pos = 0; pos < size; pos++) - { - modified |= add(itr.next()); - } + int pos = c.size(); + while (--pos >= 0) + modified |= add(itr.next()); return modified; } /** - * Remove all elements from the collection. This implementation obtains an - * iterator over the collection and calls next and remove on it repeatedly - * (thus this method will fail with an UnsupportedOperationException if the - * Iterator's remove method does) until there are no more elements to remove. + * Remove all elements from the collection (optional operation). This + * implementation obtains an iterator over the collection and calls next + * and remove on it repeatedly (thus this method will fail with an + * UnsupportedOperationException if the Iterator's remove method does) + * until there are no more elements to remove. * Many implementations will have a faster way of doing this. * - * @exception UnsupportedOperationException if the Iterator returned by - * iterator does not provide an implementation of remove + * @throws UnsupportedOperationException if the Iterator returned by + * iterator does not provide an implementation of remove + * @see Iterator#remove() */ public void clear() { Iterator itr = iterator(); - int size = size(); - for (int pos = 0; pos < size; pos++) + int pos = size(); + while (--pos >= 0) { - itr.next(); - itr.remove(); + itr.next(); + itr.remove(); } } @@ -130,12 +172,10 @@ public abstract class AbstractCollection implements Collection public boolean contains(Object o) { Iterator itr = iterator(); - int size = size(); - for (int pos = 0; pos < size; pos++) - { - if (o == null ? itr.next() == null : o.equals(itr.next())) - return true; - } + int pos = size(); + while (--pos >= 0) + if (equals(o, itr.next())) + return true; return false; } @@ -147,17 +187,17 @@ public abstract class AbstractCollection implements Collection * * @param c the collection to test against * @return true if this collection contains all the elements in the given - * collection + * collection + * @throws NullPointerException if the given collection is null + * @see #contains(Object) */ public boolean containsAll(Collection c) { Iterator itr = c.iterator(); - int size = c.size(); - for (int pos = 0; pos < size; pos++) - { - if (!contains(itr.next())) - return false; - } + int pos = c.size(); + while (--pos >= 0) + if (!contains(itr.next())) + return false; return true; } @@ -166,6 +206,7 @@ public abstract class AbstractCollection implements Collection * size() == 0. * * @return true if this collection is empty. + * @see #size() */ public boolean isEmpty() { @@ -173,92 +214,131 @@ public abstract class AbstractCollection implements Collection } /** - * Remove a single instance of an object from this collection. That is, - * remove one element e such that (o == null ? e == null : o.equals(e)), if - * such an element exists. This implementation obtains an iterator over the - * collection and iterates over it, testing each element for equality with - * the given object. If it is equal, it is removed by the iterator's remove - * method (thus this method will fail with an UnsupportedOperationException - * if the Iterator's remove method does). After the first element has been + * Remove a single instance of an object from this collection (optional + * operation). That is, remove one element e such that + * <code>(o == null ? e == null : o.equals(e))</code>, if such an element + * exists. This implementation obtains an iterator over the collection + * and iterates over it, testing each element for equality with the given + * object. If it is equal, it is removed by the iterator's remove method + * (thus this method will fail with an UnsupportedOperationException if + * the Iterator's remove method does). After the first element has been * removed, true is returned; if the end of the collection is reached, false * is returned. * * @param o the object to remove from this collection * @return true if the remove operation caused the Collection to change, or - * equivalently if the collection did contain o. - * @exception UnsupportedOperationException if this collection's Iterator - * does not support the remove method + * equivalently if the collection did contain o. + * @throws UnsupportedOperationException if this collection's Iterator + * does not support the remove method + * @see Iterator#remove() */ public boolean remove(Object o) { Iterator itr = iterator(); - int size = size(); - for (int pos = 0; pos < size; pos++) - { - if (o == null ? itr.next() == null : o.equals(itr.next())) - { - itr.remove(); - return true; - } - } + int pos = size(); + while (--pos >= 0) + if (equals(o, itr.next())) + { + itr.remove(); + return true; + } return false; } /** * Remove from this collection all its elements that are contained in a given - * collection. This implementation iterates over this collection, and for - * each element tests if it is contained in the given collection. If so, it - * is removed by the Iterator's remove method (thus this method will fail - * with an UnsupportedOperationException if the Iterator's remove method - * does). + * collection (optional operation). This implementation iterates over this + * collection, and for each element tests if it is contained in the given + * collection. If so, it is removed by the Iterator's remove method (thus + * this method will fail with an UnsupportedOperationException if the + * Iterator's remove method does). * * @param c the collection to remove the elements of * @return true if the remove operation caused the Collection to change - * @exception UnsupportedOperationException if this collection's Iterator - * does not support the remove method + * @throws UnsupportedOperationException if this collection's Iterator + * does not support the remove method + * @see Iterator#remove() */ public boolean removeAll(Collection c) { + return removeAllInternal(c); + } + + /** + * Remove from this collection all its elements that are contained in a given + * collection (optional operation). This implementation iterates over this + * collection, and for each element tests if it is contained in the given + * collection. If so, it is removed by the Iterator's remove method (thus + * this method will fail with an UnsupportedOperationException if the + * Iterator's remove method does). This method is necessary for ArrayList, + * which cannot publicly override removeAll but can optimize this call. + * + * @param c the collection to remove the elements of + * @return true if the remove operation caused the Collection to change + * @throws UnsupportedOperationException if this collection's Iterator + * does not support the remove method + * @see Iterator#remove() + */ + boolean removeAllInternal(Collection c) + { Iterator itr = iterator(); - int size = size(); boolean modified = false; - for (int pos = 0; pos < size; pos++) - { - if (c.contains(itr.next())) - { - itr.remove(); - modified = true; - } - } + int pos = size(); + while (--pos >= 0) + if (c.contains(itr.next())) + { + itr.remove(); + modified = true; + } return modified; } /** * Remove from this collection all its elements that are not contained in a - * given collection. This implementation iterates over this collection, and - * for each element tests if it is contained in the given collection. If not, - * it is removed by the Iterator's remove method (thus this method will fail - * with an UnsupportedOperationException if the Iterator's remove method - * does). + * given collection (optional operation). This implementation iterates over + * this collection, and for each element tests if it is contained in the + * given collection. If not, it is removed by the Iterator's remove method + * (thus this method will fail with an UnsupportedOperationException if + * the Iterator's remove method does). * * @param c the collection to retain the elements of * @return true if the remove operation caused the Collection to change - * @exception UnsupportedOperationException if this collection's Iterator - * does not support the remove method + * @throws UnsupportedOperationException if this collection's Iterator + * does not support the remove method + * @see Iterator#remove() */ public boolean retainAll(Collection c) { + return retainAllInternal(c); + } + + /** + * Remove from this collection all its elements that are not contained in a + * given collection (optional operation). This implementation iterates over + * this collection, and for each element tests if it is contained in the + * given collection. If not, it is removed by the Iterator's remove method + * (thus this method will fail with an UnsupportedOperationException if + * the Iterator's remove method does). This method is necessary for + * ArrayList, which cannot publicly override retainAll but can optimize + * this call. + * + * @param c the collection to retain the elements of + * @return true if the remove operation caused the Collection to change + * @throws UnsupportedOperationException if this collection's Iterator + * does not support the remove method + * @see Iterator#remove() + */ + boolean retainAllInternal(Collection c) + { Iterator itr = iterator(); - int size = size(); boolean modified = false; - for (int pos = 0; pos < size; pos++) - { - if (!c.contains(itr.next())) - { - itr.remove(); - modified = true; - } - } + int pos = size(); + while (--pos >= 0) + if (!c.contains(itr.next())) + { + itr.remove(); + modified = true; + } return modified; } @@ -266,18 +346,18 @@ public abstract class AbstractCollection implements Collection * Return an array containing the elements of this collection. This * implementation creates an Object array of size size() and then iterates * over the collection, setting each element of the array from the value - * returned by the iterator. + * returned by the iterator. The returned array is safe, and is not backed + * by the collection. * * @return an array containing the elements of this collection */ public Object[] toArray() { Iterator itr = iterator(); - Object[]a = new Object[size()]; - for (int pos = 0; pos < a.length; pos++) - { - a[pos] = itr.next(); - } + int size = size(); + Object[] a = new Object[size]; + for (int pos = 0; pos < size; pos++) + a[pos] = itr.next(); return a; } @@ -293,29 +373,29 @@ public abstract class AbstractCollection implements Collection * obtained over the collection and the elements are placed in the array as * they are returned by the iterator. Finally the first spare element, if * any, of the array is set to null, and the created array is returned. + * The returned array is safe; it is not backed by the collection. Note that + * null may not mark the last element, if the collection allows null + * elements. * * @param a the array to copy into, or of the correct run-time type * @return the array that was produced - * @exception ClassCastException if the type of the array precludes holding - * one of the elements of the Collection + * @throws NullPointerException if the given array is null + * @throws ArrayStoreException if the type of the array precludes holding + * one of the elements of the Collection */ - public Object[] toArray(Object[]a) + public Object[] toArray(Object[] a) { int size = size(); if (a.length < size) - { - a = (Object[])Array.newInstance(a.getClass().getComponentType(), - size); - } + a = (Object[]) Array.newInstance(a.getClass().getComponentType(), + size); + else if (a.length > size) + a[size] = null; + Iterator itr = iterator(); for (int pos = 0; pos < size; pos++) - { - a[pos] = itr.next(); - } - if (a.length > size) - { - a[size] = null; - } + a[pos] = itr.next(); + return a; } @@ -331,15 +411,41 @@ public abstract class AbstractCollection implements Collection public String toString() { Iterator itr = iterator(); - int size = size(); StringBuffer r = new StringBuffer("["); - for (int pos = 0; pos < size; pos++) + for (int pos = size(); pos > 0; pos--) { - r.append(itr.next()); - if (pos < size - 1) - r.append(", "); + r.append(itr.next()); + if (pos > 1) + r.append(", "); } r.append("]"); return r.toString(); } + + /** + * Compare two objects according to Collection semantics. + * + * @param o1 the first object + * @param o2 the second object + * @return o1 == null ? o2 == null : o1.equals(o2) + */ + // Package visible for use throughout java.util. + // It may be inlined since it is final. + static final boolean equals(Object o1, Object o2) + { + return o1 == null ? o2 == null : o1.equals(o2); + } + + /** + * Hash an object according to Collection semantics. + * + * @param o the object to hash + * @return o1 == null ? 0 : o1.hashCode() + */ + // Package visible for use throughout java.util. + // It may be inlined since it is final. + static final int hashCode(Object o) + { + return o == null ? 0 : o.hashCode(); + } } diff --git a/libjava/java/util/AbstractList.java b/libjava/java/util/AbstractList.java index ba589e335e7..714f92a7bc4 100644 --- a/libjava/java/util/AbstractList.java +++ b/libjava/java/util/AbstractList.java @@ -1,5 +1,5 @@ /* AbstractList.java -- Abstract implementation of most of List - Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc. + Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -25,67 +25,192 @@ This exception does not however invalidate any other reasons why the executable file might be covered by the GNU General Public License. */ -// TO DO: -// ~ Doc comments for almost everything. -// ~ Better general commenting - package java.util; /** * A basic implementation of most of the methods in the List interface to make - * it easier to create a List based on a random-access data structure. To - * create an unmodifiable list, it is only necessary to override the size() and - * get(int) methods (this contrasts with all other abstract collection classes - * which require an iterator to be provided). To make the list modifiable, the - * set(int, Object) method should also be overridden, and to make the list - * resizable, the add(int, Object) and remove(int) methods should be overridden - * too. Other methods should be overridden if the backing data structure allows - * for a more efficient implementation. The precise implementation used by - * AbstractList is documented, so that subclasses can tell which methods could - * be implemented more efficiently. + * it easier to create a List based on a random-access data structure. If + * the list is sequential (such as a linked list), use AbstractSequentialList. + * To create an unmodifiable list, it is only necessary to override the + * size() and get(int) methods (this contrasts with all other abstract + * collection classes which require an iterator to be provided). To make the + * list modifiable, the set(int, Object) method should also be overridden, and + * to make the list resizable, the add(int, Object) and remove(int) methods + * should be overridden too. Other methods should be overridden if the + * backing data structure allows for a more efficient implementation. + * The precise implementation used by AbstractList is documented, so that + * subclasses can tell which methods could be implemented more efficiently. + * <p> + * + * As recommended by Collection and List, the subclass should provide at + * least a no-argument and a Collection constructor. This class is not + * synchronized. + * + * @author Original author unknown + * @author Bryce McKinlay + * @author Eric Blake <ebb9@email.byu.edu> + * @see Collection + * @see List + * @see AbstractSequentialList + * @see AbstractCollection + * @see ListIterator + * @since 1.2 + * @status updated to 1.4 */ public abstract class AbstractList extends AbstractCollection implements List { /** * A count of the number of structural modifications that have been made to - * the list (that is, insertions and removals). + * the list (that is, insertions and removals). Structural modifications + * are ones which change the list size or affect how iterations would + * behave. This field is available for use by Iterator and ListIterator, + * in order to throw a {@link ConcurrentModificationException} in response + * to the next operation on the iterator. This <i>fail-fast</i> behavior + * saves the user from many subtle bugs otherwise possible from concurrent + * modification during iteration. + * <p> + * + * To make lists fail-fast, increment this field by just 1 in the + * <code>add(int, Object)</code> and <code>remove(int)</code> methods. + * Otherwise, this field may be ignored. + */ + protected int modCount; + + /** + * The main constructor, for use by subclasses. */ - protected transient int modCount = 0; + protected AbstractList() + { + } + /** + * Returns the elements at the specified position in the list. + * + * @param index the element to return + * @return the element at that position + * @throws IndexOutOfBoundsException if index < 0 || index >= size() + */ public abstract Object get(int index); + /** + * Insert an element into the list at a given position (optional operation). + * This shifts all existing elements from that position to the end one + * index to the right. This version of add has no return, since it is + * assumed to always succeed if there is no exception. This implementation + * always throws UnsupportedOperationException, and must be overridden to + * make a modifiable List. If you want fail-fast iterators, be sure to + * increment modCount when overriding this. + * + * @param index the location to insert the item + * @param o the object to insert + * @throws UnsupportedOperationException if this list does not support the + * add operation + * @throws IndexOutOfBoundsException if index < 0 || index > size() + * @throws ClassCastException if o cannot be added to this list due to its + * type + * @throws IllegalArgumentException if o cannot be added to this list for + * some other reason + * @see #modCount + */ public void add(int index, Object o) { throw new UnsupportedOperationException(); } + /** + * Add an element to the end of the list (optional operation). If the list + * imposes restraints on what can be inserted, such as no null elements, + * this should be documented. This implementation calls + * <code>add(size(), o);</code>, and will fail if that version does. + * + * @param o the object to add + * @return true, as defined by Collection for a modified list + * @throws UnsupportedOperationException if this list does not support the + * add operation + * @throws ClassCastException if o cannot be added to this list due to its + * type + * @throws IllegalArgumentException if o cannot be added to this list for + * some other reason + * @see #add(int, Object) + */ public boolean add(Object o) { add(size(), o); return true; } + /** + * Insert the contents of a collection into the list at a given position + * (optional operation). Shift all elements at that position to the right + * by the number of elements inserted. This operation is undefined if + * this list is modified during the operation (for example, if you try + * to insert a list into itself). This implementation uses the iterator of + * the collection, repeatedly calling add(int, Object); this will fail + * if add does. This can often be made more efficient. + * + * @param index the location to insert the collection + * @param c the collection to insert + * @return true if the list was modified by this action, that is, if c is + * non-empty + * @throws UnsupportedOperationException if this list does not support the + * addAll operation + * @throws IndexOutOfBoundsException if index < 0 || index > size() + * @throws ClassCastException if some element of c cannot be added to this + * list due to its type + * @throws IllegalArgumentException if some element of c cannot be added + * to this list for some other reason + * @throws NullPointerException if the specified collection is null + * @see #add(int, Object) + */ public boolean addAll(int index, Collection c) { Iterator itr = c.iterator(); int size = c.size(); - for (int pos = 0; pos < size; pos++) - { - add(index++, itr.next()); - } - return (size > 0); + for (int pos = size; pos > 0; pos--) + add(index++, itr.next()); + return size > 0; } + /** + * Clear the list, such that a subsequent call to isEmpty() would return + * true (optional operation). This implementation calls + * <code>removeRange(0, size())</code>, so it will fail unless remove + * or removeRange is overridden. + * + * @throws UnsupportedOperationException if this list does not support the + * clear operation + * @see #remove(int) + * @see #removeRange(int, int) + */ public void clear() { removeRange(0, size()); } + /** + * Test whether this list is equal to another object. A List is defined to be + * equal to an object if and only if that object is also a List, and the two + * lists have the same sequence. Two lists l1 and l2 are equal if and only + * if <code>l1.size() == l2.size()</code>, and for every integer n between 0 + * and <code>l1.size() - 1</code> inclusive, <code>l1.get(n) == null ? + * l2.get(n) == null : l1.get(n).equals(l2.get(n))</code>. + * <p> + * + * This implementation returns true if the object is this, or false if the + * object is not a List. Otherwise, it iterates over both lists (with + * iterator()), returning false if two elements compare false or one list + * is shorter, and true if the iteration completes successfully. + * + * @param o the object to test for equality with this list + * @return true if o is equal to this list + * @see Object#equals(Object) + * @see #hashCode() + */ public boolean equals(Object o) { if (o == this) return true; - if (!(o instanceof List)) + if (! (o instanceof List)) return false; int size = size(); if (size != ((List) o).size()) @@ -94,77 +219,272 @@ public abstract class AbstractList extends AbstractCollection implements List Iterator itr1 = iterator(); Iterator itr2 = ((List) o).iterator(); - for (int pos = 0; pos < size; pos++) - { - Object e = itr1.next(); - if (e == null ? itr2.next() != null : !e.equals(itr2.next())) - return false; - } + while (--size >= 0) + if (! equals(itr1.next(), itr2.next())) + return false; return true; } + /** + * Obtain a hash code for this list. In order to obey the general contract of + * the hashCode method of class Object, this value is calculated as follows: + * <pre> + * hashCode = 1; + * Iterator i = list.iterator(); + * while (i.hasNext()) + * { + * Object obj = i.next(); + * hashCode = 31 * hashCode + (obj == null ? 0 : obj.hashCode()); + * } + * </pre> + * This ensures that the general contract of Object.hashCode() is adhered to. + * + * @return the hash code of this list + * @see Object#hashCode() + * @see #equals(Object) + */ public int hashCode() { int hashCode = 1; Iterator itr = iterator(); - int size = size(); - for (int pos = 0; pos < size; pos++) - { - Object obj = itr.next(); - hashCode = 31 * hashCode + (obj == null ? 0 : obj.hashCode()); - } + int pos = size(); + while (--pos >= 0) + hashCode = 31 * hashCode + hashCode(itr.next()); return hashCode; } + /** + * Obtain the first index at which a given object is to be found in this + * list. This implementation follows a listIterator() until a match is found, + * or returns -1 if the list end is reached. + * + * @param o the object to search for + * @return the least integer n such that <code>o == null ? get(n) == null : + * o.equals(get(n))</code>, or -1 if there is no such index + */ public int indexOf(Object o) { - ListIterator itr = listIterator(0); + ListIterator itr = listIterator(); int size = size(); for (int pos = 0; pos < size; pos++) - { - if (o == null ? itr.next() == null : o.equals(itr.next())) - return pos; - } + if (equals(o, itr.next())) + return pos; return -1; } + /** + * Obtain an Iterator over this list, whose sequence is the list order. + * This implementation uses size(), get(int), and remove(int) of the + * backing list, and does not support remove unless the list does. This + * implementation is fail-fast if you correctly maintain modCount. + * Also, this implementation is specified by Sun to be distinct from + * listIterator, although you could easily implement it as + * <code>return listIterator(0)</code>. + * + * @return an Iterator over the elements of this list, in order + * @see #modCount + */ public Iterator iterator() { - return new AbstractListItr(0); + // Bah, Sun's implementation forbids using listIterator(0). + return new Iterator() + { + private int pos = 0; + private int size = size(); + private int last = -1; + private int knownMod = modCount; + + // This will get inlined, since it is private. + private void checkMod() + { + if (knownMod != modCount) + throw new ConcurrentModificationException(); + } + + public boolean hasNext() + { + checkMod(); + return pos < size; + } + + public Object next() + { + checkMod(); + if (pos == size) + throw new NoSuchElementException(); + last = pos; + return get(pos++); + } + + public void remove() + { + checkMod(); + if (last < 0) + throw new IllegalStateException(); + AbstractList.this.remove(last); + pos--; + size--; + last = -1; + knownMod = modCount; + } + }; } + /** + * Obtain the last index at which a given object is to be found in this + * list. This implementation grabs listIterator(size()), then searches + * backwards for a match or returns -1. + * + * @return the greatest integer n such that <code>o == null ? get(n) == null + * : o.equals(get(n))</code>, or -1 if there is no such index + */ public int lastIndexOf(Object o) { - int size = size(); - ListIterator itr = listIterator(size); - for (int pos = size; pos > 0; pos--) - { - if (o == null ? itr.previous() == null : o.equals(itr.previous())) - return pos - 1; - } + int pos = size(); + ListIterator itr = listIterator(pos); + while (--pos >= 0) + if (equals(o, itr.previous())) + return pos; return -1; } /** - * Return an Iterator over this List. This implementation calls - * listIterator(0). + * Obtain a ListIterator over this list, starting at the beginning. This + * implementation returns listIterator(0). * - * @return an Iterator over this List + * @return a ListIterator over the elements of this list, in order, starting + * at the beginning */ public ListIterator listIterator() { return listIterator(0); } - public ListIterator listIterator(int index) + /** + * Obtain a ListIterator over this list, starting at a given position. + * A first call to next() would return the same as get(index), and a + * first call to previous() would return the same as get(index - 1). + * <p> + * + * This implementation uses size(), get(int), set(int, Object), + * add(int, Object), and remove(int) of the backing list, and does not + * support remove, set, or add unless the list does. This implementation + * is fail-fast if you correctly maintain modCount. + * + * @param index the position, between 0 and size() inclusive, to begin the + * iteration from + * @return a ListIterator over the elements of this list, in order, starting + * at index + * @throws IndexOutOfBoundsException if index < 0 || index > size() + * @see #modCount + */ + public ListIterator listIterator(final int index) { if (index < 0 || index > size()) - throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + - size()); + throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + + size()); - return new AbstractListItr(index); + return new ListIterator() + { + private int knownMod = modCount; + private int position = index; + private int lastReturned = -1; + private int size = size(); + + // This will get inlined, since it is private. + private void checkMod() + { + if (knownMod != modCount) + throw new ConcurrentModificationException(); + } + + public boolean hasNext() + { + checkMod(); + return position < size; + } + + public boolean hasPrevious() + { + checkMod(); + return position > 0; + } + + public Object next() + { + checkMod(); + if (position == size) + throw new NoSuchElementException(); + lastReturned = position; + return get(position++); + } + + public Object previous() + { + checkMod(); + if (position == 0) + throw new NoSuchElementException(); + lastReturned = --position; + return get(lastReturned); + } + + public int nextIndex() + { + checkMod(); + return position; + } + + public int previousIndex() + { + checkMod(); + return position - 1; + } + + public void remove() + { + checkMod(); + if (lastReturned < 0) + throw new IllegalStateException(); + AbstractList.this.remove(lastReturned); + size--; + position = lastReturned; + lastReturned = -1; + knownMod = modCount; + } + + public void set(Object o) + { + checkMod(); + if (lastReturned < 0) + throw new IllegalStateException(); + AbstractList.this.set(lastReturned, o); + } + + public void add(Object o) + { + checkMod(); + AbstractList.this.add(position++, o); + size++; + lastReturned = -1; + knownMod = modCount; + } + }; } + /** + * Remove the element at a given position in this list (optional operation). + * Shifts all remaining elements to the left to fill the gap. This + * implementation always throws an UnsupportedOperationException. + * If you want fail-fast iterators, be sure to increment modCount when + * overriding this. + * + * @param index the position within the list of the object to remove + * @return the object that was removed + * @throws UnsupportedOperationException if this list does not support the + * remove operation + * @throws IndexOutOfBoundsException if index < 0 || index >= size() + * @see #modCount + */ public Object remove(int index) { throw new UnsupportedOperationException(); @@ -175,8 +495,10 @@ public abstract class AbstractList extends AbstractCollection implements List * removeRange methods of the class which implements subList, which are * difficult for subclasses to override directly. Therefore, this method * should be overridden instead by the more efficient implementation, if one - * exists. + * exists. Overriding this can reduce quadratic efforts to constant time + * in some cases! * <p> + * * This implementation first checks for illegal or out of range arguments. It * then obtains a ListIterator over the list using listIterator(fromIndex). * It then calls next() and remove() on this iterator repeatedly, toIndex - @@ -190,152 +512,131 @@ public abstract class AbstractList extends AbstractCollection implements List ListIterator itr = listIterator(fromIndex); for (int index = fromIndex; index < toIndex; index++) { - itr.next(); - itr.remove(); + itr.next(); + itr.remove(); } } + /** + * Replace an element of this list with another object (optional operation). + * This implementation always throws an UnsupportedOperationException. + * + * @param index the position within this list of the element to be replaced + * @param o the object to replace it with + * @return the object that was replaced + * @throws UnsupportedOperationException if this list does not support the + * set operation + * @throws IndexOutOfBoundsException if index < 0 || index >= size() + * @throws ClassCastException if o cannot be added to this list due to its + * type + * @throws IllegalArgumentException if o cannot be added to this list for + * some other reason + */ public Object set(int index, Object o) { throw new UnsupportedOperationException(); } + /** + * Obtain a List view of a subsection of this list, from fromIndex + * (inclusive) to toIndex (exclusive). If the two indices are equal, the + * sublist is empty. The returned list should be modifiable if and only + * if this list is modifiable. Changes to the returned list should be + * reflected in this list. If this list is structurally modified in + * any way other than through the returned list, the result of any subsequent + * operations on the returned list is undefined. + * <p> + * + * This implementation returns a subclass of AbstractList. It stores, in + * private fields, the offset and size of the sublist, and the expected + * modCount of the backing list. If the backing list implements RandomAccess, + * the sublist will also. + * <p> + * + * The subclass's <code>set(int, Object)</code>, <code>get(int)</code>, + * <code>add(int, Object)</code>, <code>remove(int)</code>, + * <code>addAll(int, Collection)</code> and + * <code>removeRange(int, int)</code> methods all delegate to the + * corresponding methods on the backing abstract list, after + * bounds-checking the index and adjusting for the offset. The + * <code>addAll(Collection c)</code> method merely returns addAll(size, c). + * The <code>listIterator(int)</code> method returns a "wrapper object" + * over a list iterator on the backing list, which is created with the + * corresponding method on the backing list. The <code>iterator()</code> + * method merely returns listIterator(), and the <code>size()</code> method + * merely returns the subclass's size field. + * <p> + * + * All methods first check to see if the actual modCount of the backing + * list is equal to its expected value, and throw a + * ConcurrentModificationException if it is not. + * + * @param fromIndex the index that the returned list should start from + * (inclusive) + * @param toIndex the index that the returned list should go to (exclusive) + * @return a List backed by a subsection of this list + * @throws IndexOutOfBoundsException if fromIndex < 0 + * || toIndex > size() + * @throws IllegalArgumentException if fromIndex > toIndex + * @see ConcurrentModificationException + * @see RandomAccess + */ public List subList(int fromIndex, int toIndex) { + // This follows the specification of AbstractList, but is inconsistent + // with the one in List. Don't you love Sun's inconsistencies? if (fromIndex > toIndex) throw new IllegalArgumentException(fromIndex + " > " + toIndex); if (fromIndex < 0 || toIndex > size()) throw new IndexOutOfBoundsException(); + if (this instanceof RandomAccess) + return new RandomAccessSubList(this, fromIndex, toIndex); return new SubList(this, fromIndex, toIndex); } - class AbstractListItr implements ListIterator - { - private int knownMod = modCount; - private int position; - private int lastReturned = -1; - private int size = size(); - - AbstractListItr(int start_pos) - { - this.position = start_pos; - } - - private void checkMod() - { - if (knownMod != modCount) - throw new ConcurrentModificationException(); - } - - public boolean hasNext() - { - checkMod(); - return position < size; - } - - public boolean hasPrevious() - { - checkMod(); - return position > 0; - } - - public Object next() - { - checkMod(); - if (position < size) - { - lastReturned = position++; - return get(lastReturned); - } - else - { - throw new NoSuchElementException(); - } - } - - public Object previous() - { - checkMod(); - if (position > 0) - { - lastReturned = --position; - return get(lastReturned); - } - else - { - throw new NoSuchElementException(); - } - } - - public int nextIndex() - { - checkMod(); - return position; - } - - public int previousIndex() - { - checkMod(); - return position - 1; - } - - public void remove() - { - checkMod(); - if (lastReturned < 0) - { - throw new IllegalStateException(); - } - AbstractList.this.remove(lastReturned); - knownMod++; - position = lastReturned; - lastReturned = -1; - } - - public void set(Object o) - { - checkMod(); - if (lastReturned < 0) - throw new IllegalStateException(); - AbstractList.this.set(lastReturned, o); - } - - public void add(Object o) - { - checkMod(); - AbstractList.this.add(position++, o); - lastReturned = -1; - knownMod++; - } - } // AbstractList.Iterator -} - +} // class AbstractList +/** + * This class follows the implementation requirements set forth in + * {@link AbstractList#subList(int, int)}. Some compilers have problems + * with AbstractList.this.modCount if this class is nested in AbstractList, + * even though the JLS defines that to be legal, so we make it a top-level + * class. + * + * @author Original author unknown + * @author Eric Blake <ebb9@email.byu.edu> + */ class SubList extends AbstractList { - private AbstractList backingList; - private int offset; + private final AbstractList backingList; + private final int offset; private int size; - public SubList(AbstractList backing, int fromIndex, int toIndex) + /** + * Construct the sublist. + * + * @param backing the list this comes from + * @param fromIndex the lower bound, inclusive + * @param toIndex the upper bound, exclusive + */ + SubList(AbstractList backing, int fromIndex, int toIndex) { backingList = backing; - modCount = backingList.modCount; + modCount = backing.modCount; offset = fromIndex; size = toIndex - fromIndex; } /** * This method checks the two modCount fields to ensure that there has - * not been a concurrent modification. It throws an exception if there - * has been, and otherwise returns normally. - * Note that since this method is private, it will be inlined. + * not been a concurrent modification, returning if all is okay. * - * @exception ConcurrentModificationException if there has been a - * concurrent modification. + * @throws ConcurrentModificationException if the backing list has been + * modified externally to this sublist */ + // This will get inlined, since it is private. private void checkMod() { if (modCount != backingList.modCount) @@ -345,45 +646,64 @@ class SubList extends AbstractList /** * This method checks that a value is between 0 and size (inclusive). If * it is not, an exception is thrown. - * Note that since this method is private, it will be inlined. * - * @exception IndexOutOfBoundsException if the value is out of range. + * @param index the value to check + * @throws IndexOutOfBoundsException if the value is out of range */ + // This will get inlined, since it is private. private void checkBoundsInclusive(int index) { if (index < 0 || index > size) - throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + - size); + throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + + size); } /** * This method checks that a value is between 0 (inclusive) and size * (exclusive). If it is not, an exception is thrown. - * Note that since this method is private, it will be inlined. * - * @exception IndexOutOfBoundsException if the value is out of range. + * @param index the value to check + * @throws IndexOutOfBoundsException if the value is out of range */ + // This will get inlined, since it is private. private void checkBoundsExclusive(int index) { if (index < 0 || index >= size) - throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + - size); + throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + + size); } + /** + * Specified by AbstractList.subList to return the private field size. + * + * @return the sublist size + */ public int size() { checkMod(); return size; } + /** + * Specified by AbstractList.subList to delegate to the backing list. + * + * @param index the location to modify + * @param o the new value + * @return the old value + */ public Object set(int index, Object o) { checkMod(); checkBoundsExclusive(index); - o = backingList.set(index + offset, o); - return o; + return backingList.set(index + offset, o); } + /** + * Specified by AbstractList.subList to delegate to the backing list. + * + * @param index the location to get from + * @return the object at that location + */ public Object get(int index) { checkMod(); @@ -391,62 +711,109 @@ class SubList extends AbstractList return backingList.get(index + offset); } + /** + * Specified by AbstractList.subList to delegate to the backing list. + * + * @param index the index to insert at + * @param o the object to add + */ public void add(int index, Object o) { checkMod(); checkBoundsInclusive(index); backingList.add(index + offset, o); - this.modCount++; size++; + modCount = backingList.modCount; } + /** + * Specified by AbstractList.subList to delegate to the backing list. + * + * @param index the index to remove + * @return the removed object + */ public Object remove(int index) { checkMod(); checkBoundsExclusive(index); Object o = backingList.remove(index + offset); - this.modCount++; size--; + modCount = backingList.modCount; return o; } - public void removeRange(int fromIndex, int toIndex) + /** + * Specified by AbstractList.subList to delegate to the backing list. + * This does no bounds checking, as it assumes it will only be called + * by trusted code like clear() which has already checked the bounds. + * + * @param fromIndex the lower bound, inclusive + * @param toIndex the upper bound, exclusive + */ + protected void removeRange(int fromIndex, int toIndex) { checkMod(); - checkBoundsExclusive(fromIndex); - checkBoundsInclusive(toIndex); - // this call will catch the toIndex < fromIndex condition backingList.removeRange(offset + fromIndex, offset + toIndex); - this.modCount = backingList.modCount; size -= toIndex - fromIndex; + modCount = backingList.modCount; } + /** + * Specified by AbstractList.subList to delegate to the backing list. + * + * @param index the location to insert at + * @param c the collection to insert + * @return true if this list was modified, in other words, c is non-empty + */ public boolean addAll(int index, Collection c) { checkMod(); checkBoundsInclusive(index); int csize = c.size(); boolean result = backingList.addAll(offset + index, c); - this.modCount = backingList.modCount; size += csize; + modCount = backingList.modCount; return result; } + /** + * Specified by AbstractList.subList to return addAll(size, c). + * + * @param c the collection to insert + * @return true if this list was modified, in other words, c is non-empty + */ + public boolean addAll(Collection c) + { + return addAll(size, c); + } + + /** + * Specified by AbstractList.subList to return listIterator(). + * + * @return an iterator over the sublist + */ public Iterator iterator() { - return listIterator(0); + return listIterator(); } + /** + * Specified by AbstractList.subList to return a wrapper around the + * backing list's iterator. + * + * @param index the start location of the iterator + * @return a list iterator over the sublist + */ public ListIterator listIterator(final int index) - { + { checkMod(); checkBoundsInclusive(index); - return new ListIterator() + return new ListIterator() { - ListIterator i = backingList.listIterator(index + offset); - int position = index; + private final ListIterator i = backingList.listIterator(index + offset); + private int position = index; public boolean hasNext() { @@ -462,44 +829,36 @@ class SubList extends AbstractList public Object next() { - if (position < size) - { - Object o = i.next(); - position++; - return o; - } - else + if (position == size) throw new NoSuchElementException(); + position++; + return i.next(); } public Object previous() { - if (position > 0) - { - Object o = i.previous(); - position--; - return o; - } - else + if (position == 0) throw new NoSuchElementException(); + position--; + return i.previous(); } public int nextIndex() { - return offset + i.nextIndex(); + return i.nextIndex() - offset; } public int previousIndex() { - return offset + i.previousIndex(); + return i.previousIndex() - offset; } public void remove() { i.remove(); - modCount++; size--; position = nextIndex(); + modCount = backingList.modCount; } public void set(Object o) @@ -510,14 +869,14 @@ class SubList extends AbstractList public void add(Object o) { i.add(o); - modCount++; size++; position++; + modCount = backingList.modCount; } // Here is the reason why the various modCount fields are mostly // ignored in this wrapper listIterator. - // IF the backing listIterator is failfast, then the following holds: + // If the backing listIterator is failfast, then the following holds: // Using any other method on this list will call a corresponding // method on the backing list *after* the backing listIterator // is created, which will in turn cause a ConcurrentModException @@ -530,9 +889,31 @@ class SubList extends AbstractList // only, but somewhat pointless when the list can be changed under // us. // Either way, no explicit handling of modCount is needed. - // However modCount++ must be executed in add and remove, and size - // must also be updated in these two methods, since they do not go - // through the corresponding methods of the subList. + // However modCount = backingList.modCount must be executed in add + // and remove, and size must also be updated in these two methods, + // since they do not go through the corresponding methods of the subList. }; } -} // SubList +} // class SubList + +/** + * This class is a RandomAccess version of SubList, as required by + * {@link AbstractList#subList(int, int)}. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ +final class RandomAccessSubList extends SubList + implements RandomAccess +{ + /** + * Construct the sublist. + * + * @param backing the list this comes from + * @param fromIndex the lower bound, inclusive + * @param toIndex the upper bound, exclusive + */ + RandomAccessSubList(AbstractList backing, int fromIndex, int toIndex) + { + super(backing, fromIndex, toIndex); + } +} // class RandomAccessSubList diff --git a/libjava/java/util/AbstractMap.java b/libjava/java/util/AbstractMap.java index e28ce919beb..f8cf79f29b0 100644 --- a/libjava/java/util/AbstractMap.java +++ b/libjava/java/util/AbstractMap.java @@ -1,5 +1,5 @@ /* AbstractMap.java -- Abstract implementation of most of Map - Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc. + Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -25,22 +25,71 @@ This exception does not however invalidate any other reasons why the executable file might be covered by the GNU General Public License. */ -// TO DO: -// comments -// test suite - package java.util; +/** + * An abstract implementation of Map to make it easier to create your own + * implementations. In order to create an unmodifiable Map, subclass + * AbstractMap and implement the <code>entrySet</code> (usually via an + * AbstractSet). To make it modifiable, also implement <code>put</code>, + * and have <code>entrySet().iterator()</code> support <code>remove</code>. + * <p> + * + * It is recommended that classes which extend this support at least the + * no-argument constructor, and a constructor which accepts another Map. + * Further methods in this class may be overridden if you have a more + * efficient implementation. + * + * @author Original author unknown + * @author Bryce McKinlay + * @author Eric Blake <ebb9@email.byu.edu> + * @see Map + * @see Collection + * @see HashMap + * @see LinkedHashMap + * @see TreeMap + * @see WeakHashMap + * @see IdentityHashMap + * @since 1.2 + * @status updated to 1.4 + */ public abstract class AbstractMap implements Map { + /** An "enum" of iterator types. */ + // Package visible for use by subclasses. + static final int KEYS = 0, + VALUES = 1, + ENTRIES = 2; + + /** + * The cache for {@link #keySet()}. + */ + // Package visible for use by subclasses. + Set keys; + + /** + * The cache for {@link #values()}. + */ + // Package visible for use by subclasses. + Collection values; + /** - * Remove all entries from this Map. This default implementation calls - * entrySet().clear(). + * The main constructor, for use by subclasses. + */ + protected AbstractMap() + { + } + + /** + * Remove all entries from this Map (optional operation). This default + * implementation calls entrySet().clear(). NOTE: If the entry set does + * not permit clearing, then this will fail, too. Subclasses often + * override this for efficiency. Your implementation of entrySet() should + * not call <code>AbstractMap.clear</code> unless you want an infinite loop. * - * @throws UnsupportedOperationException - * @specnote The JCL book claims that this implementation always throws - * UnsupportedOperationException, while the online docs claim it - * calls entrySet().clear(). We take the later to be correct. + * @throws UnsupportedOperationException if <code>entrySet().clear()</code> + * does not support clearing. + * @see Set#clear() */ public void clear() { @@ -48,246 +97,414 @@ public abstract class AbstractMap implements Map } /** - * Create a shallow copy of this Map, no keys or values are copied. + * Create a shallow copy of this Map, no keys or values are copied. The + * default implementation simply calls <code>super.clone()</code>. + * + * @return the shallow clone + * @throws CloneNotSupportedException if a subclass is not Cloneable + * @see Cloneable + * @see Object#clone() */ - protected Object clone () throws CloneNotSupportedException + protected Object clone() throws CloneNotSupportedException { - return super.clone (); + AbstractMap copy = (AbstractMap) super.clone(); + // Clear out the caches; they are stale. + copy.keys = null; + copy.values = null; + return copy; } + /** + * Returns true if this contains a mapping for the given key. This + * implementation does a linear search, O(n), over the + * <code>entrySet()</code>, returning <code>true</code> if a match + * is found, <code>false</code> if the iteration ends. Many subclasses + * can implement this more efficiently. + * + * @param key the key to search for + * @return true if the map contains the key + * @throws NullPointerException if key is <code>null</code> but the map + * does not permit null keys + * @see #containsValue(Object) + */ public boolean containsKey(Object key) { - Object k; - Set es = entrySet(); - Iterator entries = es.iterator(); - int size = size(); - for (int pos = 0; pos < size; pos++) - { - k = ((Map.Entry) entries.next()).getKey(); - if (key == null ? k == null : key.equals(k)) - return true; - } + Iterator entries = entrySet().iterator(); + int pos = size(); + while (--pos >= 0) + if (equals(key, ((Map.Entry) entries.next()).getKey())) + return true; return false; } + /** + * Returns true if this contains at least one mapping with the given value. + * This implementation does a linear search, O(n), over the + * <code>entrySet()</code>, returning <code>true</code> if a match + * is found, <code>false</code> if the iteration ends. A match is + * defined as <code>(value == null ? v == null : value.equals(v))</code> + * Subclasses are unlikely to implement this more efficiently. + * + * @param value the value to search for + * @return true if the map contains the value + * @see #containsKey(Object) + */ public boolean containsValue(Object value) { - Object v; - Set es = entrySet(); - Iterator entries = es.iterator(); - int size = size(); - for (int pos = 0; pos < size; pos++) - { - v = ((Map.Entry) entries.next()).getValue(); - if (value == null ? v == null : value.equals(v)) - return true; - } + Iterator entries = entrySet().iterator(); + int pos = size(); + while (--pos >= 0) + if (equals(value, ((Map.Entry) entries.next()).getValue())) + return true; return false; } + /** + * Returns a set view of the mappings in this Map. Each element in the + * set must be an implementation of Map.Entry. The set is backed by + * the map, so that changes in one show up in the other. Modifications + * made while an iterator is in progress cause undefined behavior. If + * the set supports removal, these methods must be valid: + * <code>Iterator.remove</code>, <code>Set.remove</code>, + * <code>removeAll</code>, <code>retainAll</code>, and <code>clear</code>. + * Element addition is not supported via this set. + * + * @return the entry set + * @see Map.Entry + */ public abstract Set entrySet(); + /** + * Compares the specified object with this map for equality. Returns + * <code>true</code> if the other object is a Map with the same mappings, + * that is,<br> + * <code>o instanceof Map && entrySet().equals(((Map) o).entrySet();</code> + * + * @param o the object to be compared + * @return true if the object equals this map + * @see Set#equals(Object) + */ public boolean equals(Object o) { - if (o == this) - return true; - if (!(o instanceof Map)) - return false; - - Map m = (Map) o; - Set s = m.entrySet(); - Iterator itr = entrySet().iterator(); - int size = size(); - - if (m.size() != size) - return false; - - for (int pos = 0; pos < size; pos++) - { - if (!s.contains(itr.next())) - return false; - } - return true; + return (o == this || + (o instanceof Map && + entrySet().equals(((Map) o).entrySet()))); } + /** + * Returns the value mapped by the given key. Returns <code>null</code> if + * there is no mapping. However, in Maps that accept null values, you + * must rely on <code>containsKey</code> to determine if a mapping exists. + * This iteration takes linear time, searching entrySet().iterator() of + * the key. Many implementations override this method. + * + * @param key the key to look up + * @return the value associated with the key, or null if key not in map + * @throws NullPointerException if this map does not accept null keys + * @see #containsKey(Object) + */ public Object get(Object key) { - Set s = entrySet(); - Iterator entries = s.iterator(); - int size = size(); - - for (int pos = 0; pos < size; pos++) + Iterator entries = entrySet().iterator(); + int pos = size(); + while (--pos >= 0) { - Map.Entry entry = (Map.Entry) entries.next(); - Object k = entry.getKey(); - if (key == null ? k == null : key.equals(k)) - return entry.getValue(); + Map.Entry entry = (Map.Entry) entries.next(); + if (equals(key, entry.getKey())) + return entry.getValue(); } - return null; } + /** + * Returns the hash code for this map. As defined in Map, this is the sum + * of all hashcodes for each Map.Entry object in entrySet, or basically + * entrySet().hashCode(). + * + * @return the hash code + * @see Map.Entry#hashCode() + * @see Set#hashCode() + */ public int hashCode() { - int hashcode = 0; - Iterator itr = entrySet().iterator(); - int size = size(); - for (int pos = 0; pos < size; pos++) - { - hashcode += itr.next().hashCode(); - } - return hashcode; + return entrySet().hashCode(); } + /** + * Returns true if the map contains no mappings. This is implemented by + * <code>size() == 0</code>. + * + * @return true if the map is empty + * @see #size() + */ public boolean isEmpty() { return size() == 0; } + /** + * Returns a set view of this map's keys. The set is backed by the map, + * so changes in one show up in the other. Modifications while an iteration + * is in progress produce undefined behavior. The set supports removal + * if entrySet() does, but does not support element addition. + * <p> + * + * This implementation creates an AbstractSet, where the iterator wraps + * the entrySet iterator, size defers to the Map's size, and contains + * defers to the Map's containsKey. The set is created on first use, and + * returned on subsequent uses, although since no synchronization occurs, + * there is a slight possibility of creating two sets. + * + * @return a Set view of the keys + * @see Set#iterator() + * @see #size() + * @see #containsKey(Object) + * @see #values() + */ public Set keySet() { - if (this.keySet == null) + if (keys == null) + keys = new AbstractSet() { - this.keySet = new AbstractSet() - { - public int size() - { - return AbstractMap.this.size(); - } - - public boolean contains(Object key) - { - return AbstractMap.this.containsKey(key); - } - - public Iterator iterator() - { - return new Iterator() - { - Iterator map_iterator = AbstractMap.this.entrySet().iterator(); - - public boolean hasNext() - { - return map_iterator.hasNext(); - } - - public Object next() - { - return ((Map.Entry) map_iterator.next()).getKey(); - } - - public void remove() - { - map_iterator.remove(); - } - }; - } - }; - } - - return this.keySet; + public int size() + { + return AbstractMap.this.size(); + } + + public boolean contains(Object key) + { + return containsKey(key); + } + + public Iterator iterator() + { + return new Iterator() + { + private final Iterator map_iterator = entrySet().iterator(); + + public boolean hasNext() + { + return map_iterator.hasNext(); + } + + public Object next() + { + return ((Map.Entry) map_iterator.next()).getKey(); + } + + public void remove() + { + map_iterator.remove(); + } + }; + } + }; + return keys; } + /** + * Associates the given key to the given value (optional operation). If the + * map already contains the key, its value is replaced. This implementation + * simply throws an UnsupportedOperationException. Be aware that in a map + * that permits <code>null</code> values, a null return does not always + * imply that the mapping was created. + * + * @param key the key to map + * @param value the value to be mapped + * @return the previous value of the key, or null if there was no mapping + * @throws UnsupportedOperationException if the operation is not supported + * @throws ClassCastException if the key or value is of the wrong type + * @throws IllegalArgumentException if something about this key or value + * prevents it from existing in this map + * @throws NullPointerException if the map forbids null keys or values + * @see #containsKey(Object) + */ public Object put(Object key, Object value) { throw new UnsupportedOperationException(); } + /** + * Copies all entries of the given map to this one (optional operation). If + * the map already contains a key, its value is replaced. This implementation + * simply iterates over the map's entrySet(), calling <code>put</code>, + * so it is not supported if puts are not. + * + * @param m the mapping to load into this map + * @throws UnsupportedOperationException if the operation is not supported + * @throws ClassCastException if a key or value is of the wrong type + * @throws IllegalArgumentException if something about a key or value + * prevents it from existing in this map + * @throws NullPointerException if the map forbids null keys or values, or + * if <code>m</code> is null. + * @see #put(Object, Object) + */ public void putAll(Map m) { - Map.Entry entry; Iterator entries = m.entrySet().iterator(); - int size = m.size(); - - for (int pos = 0; pos < size; pos++) + int pos = size(); + while (--pos >= 0) { - entry = (Map.Entry) entries.next(); - put(entry.getKey(), entry.getValue()); + Map.Entry entry = (Map.Entry) entries.next(); + put(entry.getKey(), entry.getValue()); } } + /** + * Removes the mapping for this key if present (optional operation). This + * implementation iterates over the entrySet searching for a matching + * key, at which point it calls the iterator's <code>remove</code> method. + * It returns the result of <code>getValue()</code> on the entry, if found, + * or null if no entry is found. Note that maps which permit null values + * may also return null if the key was removed. If the entrySet does not + * support removal, this will also fail. This is O(n), so many + * implementations override it for efficiency. + * + * @param key the key to remove + * @return the value the key mapped to, or null if not present + * @throws UnsupportedOperationException if deletion is unsupported + * @see Iterator#remove() + */ public Object remove(Object key) { Iterator entries = entrySet().iterator(); - int size = size(); - - for (int pos = 0; pos < size; pos++) + int pos = size(); + while (--pos >= 0) { - Map.Entry entry = (Map.Entry) entries.next(); - Object k = entry.getKey(); - if (key == null ? k == null : key.equals(k)) - { - Object value = entry.getValue(); - entries.remove(); - return value; - } + Map.Entry entry = (Map.Entry) entries.next(); + if (equals(key, entry.getKey())) + { + // Must get the value before we remove it from iterator. + Object r = entry.getValue(); + entries.remove(); + return r; + } } - return null; } + /** + * Returns the number of key-value mappings in the map. If there are more + * than Integer.MAX_VALUE mappings, return Integer.MAX_VALUE. This is + * implemented as <code>entrySet().size()</code>. + * + * @return the number of mappings + * @see Set#size() + */ public int size() { return entrySet().size(); } + /** + * Returns a String representation of this map. This is a listing of the + * map entries (which are specified in Map.Entry as being + * <code>getKey() + "=" + getValue()</code>), separated by a comma and + * space (", "), and surrounded by braces ('{' and '}'). This implementation + * uses a StringBuffer and iterates over the entrySet to build the String. + * Note that this can fail with an exception if underlying keys or + * values complete abruptly in toString(). + * + * @return a String representation + * @see Map.Entry#toString() + */ public String toString() { Iterator entries = entrySet().iterator(); - int size = size(); StringBuffer r = new StringBuffer("{"); - for (int pos = 0; pos < size; pos++) + for (int pos = size(); pos > 0; pos--) { - // Append the toString value of the entries rather than calling - // getKey/getValue. This is more efficient and it matches the JDK - // behaviour. - r.append(entries.next()); - if (pos < size - 1) - r.append(", "); + // Append the toString value of the entries rather than calling + // getKey/getValue. This is more efficient and it matches the JDK + // behaviour. + r.append(entries.next()); + if (pos > 1) + r.append(", "); } r.append("}"); return r.toString(); } + /** + * Returns a collection or bag view of this map's values. The collection + * is backed by the map, so changes in one show up in the other. + * Modifications while an iteration is in progress produce undefined + * behavior. The collection supports removal if entrySet() does, but + * does not support element addition. + * <p> + * + * This implementation creates an AbstractCollection, where the iterator + * wraps the entrySet iterator, size defers to the Map's size, and contains + * defers to the Map's containsValue. The collection is created on first + * use, and returned on subsequent uses, although since no synchronization + * occurs, there is a slight possibility of creating two collections. + * + * @return a Collection view of the values + * @see Collection#iterator() + * @see #size() + * @see #containsValue(Object) + * @see #keySet() + */ public Collection values() { - if (this.valueCollection == null) + if (values == null) + values = new AbstractCollection() { - this.valueCollection = new AbstractCollection() - { - public int size() - { - return AbstractMap.this.size(); - } - - public Iterator iterator() - { - return new Iterator() - { - Iterator map_iterator = AbstractMap.this.entrySet().iterator(); - - public boolean hasNext() - { - return map_iterator.hasNext(); - } - - public Object next() - { - return ((Map.Entry) map_iterator.next()).getValue(); - } - - public void remove() - { - map_iterator.remove(); - } - }; - } - }; - } + public int size() + { + return AbstractMap.this.size(); + } + + public Iterator iterator() + { + return new Iterator() + { + private final Iterator map_iterator = entrySet().iterator(); + + public boolean hasNext() + { + return map_iterator.hasNext(); + } + + public Object next() + { + return ((Map.Entry) map_iterator.next()).getValue(); + } + + public void remove() + { + map_iterator.remove(); + } + }; + } + }; + return values; + } - return this.valueCollection; + /** + * Compare two objects according to Collection semantics. + * + * @param o1 the first object + * @param o2 the second object + * @return o1 == null ? o2 == null : o1.equals(o2) + */ + // Package visible for use throughout java.util. + // It may be inlined since it is final. + static final boolean equals(Object o1, Object o2) + { + return o1 == null ? o2 == null : o1.equals(o2); } - private Collection valueCollection = null; - private Set keySet = null; + /** + * Hash an object according to Collection semantics. + * + * @param o the object to hash + * @return o1 == null ? 0 : o1.hashCode() + */ + // Package visible for use throughout java.util. + // It may be inlined since it is final. + static final int hashCode(Object o) + { + return o == null ? 0 : o.hashCode(); + } } diff --git a/libjava/java/util/AbstractSequentialList.java b/libjava/java/util/AbstractSequentialList.java index 81481be0466..2267b02e523 100644 --- a/libjava/java/util/AbstractSequentialList.java +++ b/libjava/java/util/AbstractSequentialList.java @@ -1,5 +1,5 @@ /* AbstractSequentialList.java -- List implementation for sequential access - Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc. + Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -25,100 +25,192 @@ This exception does not however invalidate any other reasons why the executable file might be covered by the GNU General Public License. */ -// TO DO: -// ~ Lots of doc comments still missing. -// ~ The class comment should include a description of what should be overridden -// to provide what features, as should the listIterator comment. - package java.util; /** * Abstract superclass to make it easier to implement the List interface when - * backed by a sequential-access store, such as a linked list. + * backed by a sequential-access store, such as a linked list. For random + * access data, use AbstractList. This class implements the random access + * methods (<code>get</code>, <code>set</code>, <code>add</code>, and + * <code>remove</code>) atop the list iterator, opposite of AbstractList's + * approach of implementing the iterator atop random access. + * <p> + * + * To implement a list, you need an implementation for <code>size()</code> + * and <code>listIterator</code>. With just <code>hasNext</code>, + * <code>next</code>, <code>hasPrevious</code>, <code>previous</code>, + * <code>nextIndex</code>, and <code>previousIndex</code>, you have an + * unmodifiable list. For a modifiable one, add <code>set</code>, and for + * a variable-size list, add <code>add</code> and <code>remove</code>. + * <p> + * + * The programmer should provide a no-argument constructor, and one that + * accepts another Collection, as recommended by the Collection interface. + * Unfortunately, there is no way to enforce this in Java. + * + * @author Original author unknown + * @author Bryce McKinlay + * @author Eric Blake <ebb9@email.byu.edu> + * @see Collection + * @see List + * @see AbstractList + * @see AbstractCollection + * @see ListIterator + * @see LinkedList + * @since 1.2 + * @status updated to 1.4 */ public abstract class AbstractSequentialList extends AbstractList { /** + * The main constructor, for use by subclasses. + */ + protected AbstractSequentialList() + { + } + + /** * Returns a ListIterator over the list, starting from position index. * Subclasses must provide an implementation of this method. * - * @exception IndexOutOfBoundsException if index < 0 || index > size() + * @param index the starting position of the list + * @return the list iterator + * @throws IndexOutOfBoundsException if index < 0 || index > size() */ public abstract ListIterator listIterator(int index); /** - * Add an element to the list at a given index. This implementation obtains a - * ListIterator positioned at the specified index, and then adds the element - * using the ListIterator's add method. + * Insert an element into the list at a given position (optional operation). + * This shifts all existing elements from that position to the end one + * index to the right. This version of add has no return, since it is + * assumed to always succeed if there is no exception. This iteration + * uses listIterator(index).add(o). * - * @param index the position to add the element - * @param o the element to insert - * @exception IndexOutOfBoundsException if index < 0 || index > size() - * @exception UnsupportedOperationException if the iterator returned by - * listIterator(index) does not support the add method. + * @param index the location to insert the item + * @param o the object to insert + * @throws UnsupportedOperationException if this list does not support the + * add operation + * @throws IndexOutOfBoundsException if index < 0 || index > size() + * @throws ClassCastException if o cannot be added to this list due to its + * type + * @throws IllegalArgumentException if o cannot be added to this list for + * some other reason */ public void add(int index, Object o) { - ListIterator i = listIterator(index); - i.add(o); + listIterator(index).add(o); } /** - * @specnote The spec in the JDK1.3 online docs is wrong. The implementation - * should not call next() to skip over new elements as they are - * added, because iterator.add() should add new elements BEFORE - * the cursor. + * Insert the contents of a collection into the list at a given position + * (optional operation). Shift all elements at that position to the right + * by the number of elements inserted. This operation is undefined if + * this list is modified during the operation (for example, if you try + * to insert a list into itself). + * <p> + * + * This implementation grabs listIterator(index), then proceeds to use add + * for each element returned by c's iterator. Sun's online specs are wrong, + * claiming that this also calls next(): listIterator.add() correctly + * skips the added element. + * + * @param index the location to insert the collection + * @param c the collection to insert + * @return true if the list was modified by this action, that is, if c is + * non-empty + * @throws UnsupportedOperationException if this list does not support the + * addAll operation + * @throws IndexOutOfBoundsException if index < 0 || index > size() + * @throws ClassCastException if some element of c cannot be added to this + * list due to its type + * @throws IllegalArgumentException if some element of c cannot be added + * to this list for some other reason + * @throws NullPointerException if the specified collection is null + * @see #add(int, Object) */ public boolean addAll(int index, Collection c) { - boolean modified = false; Iterator ci = c.iterator(); int size = c.size(); ListIterator i = listIterator(index); - for (int pos = 0; pos < size; pos++) - { - i.add(ci.next()); - } - return (size > 0); + for (int pos = size; pos > 0; pos--) + i.add(ci.next()); + return size > 0; } + /** + * Get the element at a given index in this list. This implementation + * returns listIterator(index).next(). + * + * @param index the index of the element to be returned + * @return the element at index index in this list + * @throws IndexOutOfBoundsException if index < 0 || index >= size() + */ public Object get(int index) { - ListIterator i = listIterator(index); - if (index < 0 || index > size()) - throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + - size()); - return i.next(); + // This is a legal listIterator position, but an illegal get. + if (index == size()) + throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + + size()); + return listIterator(index).next(); } /** - * Return an Iterator over this List. This implementation returns - * listIterator(). + * Obtain an Iterator over this list, whose sequence is the list order. This + * implementation returns listIterator(). * - * @return an Iterator over this List + * @return an Iterator over the elements of this list, in order */ public Iterator iterator() { return listIterator(); } + /** + * Remove the element at a given position in this list (optional operation). + * Shifts all remaining elements to the left to fill the gap. This + * implementation uses listIterator(index) and ListIterator.remove(). + * + * @param index the position within the list of the object to remove + * @return the object that was removed + * @throws UnsupportedOperationException if this list does not support the + * remove operation + * @throws IndexOutOfBoundsException if index < 0 || index >= size() + */ public Object remove(int index) { + // This is a legal listIterator position, but an illegal remove. + if (index == size()) + throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + + size()); ListIterator i = listIterator(index); - if (index < 0 || index > size()) - throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + - size()); Object removed = i.next(); i.remove(); return removed; } + /** + * Replace an element of this list with another object (optional operation). + * This implementation uses listIterator(index) and ListIterator.set(o). + * + * @param index the position within this list of the element to be replaced + * @param o the object to replace it with + * @return the object that was replaced + * @throws UnsupportedOperationException if this list does not support the + * set operation + * @throws IndexOutOfBoundsException if index < 0 || index >= size() + * @throws ClassCastException if o cannot be added to this list due to its + * type + * @throws IllegalArgumentException if o cannot be added to this list for + * some other reason + */ public Object set(int index, Object o) { + // This is a legal listIterator position, but an illegal set. + if (index == size()) + throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + + size()); ListIterator i = listIterator(index); - if (index < 0 || index > size()) - throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + - size()); Object old = i.next(); i.set(o); return old; diff --git a/libjava/java/util/AbstractSet.java b/libjava/java/util/AbstractSet.java index 6c3f2196190..e45e47ea84d 100644 --- a/libjava/java/util/AbstractSet.java +++ b/libjava/java/util/AbstractSet.java @@ -1,5 +1,5 @@ /* AbstractSet.java -- Abstract implementation of most of Set - Copyright (C) 1998, 2000 Free Software Foundation, Inc. + Copyright (C) 1998, 2000, 2001 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -35,10 +35,28 @@ package java.util; * on them - specifically, no element may be in the set more than once). This * class simply provides implementations of equals() and hashCode() to fulfil * the requirements placed on them by the Set interface. + * + * @author Original author unknown + * @author Eric Blake <ebb9@email.byu.edu> + * @see Collection + * @see AbstractCollection + * @see Set + * @see HashSet + * @see TreeSet + * @see LinkedHashSet + * @since 1.2 + * @status updated to 1.4 */ public abstract class AbstractSet extends AbstractCollection implements Set { /** + * The main constructor, for use by subclasses. + */ + protected AbstractSet() + { + } + + /** * Tests whether the given object is equal to this Set. This implementation * first checks whether this set <em>is</em> the given object, and returns * true if so. Otherwise, if o is a Set and is the same size as this one, it @@ -50,12 +68,9 @@ public abstract class AbstractSet extends AbstractCollection implements Set */ public boolean equals(Object o) { - if (o == this) - return true; - else if (o instanceof Set && ((Set) o).size() == size()) - return containsAll((Collection) o); - else - return false; + return (o == this || + (o instanceof Set && ((Set) o).size() == size() + && containsAll((Collection) o))); } /** @@ -69,14 +84,45 @@ public abstract class AbstractSet extends AbstractCollection implements Set public int hashCode() { Iterator itr = iterator(); - int size = size(); int hash = 0; - for (int pos = 0; pos < size; pos++) + int pos = size(); + while (--pos >= 0) + hash += hashCode(itr.next()); + return hash; + } + + /** + * Removes from this set all elements in the given collection (optional + * operation). This implementation uses <code>size()</code> to determine + * the smaller collection. Then, if this set is smaller, it iterates + * over the set, calling Iterator.remove if the collection contains + * the element. If this set is larger, it iterates over the collection, + * calling Set.remove for all elements in the collection. Note that + * this operation will fail if a remove methods is not supported. + * + * @param c the collection of elements to remove + * @return true if the set was modified as a result + * @throws UnsupportedOperationException if remove is not supported + * @throws NullPointerException if the collection is null + * @see AbstractCollection#remove(Object) + * @see Collection#contains(Object) + * @see Iterator#remove() + */ + public boolean removeAll(Collection c) + { + int oldsize = size(); + int count = c.size(); + Iterator i; + if (oldsize < count) { - Object obj = itr.next(); - if (obj != null) - hash += obj.hashCode(); + for (i = iterator(), count = oldsize; count > 0; count--) + if (c.contains(i.next())) + i.remove(); } - return hash; + else + for (i = c.iterator(); count > 0; count--) + remove(i.next()); + return oldsize != size(); } + } diff --git a/libjava/java/util/ArrayList.java b/libjava/java/util/ArrayList.java index d6b663414a4..3c75e56672f 100644 --- a/libjava/java/util/ArrayList.java +++ b/libjava/java/util/ArrayList.java @@ -1,6 +1,6 @@ /* ArrayList.java -- JDK1.2's answer to Vector; this is an array-backed implementation of the List interface - Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc. + Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -35,383 +35,535 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; /** - * An array-backed implementation of the List interface. ArrayList - * performs well on simple tasks: random access into a list, appending - * to or removing from the end of a list, checking the size, &c. + * An array-backed implementation of the List interface. This implements + * all optional list operations, and permits null elements, so that it is + * better than Vector, which it replaces. Random access is roughly constant + * time, and iteration is roughly linear time, so it is nice and fast, with + * less overhead than a LinkedList. + * <p> * - * @author Jon A. Zeppieri - * @see java.util.AbstractList - * @see java.util.List + * Each list has a capacity, and as the array reaches that capacity it + * is automatically transferred to a larger array. You also have access to + * ensureCapacity and trimToSize to control the backing array's size, avoiding + * reallocation or wasted memory. + * <p> + * + * ArrayList is not synchronized, so if you need multi-threaded access, + * consider using:<br> + * <code>List l = Collections.synchronizedList(new ArrayList(...));</code> + * <p> + * + * The iterators are <i>fail-fast</i>, meaning that any structural + * modification, except for <code>remove()</code> called on the iterator + * itself, cause the iterator to throw a + * {@link ConcurrentModificationException} rather than exhibit + * non-deterministic behavior. + * + * @author Jon A. Zeppieri + * @author Bryce McKinlay + * @author Eric Blake <ebb9@email.byu.edu> + * @see Collection + * @see List + * @see LinkedList + * @see Vector + * @see Collections#synchronizedList(List) + * @see AbstractList + * @status updated to 1.4 */ public class ArrayList extends AbstractList - implements List, Cloneable, Serializable + implements List, RandomAccess, Cloneable, Serializable { - /** the default capacity for new ArrayLists */ + /** + * Compatible with JDK 1.2 + */ + private static final long serialVersionUID = 8683452581122892189L; + + /** + * The default capacity for new ArrayLists. + */ private static final int DEFAULT_CAPACITY = 16; - /** the number of elements in this list */ - int size; + /** + * The number of elements in this list. + * @serial the list size + */ + private int size; - /** where the data is stored */ - transient Object[] data; + /** + * Where the data is stored. + */ + private transient Object[] data; - /** - * Construct a new ArrayList with the supplied initial capacity. + /** + * Construct a new ArrayList with the supplied initial capacity. * - * @param capacity Initial capacity of this ArrayList + * @param capacity initial capacity of this ArrayList + * @throws IllegalArgumentException if capacity is negative */ public ArrayList(int capacity) { + // Must explicitly check, to get correct exception. + if (capacity < 0) + throw new IllegalArgumentException(); data = new Object[capacity]; } - /** - * Construct a new ArrayList with the default capcity + * Construct a new ArrayList with the default capcity (16). */ public ArrayList() { this(DEFAULT_CAPACITY); } - /** + /** * Construct a new ArrayList, and initialize it with the elements - * in the supplied Collection; Sun specs say that the initial - * capacity is 110% of the Collection's size. + * in the supplied Collection. The initial capacity is 110% of the + * Collection's size. * * @param c the collection whose elements will initialize this list + * @throws NullPointerException if c is null */ public ArrayList(Collection c) { - this((int) (c.size() * 1.1)); + this((int) (c.size() * 1.1f)); addAll(c); } /** + * Trims the capacity of this List to be equal to its size; + * a memory saver. + */ + public void trimToSize() + { + // Not a structural change from the perspective of iterators on this list, + // so don't update modCount. + if (size != data.length) + { + Object[] newData = new Object[size]; + System.arraycopy(data, 0, newData, 0, size); + data = newData; + } + } + + /** * Guarantees that this list will have at least enough capacity to - * hold minCapacity elements. + * hold minCapacity elements. This implementation will grow the list to + * max(current * 2, minCapacity) if (minCapacity > current). The JCL says + * explictly that "this method increases its capacity to minCap", while + * the JDK 1.3 online docs specify that the list will grow to at least the + * size specified. * - * @specnote This implementation will grow the list to - * max(current * 2, minCapacity) if (minCapacity > current). The JCL says - * explictly that "this method increases its capacity to minCap", while - * the JDK 1.3 online docs specify that the list will grow to at least the - * size specified. * @param minCapacity the minimum guaranteed capacity */ public void ensureCapacity(int minCapacity) { - Object[] newData; int current = data.length; if (minCapacity > current) { - newData = new Object[Math.max((current * 2), minCapacity)]; - System.arraycopy(data, 0, newData, 0, size); - data = newData; + Object[] newData = new Object[Math.max(current * 2, minCapacity)]; + System.arraycopy(data, 0, newData, 0, size); + data = newData; } } /** - * Appends the supplied element to the end of this list. + * Returns the number of elements in this list. * - * @param e the element to be appended to this list + * @return the list size */ - public boolean add(Object e) + public int size() { - modCount++; - if (size == data.length) - ensureCapacity(size + 1); - data[size++] = e; - return true; + return size; } /** - * Retrieves the element at the user-supplied index. + * Checks if the list is empty. * - * @param index the index of the element we are fetching - * @throws IndexOutOfBoundsException (iIndex < 0) || (iIndex >= size()) + * @return true if there are no elements */ - public Object get(int index) + public boolean isEmpty() { - if (index < 0 || index >= size) - throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + - size); - return data[index]; + return size == 0; } /** - * Returns the number of elements in this list + * Returns true iff element is in this ArrayList. + * + * @param e the element whose inclusion in the List is being tested + * @return true if the list contains e */ - public int size() + public boolean contains(Object e) { - return size; + return indexOf(e) != -1; } /** - * Removes the element at the user-supplied index + * Returns the lowest index at which element appears in this List, or + * -1 if it does not appear. * - * @param iIndex the index of the element to be removed - * @return the removed Object - * @throws IndexOutOfBoundsException (iIndex < 0) || (iIndex >= size()) + * @param e the element whose inclusion in the List is being tested + * @return the index where e was found */ - public Object remove(int index) + public int indexOf(Object e) { - modCount++; - if (index < 0 || index > size) - throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + - size); - Object r = data[index]; - if (index != --size) - System.arraycopy(data, (index + 1), data, index, (size - index)); - data[size] = null; - return r; + for (int i = 0; i < size; i++) + if (equals(e, data[i])) + return i; + return -1; } /** - * Removes all elements in the half-open interval [iFromIndex, iToIndex). + * Returns the highest index at which element appears in this List, or + * -1 if it does not appear. * - * @param fromIndex the first index which will be removed - * @param toIndex one greater than the last index which will be - * removed + * @param e the element whose inclusion in the List is being tested + * @return the index where e was found */ - protected void removeRange(int fromIndex, int toIndex) + public int lastIndexOf(Object e) { - modCount++; - if (fromIndex != toIndex) + for (int i = size - 1; i >= 0; i--) + if (equals(e, data[i])) + return i; + return -1; + } + + /** + * Creates a shallow copy of this ArrayList (elements are not cloned). + * + * @return the cloned object + */ + public Object clone() + { + ArrayList clone = null; + try + { + clone = (ArrayList) super.clone(); + clone.data = (Object[]) data.clone(); + } + catch (CloneNotSupportedException e) { - System.arraycopy(data, toIndex, data, fromIndex, size - toIndex); - size -= (toIndex - fromIndex); + // Impossible to get here. } + return clone; + } + + /** + * Returns an Object array containing all of the elements in this ArrayList. + * The array is independent of this list. + * + * @return an array representation of this list + */ + public Object[] toArray() + { + Object[] array = new Object[size]; + System.arraycopy(data, 0, array, 0, size); + return array; + } + + /** + * Returns an Array whose component type is the runtime component type of + * the passed-in Array. The returned Array is populated with all of the + * elements in this ArrayList. If the passed-in Array is not large enough + * to store all of the elements in this List, a new Array will be created + * and returned; if the passed-in Array is <i>larger</i> than the size + * of this List, then size() index will be set to null. + * + * @param a the passed-in Array + * @return an array representation of this list + * @throws ArrayStoreException if the runtime type of a does not allow + * an element in this list + * @throws NullPointerException if a is null + */ + public Object[] toArray(Object[] a) + { + if (a.length < size) + a = (Object[]) Array.newInstance(a.getClass().getComponentType(), + size); + else if (a.length > size) + a[size] = null; + System.arraycopy(data, 0, a, 0, size); + return a; + } + + /** + * Retrieves the element at the user-supplied index. + * + * @param index the index of the element we are fetching + * @throws IndexOutOfBoundsException if index < 0 || index >= size() + */ + public Object get(int index) + { + checkBoundExclusive(index); + return data[index]; + } + + /** + * Sets the element at the specified index. + * + * @param index the index at which the element is being set + * @param e the element to be set + * @return the element previously at the specified index + * @throws IndexOutOfBoundsException if index < 0 || index >= 0 + */ + public Object set(int index, Object e) + { + checkBoundExclusive(index); + Object result = data[index]; + data[index] = e; + return result; + } + + /** + * Appends the supplied element to the end of this list. + * + * @param e the element to be appended to this list + * @return true, the add will always succeed + */ + public boolean add(Object e) + { + modCount++; + if (size == data.length) + ensureCapacity(size + 1); + data[size++] = e; + return true; } /** * Adds the supplied element at the specified index, shifting all * elements currently at that index or higher one to the right. * - * @param index the index at which the element is being added - * @param e the item being added + * @param index the index at which the element is being added + * @param e the item being added + * @throws IndexOutOfBoundsException if index < 0 || index > size() */ public void add(int index, Object e) { + checkBoundInclusive(index); modCount++; - if (index < 0 || index > size) - throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + - size); if (size == data.length) ensureCapacity(size + 1); if (index != size) - System.arraycopy(data, index, data, index + 1, size - index); + System.arraycopy(data, index, data, index + 1, size - index); data[index] = e; size++; } - /** - * Add each element in the supplied Collection to this List. + /** + * Removes the element at the user-supplied index. + * + * @param index the index of the element to be removed + * @return the removed Object + * @throws IndexOutOfBoundsException if index < 0 || index >= size() + */ + public Object remove(int index) + { + checkBoundExclusive(index); + Object r = data[index]; + modCount++; + if (index != --size) + System.arraycopy(data, index + 1, data, index, size - index); + // Aid for garbage collection by releasing this pointer. + data[size] = null; + return r; + } + + /** + * Removes all elements from this List + */ + public void clear() + { + if (size > 0) + { + modCount++; + // Allow for garbage collection. + Arrays.fill(data, 0, size, null); + size = 0; + } + } + + /** + * Add each element in the supplied Collection to this List. It is undefined + * what happens if you modify the list while this is taking place; for + * example, if the collection contains this list. * - * @param c a Collection containing elements to be - * added to this List + * @param c a Collection containing elements to be added to this List + * @return true if the list was modified, in other words c is not empty + * @throws NullPointerException if c is null */ public boolean addAll(Collection c) { return addAll(size, c); } - /** + /** * Add all elements in the supplied collection, inserting them beginning * at the specified index. * - * @param index the index at which the elements will be inserted - * @param c the Collection containing the elements to be - * inserted + * @param index the index at which the elements will be inserted + * @param c the Collection containing the elements to be inserted + * @throws IndexOutOfBoundsException if index < 0 || index > 0 + * @throws NullPointerException if c is null */ public boolean addAll(int index, Collection c) { - if (index < 0 || index > size) - throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + - size); - modCount++; + checkBoundInclusive(index); Iterator itr = c.iterator(); int csize = c.size(); + modCount++; if (csize + size > data.length) ensureCapacity(size + csize); int end = index + csize; - if (size > 0 && index != size) + if (index != size) System.arraycopy(data, index, data, end, csize); size += csize; - for (; index < end; index++) - { - data[index] = itr.next(); - } - return (csize > 0); + for ( ; index < end; index++) + data[index] = itr.next(); + return csize > 0; } /** - * Creates a shallow copy of this ArrayList + * Removes all elements in the half-open interval [fromIndex, toIndex). + * You asked for it if you call this with invalid arguments. + * + * @param fromIndex the first index which will be removed + * @param toIndex one greater than the last index which will be removed */ - public Object clone() + protected void removeRange(int fromIndex, int toIndex) { - ArrayList clone = null; - try + if (fromIndex != toIndex) { - clone = (ArrayList) super.clone(); - clone.data = new Object[data.length]; - System.arraycopy(data, 0, clone.data, 0, size); + modCount++; + System.arraycopy(data, toIndex, data, fromIndex, size - toIndex); + size -= toIndex - fromIndex; } - catch (CloneNotSupportedException e) {} - return clone; } - /** - * Returns true iff oElement is in this ArrayList. + /** + * Checks that the index is in the range of possible elements (inclusive). * - * @param e the element whose inclusion in the List is being - * tested + * @param index the index to check + * @throws IndexOutOfBoundsException if index > size */ - public boolean contains(Object e) + private void checkBoundInclusive(int index) { - return (indexOf(e) != -1); + // Implementation note: we do not check for negative ranges here, since + // use of a negative index will cause an ArrayIndexOutOfBoundsException, + // a subclass of the required exception, with no effort on our part. + if (index > size) + throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + + size); } /** - * Returns the lowest index at which oElement appears in this List, or - * -1 if it does not appear. + * Checks that the index is in the range of existing elements (exclusive). * - * @param e the element whose inclusion in the List is being - * tested + * @param index the index to check + * @throws IndexOutOfBoundsException if index >= size */ - public int indexOf(Object e) + private void checkBoundExclusive(int index) { - for (int i = 0; i < size; i++) - { - if (e == null ? data[i] == null : e.equals(data[i])) - return i; - } - return -1; + // Implementation note: we do not check for negative ranges here, since + // use of a negative index will cause an ArrayIndexOutOfBoundsException, + // a subclass of the required exception, with no effort on our part. + if (index >= size) + throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + + size); } /** - * Returns the highest index at which oElement appears in this List, or - * -1 if it does not appear. + * Remove from this list all elements contained in the given collection. + * This is not public, due to Sun's API, but this performs in linear + * time while the default behavior of AbstractList would be quadratic. * - * @param e the element whose inclusion in the List is being - * tested + * @param c the collection to filter out + * @return true if this list changed + * @throws NullPointerException if c is null */ - public int lastIndexOf(Object e) + boolean removeAllInternal(Collection c) { int i; + int j; + for (i = 0; i < size; i++) + if (c.contains(data[i])) + break; + if (i == size) + return false; - for (i = size - 1; i >= 0; i--) - { - if (e == null ? data[i] == null : e.equals(data[i])) - return i; - } - return -1; - } - - /** - * Removes all elements from this List - */ - public void clear() - { modCount++; - for (int i = 0; i < size; i++) - { - data[i] = null; - } - size = 0; + for (j = i++; i < size; i++) + if (! c.contains(data[i])) + data[j++] = data[i]; + size -= i - j; + return true; } /** - * Sets the element at the specified index. + * Retain in this vector only the elements contained in the given collection. + * This is not public, due to Sun's API, but this performs in linear + * time while the default behavior of AbstractList would be quadratic. * - * @param index the index at which the element is being set - * @param e the element to be set - * @return the element previously at the specified index, or null if - * none was there + * @param c the collection to filter by + * @return true if this vector changed + * @throws NullPointerException if c is null + * @since 1.2 */ - public Object set(int index, Object e) + boolean retainAllInternal(Collection c) { - Object result; - if (index < 0 || index >= size) - throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + - size); - result = data[index]; - // SEH: no structural change, so don't update modCount - data[index] = e; - return result; - } + int i; + int j; + for (i = 0; i < size; i++) + if (! c.contains(data[i])) + break; + if (i == size) + return false; - /** - * Returns an Object Array containing all of the elements in this ArrayList - */ - public Object[] toArray() - { - Object[] array = new Object[size]; - System.arraycopy(data, 0, array, 0, size); - return array; + modCount++; + for (j = i++; i < size; i++) + if (c.contains(data[i])) + data[j++] = data[i]; + size -= i - j; + return true; } /** - * Returns an Array whose component type is the runtime component type of - * the passed-in Array. The returned Array is populated with all of the - * elements in this ArrayList. If the passed-in Array is not large enough - * to store all of the elements in this List, a new Array will be created - * and returned; if the passed-in Array is <i>larger</i> than the size - * of this List, then size() index will be set to null. + * Serializes this object to the given stream. * - * @param array the passed-in Array + * @param out the stream to write to + * @throws IOException if the underlying stream fails + * @serialData the size field (int), the length of the backing array + * (int), followed by its elements (Objects) in proper order. */ - public Object[] toArray(Object[] array) + private void writeObject(ObjectOutputStream s) throws IOException { - if (array.length < size) - array = (Object[]) Array.newInstance(array.getClass().getComponentType(), - size); - else if (array.length > size) - array[size] = null; - System.arraycopy(data, 0, array, 0, size); - return array; + // The 'size' field. + s.defaultWriteObject(); + // We serialize unused list entries to preserve capacity. + int len = data.length; + s.writeInt(len); + for (int i = 0; i < len; i++) + s.writeObject(data[i]); } /** - * Trims the capacity of this List to be equal to its size; - * a memory saver. + * Deserializes this object from the given stream. + * + * @param in the stream to read from + * @throws ClassNotFoundException if the underlying stream fails + * @throws IOException if the underlying stream fails + * @serialData the size field (int), the length of the backing array + * (int), followed by its elements (Objects) in proper order. */ - public void trimToSize() - { - // not a structural change from the perspective of iterators on this list, - // so don't update modCount - Object[] newData = new Object[size]; - System.arraycopy(data, 0, newData, 0, size); - data = newData; - } - - private void writeObject(ObjectOutputStream out) throws IOException - { - int i; - - // The 'size' field. - out.defaultWriteObject(); - - // FIXME: Do we really want to serialize unused list entries?? - out.writeInt(data.length); - for (i = 0; i < data.length; i++) - out.writeObject(data[i]); - } - - private void readObject(ObjectInputStream in) + private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { - int i; - int capacity; - // the `size' field. - in.defaultReadObject(); - - capacity = in.readInt(); + s.defaultReadObject(); + int capacity = s.readInt(); data = new Object[capacity]; - - for (i = 0; i < capacity; i++) - data[i] = in.readObject(); + for (int i = 0; i < capacity; i++) + data[i] = s.readObject(); } } diff --git a/libjava/java/util/Arrays.java b/libjava/java/util/Arrays.java index 87a40e35547..d52431b7b3f 100644 --- a/libjava/java/util/Arrays.java +++ b/libjava/java/util/Arrays.java @@ -1,5 +1,5 @@ /* Arrays.java -- Utility class with methods to operate on arrays - Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc. + Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -25,16 +25,33 @@ This exception does not however invalidate any other reasons why the executable file might be covered by the GNU General Public License. */ -// TO DO: -// ~ Fix the behaviour of sort and binarySearch as applied to float and double -// arrays containing NaN values. See the JDC, bug ID 4143272. - package java.util; +import java.io.Serializable; +import java.lang.reflect.Array; + /** * This class contains various static utility methods performing operations on * arrays, and a method to provide a List "view" of an array to facilitate - * using arrays with Collection-based APIs. + * using arrays with Collection-based APIs. All methods throw a + * {@link NullPointerException} if the parameter array is null. + * <p> + * + * Implementations may use their own algorithms, but must obey the general + * properties; for example, the sort must be stable and n*log(n) complexity. + * Sun's implementation of sort, and therefore ours, is a tuned quicksort, + * adapted from Jon L. Bentley and M. Douglas McIlroy's "Engineering a Sort + * Function", Software-Practice and Experience, Vol. 23(11) P. 1249-1265 + * (November 1993). This algorithm offers n*log(n) performance on many data + * sets that cause other quicksorts to degrade to quadratic performance. + * + * @author Original author unknown + * @author Bryce McKinlay + * @author Eric Blake <ebb9@email.byu.edu> + * @see Comparable + * @see Comparator + * @since 1.2 + * @status updated to 1.4 */ public class Arrays { @@ -45,14 +62,8 @@ public class Arrays { } - private static Comparator defaultComparator = new Comparator() - { - public int compare(Object o1, Object o2) - { - return ((Comparable) o1).compareTo(o2); - } - }; - + +// binarySearch /** * Perform a binary search of a byte array for a key. The array must be * sorted (as by the sort() method) - if it is not, the behaviour of this @@ -63,32 +74,26 @@ public class Arrays * * @param a the array to search (must be sorted) * @param key the value to search for - * @returns the index at which the key was found, or -n-1 if it was not - * found, where n is the index of the first value higher than key or - * a.length if there is no such value. + * @return the index at which the key was found, or -n-1 if it was not + * found, where n is the index of the first value higher than key or + * a.length if there is no such value. */ - public static int binarySearch(byte[]a, byte key) + public static int binarySearch(byte[] a, byte key) { int low = 0; int hi = a.length - 1; int mid = 0; while (low <= hi) { - mid = (low + hi) >> 1; - final byte d = a[mid]; - if (d == key) - { - return mid; - } - else if (d > key) - { - hi = mid - 1; - } - else - { - // This gets the insertion point right on the last loop - low = ++mid; - } + mid = (low + hi) >> 1; + final byte d = a[mid]; + if (d == key) + return mid; + else if (d > key) + hi = mid - 1; + else + // This gets the insertion point right on the last loop. + low = ++mid; } return -mid - 1; } @@ -103,38 +108,32 @@ public class Arrays * * @param a the array to search (must be sorted) * @param key the value to search for - * @returns the index at which the key was found, or -n-1 if it was not - * found, where n is the index of the first value higher than key or - * a.length if there is no such value. + * @return the index at which the key was found, or -n-1 if it was not + * found, where n is the index of the first value higher than key or + * a.length if there is no such value. */ - public static int binarySearch(char[]a, char key) + public static int binarySearch(char[] a, char key) { int low = 0; int hi = a.length - 1; int mid = 0; while (low <= hi) { - mid = (low + hi) >> 1; - final char d = a[mid]; - if (d == key) - { - return mid; - } - else if (d > key) - { - hi = mid - 1; - } - else - { - // This gets the insertion point right on the last loop - low = ++mid; - } + mid = (low + hi) >> 1; + final char d = a[mid]; + if (d == key) + return mid; + else if (d > key) + hi = mid - 1; + else + // This gets the insertion point right on the last loop. + low = ++mid; } return -mid - 1; } /** - * Perform a binary search of a double array for a key. The array must be + * Perform a binary search of a short array for a key. The array must be * sorted (as by the sort() method) - if it is not, the behaviour of this * method is undefined, and may be an infinite loop. If the array contains * the key more than once, any one of them may be found. Note: although the @@ -143,38 +142,32 @@ public class Arrays * * @param a the array to search (must be sorted) * @param key the value to search for - * @returns the index at which the key was found, or -n-1 if it was not - * found, where n is the index of the first value higher than key or - * a.length if there is no such value. + * @return the index at which the key was found, or -n-1 if it was not + * found, where n is the index of the first value higher than key or + * a.length if there is no such value. */ - public static int binarySearch(double[]a, double key) + public static int binarySearch(short[] a, short key) { int low = 0; int hi = a.length - 1; int mid = 0; while (low <= hi) { - mid = (low + hi) >> 1; - final double d = a[mid]; - if (d == key) - { - return mid; - } - else if (d > key) - { - hi = mid - 1; - } - else - { - // This gets the insertion point right on the last loop - low = ++mid; - } + mid = (low + hi) >> 1; + final short d = a[mid]; + if (d == key) + return mid; + else if (d > key) + hi = mid - 1; + else + // This gets the insertion point right on the last loop. + low = ++mid; } return -mid - 1; } /** - * Perform a binary search of a float array for a key. The array must be + * Perform a binary search of an int array for a key. The array must be * sorted (as by the sort() method) - if it is not, the behaviour of this * method is undefined, and may be an infinite loop. If the array contains * the key more than once, any one of them may be found. Note: although the @@ -183,38 +176,32 @@ public class Arrays * * @param a the array to search (must be sorted) * @param key the value to search for - * @returns the index at which the key was found, or -n-1 if it was not - * found, where n is the index of the first value higher than key or - * a.length if there is no such value. + * @return the index at which the key was found, or -n-1 if it was not + * found, where n is the index of the first value higher than key or + * a.length if there is no such value. */ - public static int binarySearch(float[]a, float key) + public static int binarySearch(int[] a, int key) { int low = 0; int hi = a.length - 1; int mid = 0; while (low <= hi) { - mid = (low + hi) >> 1; - final float d = a[mid]; - if (d == key) - { - return mid; - } - else if (d > key) - { - hi = mid - 1; - } - else - { - // This gets the insertion point right on the last loop - low = ++mid; - } + mid = (low + hi) >> 1; + final int d = a[mid]; + if (d == key) + return mid; + else if (d > key) + hi = mid - 1; + else + // This gets the insertion point right on the last loop. + low = ++mid; } return -mid - 1; } /** - * Perform a binary search of an int array for a key. The array must be + * Perform a binary search of a long array for a key. The array must be * sorted (as by the sort() method) - if it is not, the behaviour of this * method is undefined, and may be an infinite loop. If the array contains * the key more than once, any one of them may be found. Note: although the @@ -223,38 +210,32 @@ public class Arrays * * @param a the array to search (must be sorted) * @param key the value to search for - * @returns the index at which the key was found, or -n-1 if it was not - * found, where n is the index of the first value higher than key or - * a.length if there is no such value. + * @return the index at which the key was found, or -n-1 if it was not + * found, where n is the index of the first value higher than key or + * a.length if there is no such value. */ - public static int binarySearch(int[]a, int key) + public static int binarySearch(long[] a, long key) { int low = 0; int hi = a.length - 1; int mid = 0; while (low <= hi) { - mid = (low + hi) >> 1; - final int d = a[mid]; - if (d == key) - { - return mid; - } - else if (d > key) - { - hi = mid - 1; - } - else - { - // This gets the insertion point right on the last loop - low = ++mid; - } + mid = (low + hi) >> 1; + final long d = a[mid]; + if (d == key) + return mid; + else if (d > key) + hi = mid - 1; + else + // This gets the insertion point right on the last loop. + low = ++mid; } return -mid - 1; } /** - * Perform a binary search of a long array for a key. The array must be + * Perform a binary search of a float array for a key. The array must be * sorted (as by the sort() method) - if it is not, the behaviour of this * method is undefined, and may be an infinite loop. If the array contains * the key more than once, any one of them may be found. Note: although the @@ -263,38 +244,33 @@ public class Arrays * * @param a the array to search (must be sorted) * @param key the value to search for - * @returns the index at which the key was found, or -n-1 if it was not - * found, where n is the index of the first value higher than key or - * a.length if there is no such value. + * @return the index at which the key was found, or -n-1 if it was not + * found, where n is the index of the first value higher than key or + * a.length if there is no such value. */ - public static int binarySearch(long[]a, long key) + public static int binarySearch(float[] a, float key) { + // Must use Float.compare to take into account NaN, +-0. int low = 0; int hi = a.length - 1; int mid = 0; while (low <= hi) { - mid = (low + hi) >> 1; - final long d = a[mid]; - if (d == key) - { - return mid; - } - else if (d > key) - { - hi = mid - 1; - } - else - { - // This gets the insertion point right on the last loop - low = ++mid; - } + mid = (low + hi) >> 1; + final int r = Float.compare(a[mid], key); + if (r == 0) + return mid; + else if (r > 0) + hi = mid - 1; + else + // This gets the insertion point right on the last loop + low = ++mid; } return -mid - 1; } /** - * Perform a binary search of a short array for a key. The array must be + * Perform a binary search of a double array for a key. The array must be * sorted (as by the sort() method) - if it is not, the behaviour of this * method is undefined, and may be an infinite loop. If the array contains * the key more than once, any one of them may be found. Note: although the @@ -303,63 +279,27 @@ public class Arrays * * @param a the array to search (must be sorted) * @param key the value to search for - * @returns the index at which the key was found, or -n-1 if it was not - * found, where n is the index of the first value higher than key or - * a.length if there is no such value. - */ - public static int binarySearch(short[]a, short key) - { - int low = 0; - int hi = a.length - 1; - int mid = 0; - while (low <= hi) - { - mid = (low + hi) >> 1; - final short d = a[mid]; - if (d == key) - { - return mid; - } - else if (d > key) - { - hi = mid - 1; - } - else - { - // This gets the insertion point right on the last loop - low = ++mid; - } - } - return -mid - 1; - } - - /** - * This method does the work for the Object binary search methods. - * @exception NullPointerException if the specified comparator is null. - * @exception ClassCastException if the objects are not comparable by c. + * @return the index at which the key was found, or -n-1 if it was not + * found, where n is the index of the first value higher than key or + * a.length if there is no such value. */ - private static int objectSearch(Object[]a, Object key, final Comparator c) + public static int binarySearch(double[] a, double key) { + // Must use Double.compare to take into account NaN, +-0. int low = 0; int hi = a.length - 1; int mid = 0; while (low <= hi) { - mid = (low + hi) >> 1; - final int d = c.compare(key, a[mid]); - if (d == 0) - { - return mid; - } - else if (d < 0) - { - hi = mid - 1; - } - else - { - // This gets the insertion point right on the last loop - low = ++mid; - } + mid = (low + hi) >> 1; + final int r = Double.compare(a[mid], key); + if (r == 0) + return mid; + else if (r > 0) + hi = mid - 1; + else + // This gets the insertion point right on the last loop + low = ++mid; } return -mid - 1; } @@ -376,16 +316,16 @@ public class Arrays * * @param a the array to search (must be sorted) * @param key the value to search for - * @returns the index at which the key was found, or -n-1 if it was not - * found, where n is the index of the first value higher than key or - * a.length if there is no such value. - * @exception ClassCastException if key could not be compared with one of the - * elements of a - * @exception NullPointerException if a null element has compareTo called + * @return the index at which the key was found, or -n-1 if it was not + * found, where n is the index of the first value higher than key or + * a.length if there is no such value. + * @throws ClassCastException if key could not be compared with one of the + * elements of a + * @throws NullPointerException if a null element in a is compared */ - public static int binarySearch(Object[]a, Object key) + public static int binarySearch(Object[] a, Object key) { - return objectSearch(a, key, defaultComparator); + return binarySearch(a, key, null); } /** @@ -400,343 +340,310 @@ public class Arrays * * @param a the array to search (must be sorted) * @param key the value to search for - * @param c the comparator by which the array is sorted - * @returns the index at which the key was found, or -n-1 if it was not - * found, where n is the index of the first value higher than key or - * a.length if there is no such value. - * @exception ClassCastException if key could not be compared with one of the - * elements of a + * @param c the comparator by which the array is sorted; or null to + * use the elements' natural order + * @return the index at which the key was found, or -n-1 if it was not + * found, where n is the index of the first value higher than key or + * a.length if there is no such value. + * @throws ClassCastException if key could not be compared with one of the + * elements of a + * @throws NullPointerException if a null element is compared with natural + * ordering (only possible when c is null) */ - public static int binarySearch(Object[]a, Object key, Comparator c) + public static int binarySearch(Object[] a, Object key, Comparator c) { - return objectSearch(a, key, c); + int low = 0; + int hi = a.length - 1; + int mid = 0; + while (low <= hi) + { + mid = (low + hi) >> 1; + final int d = Collections.compare(key, a[mid], c); + if (d == 0) + return mid; + else if (d < 0) + hi = mid - 1; + else + // This gets the insertion point right on the last loop + low = ++mid; + } + return -mid - 1; } + +// equals /** - * Compare two byte arrays for equality. + * Compare two boolean arrays for equality. * * @param a1 the first array to compare * @param a2 the second array to compare - * @returns true if a1 and a2 are both null, or if a2 is of the same length - * as a1, and for each 0 <= i < a1.length, a1[i] == a2[i] + * @return true if a1 and a2 are both null, or if a2 is of the same length + * as a1, and for each 0 <= i < a1.length, a1[i] == a2[i] */ - public static boolean equals(byte[]a1, byte[]a2) + public static boolean equals(boolean[] a1, boolean[] a2) { // Quick test which saves comparing elements of the same array, and also // catches the case that both are null. if (a1 == a2) - { - return true; - } - + return true; + try { - // If they're the same length, test each element - if (a1.length == a2.length) - { - for (int i = 0; i < a1.length; i++) - { - if (a1[i] != a2[i]) - { - return false; - } - } - return true; - } - - // If a1 == null or a2 == null but not both then we will get a NullPointer + // If they're the same length, test each element + if (a1.length == a2.length) + { + int i = a1.length; + while (--i >= 0) + if (a1[i] != a2[i]) + return false; + return true; + } } catch (NullPointerException e) { + // If one is null, we get a harmless NullPointerException } return false; } /** - * Compare two char arrays for equality. + * Compare two byte arrays for equality. * * @param a1 the first array to compare * @param a2 the second array to compare - * @returns true if a1 and a2 are both null, or if a2 is of the same length - * as a1, and for each 0 <= i < a1.length, a1[i] == a2[i] + * @return true if a1 and a2 are both null, or if a2 is of the same length + * as a1, and for each 0 <= i < a1.length, a1[i] == a2[i] */ - public static boolean equals(char[]a1, char[]a2) + public static boolean equals(byte[] a1, byte[] a2) { // Quick test which saves comparing elements of the same array, and also // catches the case that both are null. if (a1 == a2) - { - return true; - } - + return true; + try { - // If they're the same length, test each element - if (a1.length == a2.length) - { - for (int i = 0; i < a1.length; i++) - { - if (a1[i] != a2[i]) - { - return false; - } - } - return true; - } - - // If a1 == null or a2 == null but not both then we will get a NullPointer + // If they're the same length, test each element + if (a1.length == a2.length) + { + int i = a1.length; + while (--i >= 0) + if (a1[i] != a2[i]) + return false; + return true; + } } catch (NullPointerException e) { + // If one is null, we get a harmless NullPointerException } - return false; } /** - * Compare two double arrays for equality. + * Compare two char arrays for equality. * * @param a1 the first array to compare * @param a2 the second array to compare - * @returns true if a1 and a2 are both null, or if a2 is of the same length - * as a1, and for each 0 <= i < a1.length, a1[i] == a2[i] + * @return true if a1 and a2 are both null, or if a2 is of the same length + * as a1, and for each 0 <= i < a1.length, a1[i] == a2[i] */ - public static boolean equals(double[]a1, double[]a2) + public static boolean equals(char[] a1, char[] a2) { // Quick test which saves comparing elements of the same array, and also // catches the case that both are null. if (a1 == a2) - { - return true; - } - + return true; + try { - // If they're the same length, test each element - if (a1.length == a2.length) - { - for (int i = 0; i < a1.length; i++) - { - if (a1[i] != a2[i]) - { - return false; - } - } - return true; - } - - // If a1 == null or a2 == null but not both then we will get a NullPointer + // If they're the same length, test each element + if (a1.length == a2.length) + { + int i = a1.length; + while (--i >= 0) + if (a1[i] != a2[i]) + return false; + return true; + } } catch (NullPointerException e) { + // If one is null, we get a harmless NullPointerException } - return false; } /** - * Compare two float arrays for equality. + * Compare two short arrays for equality. * * @param a1 the first array to compare * @param a2 the second array to compare - * @returns true if a1 and a2 are both null, or if a2 is of the same length - * as a1, and for each 0 <= i < a1.length, a1[i] == a2[i] + * @return true if a1 and a2 are both null, or if a2 is of the same length + * as a1, and for each 0 <= i < a1.length, a1[i] == a2[i] */ - public static boolean equals(float[]a1, float[]a2) + public static boolean equals(short[] a1, short[] a2) { // Quick test which saves comparing elements of the same array, and also // catches the case that both are null. if (a1 == a2) - { - return true; - } - + return true; + try { - // If they're the same length, test each element - if (a1.length == a2.length) - { - for (int i = 0; i < a1.length; i++) - { - if (a1[i] != a2[i]) - { - return false; - } - } - return true; - } - - // If a1 == null or a2 == null but not both then we will get a NullPointer + // If they're the same length, test each element + if (a1.length == a2.length) + { + int i = a1.length; + while (--i >= 0) + if (a1[i] != a2[i]) + return false; + return true; + } } catch (NullPointerException e) { + // If one is null, we get a harmless NullPointerException } - return false; } /** - * Compare two long arrays for equality. + * Compare two int arrays for equality. * * @param a1 the first array to compare * @param a2 the second array to compare - * @returns true if a1 and a2 are both null, or if a2 is of the same length - * as a1, and for each 0 <= i < a1.length, a1[i] == a2[i] + * @return true if a1 and a2 are both null, or if a2 is of the same length + * as a1, and for each 0 <= i < a1.length, a1[i] == a2[i] */ - public static boolean equals(long[]a1, long[]a2) + public static boolean equals(int[] a1, int[] a2) { // Quick test which saves comparing elements of the same array, and also // catches the case that both are null. if (a1 == a2) - { - return true; - } - + return true; + try { - // If they're the same length, test each element - if (a1.length == a2.length) - { - for (int i = 0; i < a1.length; i++) - { - if (a1[i] != a2[i]) - { - return false; - } - } - return true; - } - - // If a1 == null or a2 == null but not both then we will get a NullPointer + // If they're the same length, test each element + if (a1.length == a2.length) + { + int i = a1.length; + while (--i >= 0) + if (a1[i] != a2[i]) + return false; + return true; + } } catch (NullPointerException e) { + // If one is null, we get a harmless NullPointerException } - return false; } /** - * Compare two short arrays for equality. + * Compare two long arrays for equality. * * @param a1 the first array to compare * @param a2 the second array to compare - * @returns true if a1 and a2 are both null, or if a2 is of the same length - * as a1, and for each 0 <= i < a1.length, a1[i] == a2[i] + * @return true if a1 and a2 are both null, or if a2 is of the same length + * as a1, and for each 0 <= i < a1.length, a1[i] == a2[i] */ - public static boolean equals(short[]a1, short[]a2) + public static boolean equals(long[] a1, long[] a2) { // Quick test which saves comparing elements of the same array, and also // catches the case that both are null. if (a1 == a2) - { - return true; - } - + return true; + try { - // If they're the same length, test each element - if (a1.length == a2.length) - { - for (int i = 0; i < a1.length; i++) - { - if (a1[i] != a2[i]) - { - return false; - } - } - return true; - } - - // If a1 == null or a2 == null but not both then we will get a NullPointer + // If they're the same length, test each element + if (a1.length == a2.length) + { + int i = a1.length; + while (--i >= 0) + if (a1[i] != a2[i]) + return false; + return true; + } } catch (NullPointerException e) { + // If one is null, we get a harmless NullPointerException } - return false; } /** - * Compare two boolean arrays for equality. + * Compare two float arrays for equality. * * @param a1 the first array to compare * @param a2 the second array to compare - * @returns true if a1 and a2 are both null, or if a2 is of the same length - * as a1, and for each 0 <= i < a1.length, a1[i] == a2[i] + * @return true if a1 and a2 are both null, or if a2 is of the same length + * as a1, and for each 0 <= i < a1.length, a1[i] == a2[i] */ - public static boolean equals(boolean[]a1, boolean[]a2) + public static boolean equals(float[] a1, float[] a2) { // Quick test which saves comparing elements of the same array, and also // catches the case that both are null. if (a1 == a2) - { - return true; - } - + return true; + + // Must use Float.compare to take into account NaN, +-0. try { - // If they're the same length, test each element - if (a1.length == a2.length) - { - for (int i = 0; i < a1.length; i++) - { - if (a1[i] != a2[i]) - { - return false; - } - } - return true; - } - - // If a1 == null or a2 == null but not both then we will get a NullPointer + // If they're the same length, test each element + if (a1.length == a2.length) + { + int i = a1.length; + while (--i >= 0) + if (Float.compare(a1[i], a2[i]) != 0) + return false; + return true; + } } catch (NullPointerException e) { + // If one is null, we get a harmless NullPointerException } - return false; } /** - * Compare two int arrays for equality. + * Compare two double arrays for equality. * * @param a1 the first array to compare * @param a2 the second array to compare - * @returns true if a1 and a2 are both null, or if a2 is of the same length - * as a1, and for each 0 <= i < a1.length, a1[i] == a2[i] + * @return true if a1 and a2 are both null, or if a2 is of the same length + * as a1, and for each 0 <= i < a1.length, a1[i] == a2[i] */ - public static boolean equals(int[]a1, int[]a2) + public static boolean equals(double[] a1, double[] a2) { // Quick test which saves comparing elements of the same array, and also // catches the case that both are null. if (a1 == a2) - { - return true; - } - + return true; + + // Must use Double.compare to take into account NaN, +-0. try { - // If they're the same length, test each element - if (a1.length == a2.length) - { - for (int i = 0; i < a1.length; i++) - { - if (a1[i] != a2[i]) - { - return false; - } - } - return true; - } - - // If a1 == null or a2 == null but not both then we will get a NullPointer + // If they're the same length, test each element + if (a1.length == a2.length) + { + int i = a1.length; + while (--i >= 0) + if (Double.compare(a1[i], a2[i]) != 0) + return false; + return true; + } } catch (NullPointerException e) { + // If one is null, we get a harmless NullPointerException } - return false; } @@ -745,54 +652,46 @@ public class Arrays * * @param a1 the first array to compare * @param a2 the second array to compare - * @returns true if a1 and a2 are both null, or if a1 is of the same length - * as a2, and for each 0 <= i < a.length, a1[i] == null ? a2[i] == null : - * a1[i].equals(a2[i]). + * @return true if a1 and a2 are both null, or if a1 is of the same length + * as a2, and for each 0 <= i < a.length, a1[i] == null ? + * a2[i] == null : a1[i].equals(a2[i]). */ - public static boolean equals(Object[]a1, Object[]a2) + public static boolean equals(Object[] a1, Object[] a2) { // Quick test which saves comparing elements of the same array, and also // catches the case that both are null. if (a1 == a2) - { - return true; - } - + return true; + try { - // If they're the same length, test each element - if (a1.length == a2.length) - { - for (int i = 0; i < a1.length; i++) - { - if (!(a1[i] == null ? a2[i] == null : a1[i].equals(a2[i]))) - { - return false; - } - } - return true; - } - - // If a1 == null or a2 == null but not both then we will get a NullPointer + // If they're the same length, test each element + if (a1.length == a2.length) + { + int i = a1.length; + while (--i >= 0) + if (! AbstractCollection.equals(a1[i], a2[i])) + return false; + return true; + } } catch (NullPointerException e) { + // If one is null, we get a harmless NullPointerException } - return false; } + +// fill /** * Fill an array with a boolean value. * * @param a the array to fill * @param val the value to fill it with */ - public static void fill(boolean[]a, boolean val) + public static void fill(boolean[] a, boolean val) { - // This implementation is slightly inefficient timewise, but the extra - // effort over inlining it is O(1) and small, and I refuse to repeat code - // if it can be helped. fill(a, 0, a.length, val); } @@ -803,13 +702,16 @@ public class Arrays * @param fromIndex the index to fill from, inclusive * @param toIndex the index to fill to, exclusive * @param val the value to fill with + * @throws IllegalArgumentException if fromIndex > toIndex + * @throws ArrayIndexOutOfBoundsException if fromIndex < 0 + * || toIndex > a.length */ - public static void fill(boolean[]a, int fromIndex, int toIndex, boolean val) + public static void fill(boolean[] a, int fromIndex, int toIndex, boolean val) { + if (fromIndex > toIndex) + throw new IllegalArgumentException(); for (int i = fromIndex; i < toIndex; i++) - { - a[i] = val; - } + a[i] = val; } /** @@ -818,11 +720,8 @@ public class Arrays * @param a the array to fill * @param val the value to fill it with */ - public static void fill(byte[]a, byte val) + public static void fill(byte[] a, byte val) { - // This implementation is slightly inefficient timewise, but the extra - // effort over inlining it is O(1) and small, and I refuse to repeat code - // if it can be helped. fill(a, 0, a.length, val); } @@ -833,13 +732,16 @@ public class Arrays * @param fromIndex the index to fill from, inclusive * @param toIndex the index to fill to, exclusive * @param val the value to fill with + * @throws IllegalArgumentException if fromIndex > toIndex + * @throws ArrayIndexOutOfBoundsException if fromIndex < 0 + * || toIndex > a.length */ - public static void fill(byte[]a, int fromIndex, int toIndex, byte val) + public static void fill(byte[] a, int fromIndex, int toIndex, byte val) { + if (fromIndex > toIndex) + throw new IllegalArgumentException(); for (int i = fromIndex; i < toIndex; i++) - { - a[i] = val; - } + a[i] = val; } /** @@ -848,11 +750,8 @@ public class Arrays * @param a the array to fill * @param val the value to fill it with */ - public static void fill(char[]a, char val) + public static void fill(char[] a, char val) { - // This implementation is slightly inefficient timewise, but the extra - // effort over inlining it is O(1) and small, and I refuse to repeat code - // if it can be helped. fill(a, 0, a.length, val); } @@ -863,163 +762,166 @@ public class Arrays * @param fromIndex the index to fill from, inclusive * @param toIndex the index to fill to, exclusive * @param val the value to fill with + * @throws IllegalArgumentException if fromIndex > toIndex + * @throws ArrayIndexOutOfBoundsException if fromIndex < 0 + * || toIndex > a.length */ - public static void fill(char[]a, int fromIndex, int toIndex, char val) + public static void fill(char[] a, int fromIndex, int toIndex, char val) { + if (fromIndex > toIndex) + throw new IllegalArgumentException(); for (int i = fromIndex; i < toIndex; i++) - { - a[i] = val; - } + a[i] = val; } /** - * Fill an array with a double value. + * Fill an array with a short value. * * @param a the array to fill * @param val the value to fill it with */ - public static void fill(double[]a, double val) + public static void fill(short[] a, short val) { - // This implementation is slightly inefficient timewise, but the extra - // effort over inlining it is O(1) and small, and I refuse to repeat code - // if it can be helped. fill(a, 0, a.length, val); } /** - * Fill a range of an array with a double value. + * Fill a range of an array with a short value. * * @param a the array to fill * @param fromIndex the index to fill from, inclusive * @param toIndex the index to fill to, exclusive * @param val the value to fill with + * @throws IllegalArgumentException if fromIndex > toIndex + * @throws ArrayIndexOutOfBoundsException if fromIndex < 0 + * || toIndex > a.length */ - public static void fill(double[]a, int fromIndex, int toIndex, double val) + public static void fill(short[] a, int fromIndex, int toIndex, short val) { + if (fromIndex > toIndex) + throw new IllegalArgumentException(); for (int i = fromIndex; i < toIndex; i++) - { - a[i] = val; - } + a[i] = val; } /** - * Fill an array with a float value. + * Fill an array with an int value. * * @param a the array to fill * @param val the value to fill it with */ - public static void fill(float[]a, float val) + public static void fill(int[] a, int val) { - // This implementation is slightly inefficient timewise, but the extra - // effort over inlining it is O(1) and small, and I refuse to repeat code - // if it can be helped. fill(a, 0, a.length, val); } /** - * Fill a range of an array with a float value. + * Fill a range of an array with an int value. * * @param a the array to fill * @param fromIndex the index to fill from, inclusive * @param toIndex the index to fill to, exclusive * @param val the value to fill with + * @throws IllegalArgumentException if fromIndex > toIndex + * @throws ArrayIndexOutOfBoundsException if fromIndex < 0 + * || toIndex > a.length */ - public static void fill(float[]a, int fromIndex, int toIndex, float val) + public static void fill(int[] a, int fromIndex, int toIndex, int val) { + if (fromIndex > toIndex) + throw new IllegalArgumentException(); for (int i = fromIndex; i < toIndex; i++) - { - a[i] = val; - } + a[i] = val; } /** - * Fill an array with an int value. + * Fill an array with a long value. * * @param a the array to fill * @param val the value to fill it with */ - public static void fill(int[]a, int val) + public static void fill(long[] a, long val) { - // This implementation is slightly inefficient timewise, but the extra - // effort over inlining it is O(1) and small, and I refuse to repeat code - // if it can be helped. fill(a, 0, a.length, val); } /** - * Fill a range of an array with an int value. + * Fill a range of an array with a long value. * * @param a the array to fill * @param fromIndex the index to fill from, inclusive * @param toIndex the index to fill to, exclusive * @param val the value to fill with + * @throws IllegalArgumentException if fromIndex > toIndex + * @throws ArrayIndexOutOfBoundsException if fromIndex < 0 + * || toIndex > a.length */ - public static void fill(int[]a, int fromIndex, int toIndex, int val) + public static void fill(long[] a, int fromIndex, int toIndex, long val) { + if (fromIndex > toIndex) + throw new IllegalArgumentException(); for (int i = fromIndex; i < toIndex; i++) - { - a[i] = val; - } + a[i] = val; } /** - * Fill an array with a long value. + * Fill an array with a float value. * * @param a the array to fill * @param val the value to fill it with */ - public static void fill(long[]a, long val) + public static void fill(float[] a, float val) { - // This implementation is slightly inefficient timewise, but the extra - // effort over inlining it is O(1) and small, and I refuse to repeat code - // if it can be helped. fill(a, 0, a.length, val); } /** - * Fill a range of an array with a long value. + * Fill a range of an array with a float value. * * @param a the array to fill * @param fromIndex the index to fill from, inclusive * @param toIndex the index to fill to, exclusive * @param val the value to fill with + * @throws IllegalArgumentException if fromIndex > toIndex + * @throws ArrayIndexOutOfBoundsException if fromIndex < 0 + * || toIndex > a.length */ - public static void fill(long[]a, int fromIndex, int toIndex, long val) + public static void fill(float[] a, int fromIndex, int toIndex, float val) { + if (fromIndex > toIndex) + throw new IllegalArgumentException(); for (int i = fromIndex; i < toIndex; i++) - { - a[i] = val; - } + a[i] = val; } /** - * Fill an array with a short value. + * Fill an array with a double value. * * @param a the array to fill * @param val the value to fill it with */ - public static void fill(short[]a, short val) + public static void fill(double[] a, double val) { - // This implementation is slightly inefficient timewise, but the extra - // effort over inlining it is O(1) and small, and I refuse to repeat code - // if it can be helped. fill(a, 0, a.length, val); } /** - * Fill a range of an array with a short value. + * Fill a range of an array with a double value. * * @param a the array to fill * @param fromIndex the index to fill from, inclusive * @param toIndex the index to fill to, exclusive * @param val the value to fill with + * @throws IllegalArgumentException if fromIndex > toIndex + * @throws ArrayIndexOutOfBoundsException if fromIndex < 0 + * || toIndex > a.length */ - public static void fill(short[]a, int fromIndex, int toIndex, short val) + public static void fill(double[] a, int fromIndex, int toIndex, double val) { + if (fromIndex > toIndex) + throw new IllegalArgumentException(); for (int i = fromIndex; i < toIndex; i++) - { - a[i] = val; - } + a[i] = val; } /** @@ -1027,14 +929,11 @@ public class Arrays * * @param a the array to fill * @param val the value to fill it with - * @exception ClassCastException if val is not an instance of the element - * type of a. + * @throws ClassCastException if val is not an instance of the element + * type of a. */ - public static void fill(Object[]a, Object val) + public static void fill(Object[] a, Object val) { - // This implementation is slightly inefficient timewise, but the extra - // effort over inlining it is O(1) and small, and I refuse to repeat code - // if it can be helped. fill(a, 0, a.length, val); } @@ -1045,926 +944,1191 @@ public class Arrays * @param fromIndex the index to fill from, inclusive * @param toIndex the index to fill to, exclusive * @param val the value to fill with - * @exception ClassCastException if val is not an instance of the element - * type of a. + * @throws ClassCastException if val is not an instance of the element + * type of a. + * @throws IllegalArgumentException if fromIndex > toIndex + * @throws ArrayIndexOutOfBoundsException if fromIndex < 0 + * || toIndex > a.length */ - public static void fill(Object[]a, int fromIndex, int toIndex, Object val) + public static void fill(Object[] a, int fromIndex, int toIndex, Object val) { + if (fromIndex > toIndex) + throw new IllegalArgumentException(); for (int i = fromIndex; i < toIndex; i++) - { - a[i] = val; - } + a[i] = val; } + +// sort // Thanks to Paul Fisher <rao@gnu.org> for finding this quicksort algorithm - // as specified by Sun and porting it to Java. + // as specified by Sun and porting it to Java. The algorithm is an optimised + // quicksort, as described in Jon L. Bentley and M. Douglas McIlroy's + // "Engineering a Sort Function", Software-Practice and Experience, Vol. + // 23(11) P. 1249-1265 (November 1993). This algorithm gives n*log(n) + // performance on many arrays that would take quadratic time with a standard + // quicksort. /** - * Sort a byte array into ascending order. The sort algorithm is an optimised - * quicksort, as described in Jon L. Bentley and M. Douglas McIlroy's - * "Engineering a Sort Function", Software-Practice and Experience, Vol. - * 23(11) P. 1249-1265 (November 1993). This algorithm gives nlog(n) - * performance on many arrays that would take quadratic time with a standard - * quicksort. + * Performs a stable sort on the elements, arranging them according to their + * natural order. * - * @param a the array to sort + * @param a the byte array to sort */ - public static void sort(byte[]a) + public static void sort(byte[] a) { qsort(a, 0, a.length); } + /** + * Performs a stable sort on the elements, arranging them according to their + * natural order. + * + * @param a the byte array to sort + * @param fromIndex the first index to sort (inclusive) + * @param toIndex the last index to sort (exclusive) + * @throws IllegalArgumentException if fromIndex > toIndex + * @throws ArrayIndexOutOfBoundsException if fromIndex < 0 + * || toIndex > a.length + */ public static void sort(byte[] a, int fromIndex, int toIndex) { - qsort(a, fromIndex, toIndex); + if (fromIndex > toIndex) + throw new IllegalArgumentException(); + qsort(a, fromIndex, toIndex - fromIndex); } - private static int med3(int a, int b, int c, byte[]d) + /** + * Finds the index of the median of three array elements. + * + * @param a the first index + * @param b the second index + * @param c the third index + * @param d the array + * @return the index (a, b, or c) which has the middle value of the three + */ + private static int med3(int a, int b, int c, byte[] d) { - return d[a] < d[b] ? - (d[b] < d[c] ? b : d[a] < d[c] ? c : a) - : (d[b] > d[c] ? b : d[a] > d[c] ? c : a); + return (d[a] < d[b] + ? (d[b] < d[c] ? b : d[a] < d[c] ? c : a) + : (d[b] > d[c] ? b : d[a] > d[c] ? c : a)); } - private static void swap(int i, int j, byte[]a) + /** + * Swaps the elements at two locations of an array + * + * @param i the first index + * @param j the second index + * @param a the array + */ + private static void swap(int i, int j, byte[] a) { byte c = a[i]; a[i] = a[j]; a[j] = c; } - private static void qsort(byte[]a, int start, int n) - { - // use an insertion sort on small arrays - if (n <= 7) - { - for (int i = start + 1; i < start + n; i++) - for (int j = i; j > 0 && a[j - 1] > a[j]; j--) - swap(j, j - 1, a); - return; - } - - int pm = n / 2; // small arrays, middle element - if (n > 7) - { - int pl = start; - int pn = start + n - 1; - - if (n > 40) - { // big arrays, pseudomedian of 9 - int s = n / 8; - pl = med3(pl, pl + s, pl + 2 * s, a); - pm = med3(pm - s, pm, pm + s, a); - pn = med3(pn - 2 * s, pn - s, pn, a); - } - pm = med3(pl, pm, pn, a); // mid-size, med of 3 - } - - int pa, pb, pc, pd, pv; - int r; - - pv = start; - swap(pv, pm, a); - pa = pb = start; - pc = pd = start + n - 1; - - for (;;) - { - while (pb <= pc && (r = a[pb] - a[pv]) <= 0) - { - if (r == 0) - { - swap(pa, pb, a); - pa++; - } - pb++; - } - while (pc >= pb && (r = a[pc] - a[pv]) >= 0) - { - if (r == 0) - { - swap(pc, pd, a); - pd--; - } - pc--; - } - if (pb > pc) - break; - swap(pb, pc, a); - pb++; - pc--; - } - int pn = start + n; - int s; - s = Math.min(pa - start, pb - pa); - vecswap(start, pb - s, s, a); - s = Math.min(pd - pc, pn - pd - 1); - vecswap(pb, pn - s, s, a); - if ((s = pb - pa) > 1) - qsort(a, start, s); - if ((s = pd - pc) > 1) - qsort(a, pn - s, s); - } - - private static void vecswap(int i, int j, int n, byte[]a) + /** + * Swaps two ranges of an array. + * + * @param i the first range start + * @param j the second range start + * @param n the element count + * @param a the array + */ + private static void vecswap(int i, int j, int n, byte[] a) { - for (; n > 0; i++, j++, n--) + for ( ; n > 0; i++, j++, n--) swap(i, j, a); } /** - * Sort a char array into ascending order. The sort algorithm is an optimised - * quicksort, as described in Jon L. Bentley and M. Douglas McIlroy's - * "Engineering a Sort Function", Software-Practice and Experience, Vol. - * 23(11) P. 1249-1265 (November 1993). This algorithm gives nlog(n) - * performance on many arrays that would take quadratic time with a standard - * quicksort. + * Performs a recursive modified quicksort. * * @param a the array to sort + * @param from the start index (inclusive) + * @param count the number of elements to sort + */ + private static void qsort(byte[] array, int from, int count) + { + // Use an insertion sort on small arrays. + if (count <= 7) + { + for (int i = from + 1; i < from + count; i++) + for (int j = i; j > 0 && array[j - 1] > array[j]; j--) + swap(j, j - 1, array); + return; + } + + // Determine a good median element. + int mid = count / 2; + int lo = from; + int hi = from + count - 1; + + if (count > 40) + { // big arrays, pseudomedian of 9 + int s = count / 8; + lo = med3(lo, lo + s, lo + s + s, array); + mid = med3(mid - s, mid, mid + s, array); + hi = med3(hi - s - s, hi - s, hi, array); + } + mid = med3(lo, mid, hi, array); + + int a, b, c, d; + int comp; + + // Pull the median element out of the fray, and use it as a pivot. + swap(from, mid, array); + a = b = from + 1; + c = d = hi; + + // Repeatedly move b and c to each other, swapping elements so + // that all elements before index b are less than the pivot, and all + // elements after index c are greater than the pivot. a and b track + // the elements equal to the pivot. + while (true) + { + while (b <= c && (comp = array[b] - array[from]) <= 0) + { + if (comp == 0) + { + swap(a, b, array); + a++; + } + b++; + } + while (c >= b && (comp = array[c] - array[from]) >= 0) + { + if (comp == 0) + { + swap(c, d, array); + d--; + } + c--; + } + if (b > c) + break; + swap(b, c, array); + b++; + c--; + } + + // Swap pivot(s) back in place, the recurse on left and right sections. + int span; + span = Math.min(a - from, b - a); + vecswap(from, b - span, span, array); + + span = Math.min(d - c, hi - d - 1); + vecswap(b, hi - span + 1, span, array); + + span = b - a; + if (span > 1) + qsort(array, from, span); + + span = d - c; + if (span > 1) + qsort(array, hi - span + 1, span); + } + + /** + * Performs a stable sort on the elements, arranging them according to their + * natural order. + * + * @param a the char array to sort */ - public static void sort(char[]a) + public static void sort(char[] a) { qsort(a, 0, a.length); } + /** + * Performs a stable sort on the elements, arranging them according to their + * natural order. + * + * @param a the char array to sort + * @param fromIndex the first index to sort (inclusive) + * @param toIndex the last index to sort (exclusive) + * @throws IllegalArgumentException if fromIndex > toIndex + * @throws ArrayIndexOutOfBoundsException if fromIndex < 0 + * || toIndex > a.length + */ public static void sort(char[] a, int fromIndex, int toIndex) { - qsort(a, fromIndex, toIndex); + if (fromIndex > toIndex) + throw new IllegalArgumentException(); + qsort(a, fromIndex, toIndex - fromIndex); } - private static int med3(int a, int b, int c, char[]d) + /** + * Finds the index of the median of three array elements. + * + * @param a the first index + * @param b the second index + * @param c the third index + * @param d the array + * @return the index (a, b, or c) which has the middle value of the three + */ + private static int med3(int a, int b, int c, char[] d) { - return d[a] < d[b] ? - (d[b] < d[c] ? b : d[a] < d[c] ? c : a) - : (d[b] > d[c] ? b : d[a] > d[c] ? c : a); + return (d[a] < d[b] + ? (d[b] < d[c] ? b : d[a] < d[c] ? c : a) + : (d[b] > d[c] ? b : d[a] > d[c] ? c : a)); } - private static void swap(int i, int j, char[]a) + /** + * Swaps the elements at two locations of an array + * + * @param i the first index + * @param j the second index + * @param a the array + */ + private static void swap(int i, int j, char[] a) { char c = a[i]; a[i] = a[j]; a[j] = c; } - private static void qsort(char[]a, int start, int n) - { - // use an insertion sort on small arrays - if (n <= 7) - { - for (int i = start + 1; i < start + n; i++) - for (int j = i; j > 0 && a[j - 1] > a[j]; j--) - swap(j, j - 1, a); - return; - } - - int pm = n / 2; // small arrays, middle element - if (n > 7) - { - int pl = start; - int pn = start + n - 1; - - if (n > 40) - { // big arrays, pseudomedian of 9 - int s = n / 8; - pl = med3(pl, pl + s, pl + 2 * s, a); - pm = med3(pm - s, pm, pm + s, a); - pn = med3(pn - 2 * s, pn - s, pn, a); - } - pm = med3(pl, pm, pn, a); // mid-size, med of 3 - } - - int pa, pb, pc, pd, pv; - int r; - - pv = start; - swap(pv, pm, a); - pa = pb = start; - pc = pd = start + n - 1; - - for (;;) - { - while (pb <= pc && (r = a[pb] - a[pv]) <= 0) - { - if (r == 0) - { - swap(pa, pb, a); - pa++; - } - pb++; - } - while (pc >= pb && (r = a[pc] - a[pv]) >= 0) - { - if (r == 0) - { - swap(pc, pd, a); - pd--; - } - pc--; - } - if (pb > pc) - break; - swap(pb, pc, a); - pb++; - pc--; - } - int pn = start + n; - int s; - s = Math.min(pa - start, pb - pa); - vecswap(start, pb - s, s, a); - s = Math.min(pd - pc, pn - pd - 1); - vecswap(pb, pn - s, s, a); - if ((s = pb - pa) > 1) - qsort(a, start, s); - if ((s = pd - pc) > 1) - qsort(a, pn - s, s); - } - - private static void vecswap(int i, int j, int n, char[]a) + /** + * Swaps two ranges of an array. + * + * @param i the first range start + * @param j the second range start + * @param n the element count + * @param a the array + */ + private static void vecswap(int i, int j, int n, char[] a) { - for (; n > 0; i++, j++, n--) + for ( ; n > 0; i++, j++, n--) swap(i, j, a); } /** - * Sort a double array into ascending order. The sort algorithm is an - * optimised quicksort, as described in Jon L. Bentley and M. Douglas - * McIlroy's "Engineering a Sort Function", Software-Practice and Experience, - * Vol. 23(11) P. 1249-1265 (November 1993). This algorithm gives nlog(n) - * performance on many arrays that would take quadratic time with a standard - * quicksort. Note that this implementation, like Sun's, has undefined - * behaviour if the array contains any NaN values. + * Performs a recursive modified quicksort. * * @param a the array to sort + * @param from the start index (inclusive) + * @param count the number of elements to sort + */ + private static void qsort(char[] array, int from, int count) + { + // Use an insertion sort on small arrays. + if (count <= 7) + { + for (int i = from + 1; i < from + count; i++) + for (int j = i; j > 0 && array[j - 1] > array[j]; j--) + swap(j, j - 1, array); + return; + } + + // Determine a good median element. + int mid = count / 2; + int lo = from; + int hi = from + count - 1; + + if (count > 40) + { // big arrays, pseudomedian of 9 + int s = count / 8; + lo = med3(lo, lo + s, lo + s + s, array); + mid = med3(mid - s, mid, mid + s, array); + hi = med3(hi - s - s, hi - s, hi, array); + } + mid = med3(lo, mid, hi, array); + + int a, b, c, d; + int comp; + + // Pull the median element out of the fray, and use it as a pivot. + swap(from, mid, array); + a = b = from + 1; + c = d = hi; + + // Repeatedly move b and c to each other, swapping elements so + // that all elements before index b are less than the pivot, and all + // elements after index c are greater than the pivot. a and b track + // the elements equal to the pivot. + while (true) + { + while (b <= c && (comp = array[b] - array[from]) <= 0) + { + if (comp == 0) + { + swap(a, b, array); + a++; + } + b++; + } + while (c >= b && (comp = array[c] - array[from]) >= 0) + { + if (comp == 0) + { + swap(c, d, array); + d--; + } + c--; + } + if (b > c) + break; + swap(b, c, array); + b++; + c--; + } + + // Swap pivot(s) back in place, the recurse on left and right sections. + int span; + span = Math.min(a - from, b - a); + vecswap(from, b - span, span, array); + + span = Math.min(d - c, hi - d - 1); + vecswap(b, hi - span + 1, span, array); + + span = b - a; + if (span > 1) + qsort(array, from, span); + + span = d - c; + if (span > 1) + qsort(array, hi - span + 1, span); + } + + /** + * Performs a stable sort on the elements, arranging them according to their + * natural order. + * + * @param a the short array to sort */ - public static void sort(double[]a) + public static void sort(short[] a) { qsort(a, 0, a.length); } - public static void sort(double[] a, int fromIndex, int toIndex) + /** + * Performs a stable sort on the elements, arranging them according to their + * natural order. + * + * @param a the short array to sort + * @param fromIndex the first index to sort (inclusive) + * @param toIndex the last index to sort (exclusive) + * @throws IllegalArgumentException if fromIndex > toIndex + * @throws ArrayIndexOutOfBoundsException if fromIndex < 0 + * || toIndex > a.length + */ + public static void sort(short[] a, int fromIndex, int toIndex) { - qsort(a, fromIndex, toIndex); + if (fromIndex > toIndex) + throw new IllegalArgumentException(); + qsort(a, fromIndex, toIndex - fromIndex); } - private static int med3(int a, int b, int c, double[]d) + /** + * Finds the index of the median of three array elements. + * + * @param a the first index + * @param b the second index + * @param c the third index + * @param d the array + * @return the index (a, b, or c) which has the middle value of the three + */ + private static int med3(int a, int b, int c, short[] d) { - return d[a] < d[b] ? - (d[b] < d[c] ? b : d[a] < d[c] ? c : a) - : (d[b] > d[c] ? b : d[a] > d[c] ? c : a); + return (d[a] < d[b] + ? (d[b] < d[c] ? b : d[a] < d[c] ? c : a) + : (d[b] > d[c] ? b : d[a] > d[c] ? c : a)); } - private static void swap(int i, int j, double[]a) + /** + * Swaps the elements at two locations of an array + * + * @param i the first index + * @param j the second index + * @param a the array + */ + private static void swap(int i, int j, short[] a) { - double c = a[i]; + short c = a[i]; a[i] = a[j]; a[j] = c; } - private static void qsort(double[]a, int start, int n) - { - // use an insertion sort on small arrays - if (n <= 7) - { - for (int i = start + 1; i < start + n; i++) - for (int j = i; j > 0 && a[j - 1] > a[j]; j--) - swap(j, j - 1, a); - return; - } - - int pm = n / 2; // small arrays, middle element - if (n > 7) - { - int pl = start; - int pn = start + n - 1; - - if (n > 40) - { // big arrays, pseudomedian of 9 - int s = n / 8; - pl = med3(pl, pl + s, pl + 2 * s, a); - pm = med3(pm - s, pm, pm + s, a); - pn = med3(pn - 2 * s, pn - s, pn, a); - } - pm = med3(pl, pm, pn, a); // mid-size, med of 3 - } - - int pa, pb, pc, pd, pv; - double r; - - pv = start; - swap(pv, pm, a); - pa = pb = start; - pc = pd = start + n - 1; - - for (;;) - { - while (pb <= pc && (r = a[pb] - a[pv]) <= 0) - { - if (r == 0) - { - swap(pa, pb, a); - pa++; - } - pb++; - } - while (pc >= pb && (r = a[pc] - a[pv]) >= 0) - { - if (r == 0) - { - swap(pc, pd, a); - pd--; - } - pc--; - } - if (pb > pc) - break; - swap(pb, pc, a); - pb++; - pc--; - } - int pn = start + n; - int s; - s = Math.min(pa - start, pb - pa); - vecswap(start, pb - s, s, a); - s = Math.min(pd - pc, pn - pd - 1); - vecswap(pb, pn - s, s, a); - if ((s = pb - pa) > 1) - qsort(a, start, s); - if ((s = pd - pc) > 1) - qsort(a, pn - s, s); - } - - private static void vecswap(int i, int j, int n, double[]a) + /** + * Swaps two ranges of an array. + * + * @param i the first range start + * @param j the second range start + * @param n the element count + * @param a the array + */ + private static void vecswap(int i, int j, int n, short[] a) { - for (; n > 0; i++, j++, n--) + for ( ; n > 0; i++, j++, n--) swap(i, j, a); } /** - * Sort a float array into ascending order. The sort algorithm is an - * optimised quicksort, as described in Jon L. Bentley and M. Douglas - * McIlroy's "Engineering a Sort Function", Software-Practice and Experience, - * Vol. 23(11) P. 1249-1265 (November 1993). This algorithm gives nlog(n) - * performance on many arrays that would take quadratic time with a standard - * quicksort. Note that this implementation, like Sun's, has undefined - * behaviour if the array contains any NaN values. + * Performs a recursive modified quicksort. * * @param a the array to sort + * @param from the start index (inclusive) + * @param count the number of elements to sort */ - public static void sort(float[]a) + private static void qsort(short[] array, int from, int count) + { + // Use an insertion sort on small arrays. + if (count <= 7) + { + for (int i = from + 1; i < from + count; i++) + for (int j = i; j > 0 && array[j - 1] > array[j]; j--) + swap(j, j - 1, array); + return; + } + + // Determine a good median element. + int mid = count / 2; + int lo = from; + int hi = from + count - 1; + + if (count > 40) + { // big arrays, pseudomedian of 9 + int s = count / 8; + lo = med3(lo, lo + s, lo + s + s, array); + mid = med3(mid - s, mid, mid + s, array); + hi = med3(hi - s - s, hi - s, hi, array); + } + mid = med3(lo, mid, hi, array); + + int a, b, c, d; + int comp; + + // Pull the median element out of the fray, and use it as a pivot. + swap(from, mid, array); + a = b = from + 1; + c = d = hi; + + // Repeatedly move b and c to each other, swapping elements so + // that all elements before index b are less than the pivot, and all + // elements after index c are greater than the pivot. a and b track + // the elements equal to the pivot. + while (true) + { + while (b <= c && (comp = array[b] - array[from]) <= 0) + { + if (comp == 0) + { + swap(a, b, array); + a++; + } + b++; + } + while (c >= b && (comp = array[c] - array[from]) >= 0) + { + if (comp == 0) + { + swap(c, d, array); + d--; + } + c--; + } + if (b > c) + break; + swap(b, c, array); + b++; + c--; + } + + // Swap pivot(s) back in place, the recurse on left and right sections. + int span; + span = Math.min(a - from, b - a); + vecswap(from, b - span, span, array); + + span = Math.min(d - c, hi - d - 1); + vecswap(b, hi - span + 1, span, array); + + span = b - a; + if (span > 1) + qsort(array, from, span); + + span = d - c; + if (span > 1) + qsort(array, hi - span + 1, span); + } + + /** + * Performs a stable sort on the elements, arranging them according to their + * natural order. + * + * @param a the int array to sort + */ + public static void sort(int[] a) { qsort(a, 0, a.length); } - public static void sort(float[] a, int fromIndex, int toIndex) + /** + * Performs a stable sort on the elements, arranging them according to their + * natural order. + * + * @param a the int array to sort + * @param fromIndex the first index to sort (inclusive) + * @param toIndex the last index to sort (exclusive) + * @throws IllegalArgumentException if fromIndex > toIndex + * @throws ArrayIndexOutOfBoundsException if fromIndex < 0 + * || toIndex > a.length + */ + public static void sort(int[] a, int fromIndex, int toIndex) { - qsort(a, fromIndex, toIndex); + if (fromIndex > toIndex) + throw new IllegalArgumentException(); + qsort(a, fromIndex, toIndex - fromIndex); } - private static int med3(int a, int b, int c, float[]d) + /** + * Finds the index of the median of three array elements. + * + * @param a the first index + * @param b the second index + * @param c the third index + * @param d the array + * @return the index (a, b, or c) which has the middle value of the three + */ + private static int med3(int a, int b, int c, int[] d) { - return d[a] < d[b] ? - (d[b] < d[c] ? b : d[a] < d[c] ? c : a) - : (d[b] > d[c] ? b : d[a] > d[c] ? c : a); + return (d[a] < d[b] + ? (d[b] < d[c] ? b : d[a] < d[c] ? c : a) + : (d[b] > d[c] ? b : d[a] > d[c] ? c : a)); } - private static void swap(int i, int j, float[]a) + /** + * Swaps the elements at two locations of an array + * + * @param i the first index + * @param j the second index + * @param a the array + */ + private static void swap(int i, int j, int[] a) { - float c = a[i]; + int c = a[i]; a[i] = a[j]; a[j] = c; } - private static void qsort(float[]a, int start, int n) + /** + * Swaps two ranges of an array. + * + * @param i the first range start + * @param j the second range start + * @param n the element count + * @param a the array + */ + private static void vecswap(int i, int j, int n, int[] a) { - // use an insertion sort on small arrays - if (n <= 7) - { - for (int i = start + 1; i < start + n; i++) - for (int j = i; j > 0 && a[j - 1] > a[j]; j--) - swap(j, j - 1, a); - return; - } - - int pm = n / 2; // small arrays, middle element - if (n > 7) - { - int pl = start; - int pn = start + n - 1; - - if (n > 40) - { // big arrays, pseudomedian of 9 - int s = n / 8; - pl = med3(pl, pl + s, pl + 2 * s, a); - pm = med3(pm - s, pm, pm + s, a); - pn = med3(pn - 2 * s, pn - s, pn, a); - } - pm = med3(pl, pm, pn, a); // mid-size, med of 3 - } - - int pa, pb, pc, pd, pv; - float r; - - pv = start; - swap(pv, pm, a); - pa = pb = start; - pc = pd = start + n - 1; - - for (;;) - { - while (pb <= pc && (r = a[pb] - a[pv]) <= 0) - { - if (r == 0) - { - swap(pa, pb, a); - pa++; - } - pb++; - } - while (pc >= pb && (r = a[pc] - a[pv]) >= 0) - { - if (r == 0) - { - swap(pc, pd, a); - pd--; - } - pc--; - } - if (pb > pc) - break; - swap(pb, pc, a); - pb++; - pc--; - } - int pn = start + n; - int s; - s = Math.min(pa - start, pb - pa); - vecswap(start, pb - s, s, a); - s = Math.min(pd - pc, pn - pd - 1); - vecswap(pb, pn - s, s, a); - if ((s = pb - pa) > 1) - qsort(a, start, s); - if ((s = pd - pc) > 1) - qsort(a, pn - s, s); + for ( ; n > 0; i++, j++, n--) + swap(i, j, a); } - private static void vecswap(int i, int j, int n, float[]a) + /** + * Compares two integers in natural order, since a - b is inadequate. + * + * @param a the first int + * @param b the second int + * @return < 0, 0, or > 0 accorting to the comparison + */ + private static int compare(int a, int b) { - for (; n > 0; i++, j++, n--) - swap(i, j, a); + return a < b ? -1 : a == b ? 0 : 1; } /** - * Sort an int array into ascending order. The sort algorithm is an optimised - * quicksort, as described in Jon L. Bentley and M. Douglas McIlroy's - * "Engineering a Sort Function", Software-Practice and Experience, Vol. - * 23(11) P. 1249-1265 (November 1993). This algorithm gives nlog(n) - * performance on many arrays that would take quadratic time with a standard - * quicksort. + * Performs a recursive modified quicksort. * * @param a the array to sort + * @param from the start index (inclusive) + * @param count the number of elements to sort */ - public static void sort(int[]a) + private static void qsort(int[] array, int from, int count) + { + // Use an insertion sort on small arrays. + if (count <= 7) + { + for (int i = from + 1; i < from + count; i++) + for (int j = i; j > 0 && array[j - 1] > array[j]; j--) + swap(j, j - 1, array); + return; + } + + // Determine a good median element. + int mid = count / 2; + int lo = from; + int hi = from + count - 1; + + if (count > 40) + { // big arrays, pseudomedian of 9 + int s = count / 8; + lo = med3(lo, lo + s, lo + s + s, array); + mid = med3(mid - s, mid, mid + s, array); + hi = med3(hi - s - s, hi - s, hi, array); + } + mid = med3(lo, mid, hi, array); + + int a, b, c, d; + int comp; + + // Pull the median element out of the fray, and use it as a pivot. + swap(from, mid, array); + a = b = from + 1; + c = d = hi; + + // Repeatedly move b and c to each other, swapping elements so + // that all elements before index b are less than the pivot, and all + // elements after index c are greater than the pivot. a and b track + // the elements equal to the pivot. + while (true) + { + while (b <= c && (comp = compare(array[b], array[from])) <= 0) + { + if (comp == 0) + { + swap(a, b, array); + a++; + } + b++; + } + while (c >= b && (comp = compare(array[c], array[from])) >= 0) + { + if (comp == 0) + { + swap(c, d, array); + d--; + } + c--; + } + if (b > c) + break; + swap(b, c, array); + b++; + c--; + } + + // Swap pivot(s) back in place, the recurse on left and right sections. + int span; + span = Math.min(a - from, b - a); + vecswap(from, b - span, span, array); + + span = Math.min(d - c, hi - d - 1); + vecswap(b, hi - span + 1, span, array); + + span = b - a; + if (span > 1) + qsort(array, from, span); + + span = d - c; + if (span > 1) + qsort(array, hi - span + 1, span); + } + + /** + * Performs a stable sort on the elements, arranging them according to their + * natural order. + * + * @param a the long array to sort + */ + public static void sort(long[] a) { qsort(a, 0, a.length); } - public static void sort(int[] a, int fromIndex, int toIndex) + /** + * Performs a stable sort on the elements, arranging them according to their + * natural order. + * + * @param a the long array to sort + * @param fromIndex the first index to sort (inclusive) + * @param toIndex the last index to sort (exclusive) + * @throws IllegalArgumentException if fromIndex > toIndex + * @throws ArrayIndexOutOfBoundsException if fromIndex < 0 + * || toIndex > a.length + */ + public static void sort(long[] a, int fromIndex, int toIndex) { - qsort(a, fromIndex, toIndex); + if (fromIndex > toIndex) + throw new IllegalArgumentException(); + qsort(a, fromIndex, toIndex - fromIndex); } - private static int med3(int a, int b, int c, int[]d) + /** + * Finds the index of the median of three array elements. + * + * @param a the first index + * @param b the second index + * @param c the third index + * @param d the array + * @return the index (a, b, or c) which has the middle value of the three + */ + private static int med3(int a, int b, int c, long[] d) { - return d[a] < d[b] ? - (d[b] < d[c] ? b : d[a] < d[c] ? c : a) - : (d[b] > d[c] ? b : d[a] > d[c] ? c : a); + return (d[a] < d[b] + ? (d[b] < d[c] ? b : d[a] < d[c] ? c : a) + : (d[b] > d[c] ? b : d[a] > d[c] ? c : a)); } - private static void swap(int i, int j, int[]a) + /** + * Swaps the elements at two locations of an array + * + * @param i the first index + * @param j the second index + * @param a the array + */ + private static void swap(int i, int j, long[] a) { - int c = a[i]; + long c = a[i]; a[i] = a[j]; a[j] = c; } - private static void qsort(int[]a, int start, int n) + /** + * Swaps two ranges of an array. + * + * @param i the first range start + * @param j the second range start + * @param n the element count + * @param a the array + */ + private static void vecswap(int i, int j, int n, long[] a) { - // use an insertion sort on small arrays - if (n <= 7) - { - for (int i = start + 1; i < start + n; i++) - for (int j = i; j > 0 && a[j - 1] > a[j]; j--) - swap(j, j - 1, a); - return; - } - - int pm = n / 2; // small arrays, middle element - if (n > 7) - { - int pl = start; - int pn = start + n - 1; - - if (n > 40) - { // big arrays, pseudomedian of 9 - int s = n / 8; - pl = med3(pl, pl + s, pl + 2 * s, a); - pm = med3(pm - s, pm, pm + s, a); - pn = med3(pn - 2 * s, pn - s, pn, a); - } - pm = med3(pl, pm, pn, a); // mid-size, med of 3 - } - - int pa, pb, pc, pd, pv; - int r; - - pv = start; - swap(pv, pm, a); - pa = pb = start; - pc = pd = start + n - 1; - - for (;;) - { - while (pb <= pc && (r = a[pb] - a[pv]) <= 0) - { - if (r == 0) - { - swap(pa, pb, a); - pa++; - } - pb++; - } - while (pc >= pb && (r = a[pc] - a[pv]) >= 0) - { - if (r == 0) - { - swap(pc, pd, a); - pd--; - } - pc--; - } - if (pb > pc) - break; - swap(pb, pc, a); - pb++; - pc--; - } - int pn = start + n; - int s; - s = Math.min(pa - start, pb - pa); - vecswap(start, pb - s, s, a); - s = Math.min(pd - pc, pn - pd - 1); - vecswap(pb, pn - s, s, a); - if ((s = pb - pa) > 1) - qsort(a, start, s); - if ((s = pd - pc) > 1) - qsort(a, pn - s, s); + for ( ; n > 0; i++, j++, n--) + swap(i, j, a); } - private static void vecswap(int i, int j, int n, int[]a) + /** + * Compares two longs in natural order, since a - b is inadequate. + * + * @param a the first long + * @param b the second long + * @return < 0, 0, or > 0 accorting to the comparison + */ + private static int compare(long a, long b) { - for (; n > 0; i++, j++, n--) - swap(i, j, a); + return a < b ? -1 : a == b ? 0 : 1; } /** - * Sort a long array into ascending order. The sort algorithm is an optimised - * quicksort, as described in Jon L. Bentley and M. Douglas McIlroy's - * "Engineering a Sort Function", Software-Practice and Experience, Vol. - * 23(11) P. 1249-1265 (November 1993). This algorithm gives nlog(n) - * performance on many arrays that would take quadratic time with a standard - * quicksort. + * Performs a recursive modified quicksort. * * @param a the array to sort + * @param from the start index (inclusive) + * @param count the number of elements to sort + */ + private static void qsort(long[] array, int from, int count) + { + // Use an insertion sort on small arrays. + if (count <= 7) + { + for (int i = from + 1; i < from + count; i++) + for (int j = i; j > 0 && array[j - 1] > array[j]; j--) + swap(j, j - 1, array); + return; + } + + // Determine a good median element. + int mid = count / 2; + int lo = from; + int hi = from + count - 1; + + if (count > 40) + { // big arrays, pseudomedian of 9 + int s = count / 8; + lo = med3(lo, lo + s, lo + s + s, array); + mid = med3(mid - s, mid, mid + s, array); + hi = med3(hi - s - s, hi - s, hi, array); + } + mid = med3(lo, mid, hi, array); + + int a, b, c, d; + int comp; + + // Pull the median element out of the fray, and use it as a pivot. + swap(from, mid, array); + a = b = from + 1; + c = d = hi; + + // Repeatedly move b and c to each other, swapping elements so + // that all elements before index b are less than the pivot, and all + // elements after index c are greater than the pivot. a and b track + // the elements equal to the pivot. + while (true) + { + while (b <= c && (comp = compare(array[b], array[from])) <= 0) + { + if (comp == 0) + { + swap(a, b, array); + a++; + } + b++; + } + while (c >= b && (comp = compare(array[c], array[from])) >= 0) + { + if (comp == 0) + { + swap(c, d, array); + d--; + } + c--; + } + if (b > c) + break; + swap(b, c, array); + b++; + c--; + } + + // Swap pivot(s) back in place, the recurse on left and right sections. + int span; + span = Math.min(a - from, b - a); + vecswap(from, b - span, span, array); + + span = Math.min(d - c, hi - d - 1); + vecswap(b, hi - span + 1, span, array); + + span = b - a; + if (span > 1) + qsort(array, from, span); + + span = d - c; + if (span > 1) + qsort(array, hi - span + 1, span); + } + + /** + * Performs a stable sort on the elements, arranging them according to their + * natural order. + * + * @param a the float array to sort */ - public static void sort(long[]a) + public static void sort(float[] a) { qsort(a, 0, a.length); } - public static void sort(long[] a, int fromIndex, int toIndex) + /** + * Performs a stable sort on the elements, arranging them according to their + * natural order. + * + * @param a the float array to sort + * @param fromIndex the first index to sort (inclusive) + * @param toIndex the last index to sort (exclusive) + * @throws IllegalArgumentException if fromIndex > toIndex + * @throws ArrayIndexOutOfBoundsException if fromIndex < 0 + * || toIndex > a.length + */ + public static void sort(float[] a, int fromIndex, int toIndex) { - qsort(a, fromIndex, toIndex); + if (fromIndex > toIndex) + throw new IllegalArgumentException(); + qsort(a, fromIndex, toIndex - fromIndex); } - private static int med3(int a, int b, int c, long[]d) + /** + * Finds the index of the median of three array elements. + * + * @param a the first index + * @param b the second index + * @param c the third index + * @param d the array + * @return the index (a, b, or c) which has the middle value of the three + */ + private static int med3(int a, int b, int c, float[] d) { - return d[a] < d[b] ? - (d[b] < d[c] ? b : d[a] < d[c] ? c : a) - : (d[b] > d[c] ? b : d[a] > d[c] ? c : a); + return (Float.compare(d[a], d[b]) < 0 + ? (Float.compare(d[b], d[c]) < 0 ? b + : Float.compare(d[a], d[c]) < 0 ? c : a) + : (Float.compare(d[b], d[c]) > 0 ? b + : Float.compare(d[a], d[c]) > 0 ? c : a)); } - private static void swap(int i, int j, long[]a) + /** + * Swaps the elements at two locations of an array + * + * @param i the first index + * @param j the second index + * @param a the array + */ + private static void swap(int i, int j, float[] a) { - long c = a[i]; + float c = a[i]; a[i] = a[j]; a[j] = c; } - private static void qsort(long[]a, int start, int n) - { - // use an insertion sort on small arrays - if (n <= 7) - { - for (int i = start + 1; i < start + n; i++) - for (int j = i; j > 0 && a[j - 1] > a[j]; j--) - swap(j, j - 1, a); - return; - } - - int pm = n / 2; // small arrays, middle element - if (n > 7) - { - int pl = start; - int pn = start + n - 1; - - if (n > 40) - { // big arrays, pseudomedian of 9 - int s = n / 8; - pl = med3(pl, pl + s, pl + 2 * s, a); - pm = med3(pm - s, pm, pm + s, a); - pn = med3(pn - 2 * s, pn - s, pn, a); - } - pm = med3(pl, pm, pn, a); // mid-size, med of 3 - } - - int pa, pb, pc, pd, pv; - long r; - - pv = start; - swap(pv, pm, a); - pa = pb = start; - pc = pd = start + n - 1; - - for (;;) - { - while (pb <= pc && (r = a[pb] - a[pv]) <= 0) - { - if (r == 0) - { - swap(pa, pb, a); - pa++; - } - pb++; - } - while (pc >= pb && (r = a[pc] - a[pv]) >= 0) - { - if (r == 0) - { - swap(pc, pd, a); - pd--; - } - pc--; - } - if (pb > pc) - break; - swap(pb, pc, a); - pb++; - pc--; - } - int pn = start + n; - int s; - s = Math.min(pa - start, pb - pa); - vecswap(start, pb - s, s, a); - s = Math.min(pd - pc, pn - pd - 1); - vecswap(pb, pn - s, s, a); - if ((s = pb - pa) > 1) - qsort(a, start, s); - if ((s = pd - pc) > 1) - qsort(a, pn - s, s); - } - - private static void vecswap(int i, int j, int n, long[]a) + /** + * Swaps two ranges of an array. + * + * @param i the first range start + * @param j the second range start + * @param n the element count + * @param a the array + */ + private static void vecswap(int i, int j, int n, float[] a) { - for (; n > 0; i++, j++, n--) + for ( ; n > 0; i++, j++, n--) swap(i, j, a); } /** - * Sort a short array into ascending order. The sort algorithm is an - * optimised quicksort, as described in Jon L. Bentley and M. Douglas - * McIlroy's "Engineering a Sort Function", Software-Practice and Experience, - * Vol. 23(11) P. 1249-1265 (November 1993). This algorithm gives nlog(n) - * performance on many arrays that would take quadratic time with a standard - * quicksort. + * Performs a recursive modified quicksort. * * @param a the array to sort + * @param from the start index (inclusive) + * @param count the number of elements to sort */ - public static void sort(short[]a) + private static void qsort(float[] array, int from, int count) + { + // Use an insertion sort on small arrays. + if (count <= 7) + { + for (int i = from + 1; i < from + count; i++) + for (int j = i; + j > 0 && Float.compare(array[j - 1], array[j]) > 0; + j--) + { + swap(j, j - 1, array); + } + return; + } + + // Determine a good median element. + int mid = count / 2; + int lo = from; + int hi = from + count - 1; + + if (count > 40) + { // big arrays, pseudomedian of 9 + int s = count / 8; + lo = med3(lo, lo + s, lo + s + s, array); + mid = med3(mid - s, mid, mid + s, array); + hi = med3(hi - s - s, hi - s, hi, array); + } + mid = med3(lo, mid, hi, array); + + int a, b, c, d; + int comp; + + // Pull the median element out of the fray, and use it as a pivot. + swap(from, mid, array); + a = b = from + 1; + c = d = hi; + + // Repeatedly move b and c to each other, swapping elements so + // that all elements before index b are less than the pivot, and all + // elements after index c are greater than the pivot. a and b track + // the elements equal to the pivot. + while (true) + { + while (b <= c && (comp = Float.compare(array[b], array[from])) <= 0) + { + if (comp == 0) + { + swap(a, b, array); + a++; + } + b++; + } + while (c >= b && (comp = Float.compare(array[c], array[from])) >= 0) + { + if (comp == 0) + { + swap(c, d, array); + d--; + } + c--; + } + if (b > c) + break; + swap(b, c, array); + b++; + c--; + } + + // Swap pivot(s) back in place, the recurse on left and right sections. + int span; + span = Math.min(a - from, b - a); + vecswap(from, b - span, span, array); + + span = Math.min(d - c, hi - d - 1); + vecswap(b, hi - span + 1, span, array); + + span = b - a; + if (span > 1) + qsort(array, from, span); + + span = d - c; + if (span > 1) + qsort(array, hi - span + 1, span); + } + + /** + * Performs a stable sort on the elements, arranging them according to their + * natural order. + * + * @param a the double array to sort + */ + public static void sort(double[] a) { qsort(a, 0, a.length); } - public static void sort(short[] a, int fromIndex, int toIndex) + /** + * Performs a stable sort on the elements, arranging them according to their + * natural order. + * + * @param a the double array to sort + * @param fromIndex the first index to sort (inclusive) + * @param toIndex the last index to sort (exclusive) + * @throws IllegalArgumentException if fromIndex > toIndex + * @throws ArrayIndexOutOfBoundsException if fromIndex < 0 + * || toIndex > a.length + */ + public static void sort(double[] a, int fromIndex, int toIndex) { - qsort(a, fromIndex, toIndex); + if (fromIndex > toIndex) + throw new IllegalArgumentException(); + qsort(a, fromIndex, toIndex - fromIndex); } - private static int med3(int a, int b, int c, short[]d) + /** + * Finds the index of the median of three array elements. + * + * @param a the first index + * @param b the second index + * @param c the third index + * @param d the array + * @return the index (a, b, or c) which has the middle value of the three + */ + private static int med3(int a, int b, int c, double[] d) { - return d[a] < d[b] ? - (d[b] < d[c] ? b : d[a] < d[c] ? c : a) - : (d[b] > d[c] ? b : d[a] > d[c] ? c : a); + return (Double.compare(d[a], d[b]) < 0 + ? (Double.compare(d[b], d[c]) < 0 ? b + : Double.compare(d[a], d[c]) < 0 ? c : a) + : (Double.compare(d[b], d[c]) > 0 ? b + : Double.compare(d[a], d[c]) > 0 ? c : a)); } - private static void swap(int i, int j, short[]a) + /** + * Swaps the elements at two locations of an array + * + * @param i the first index + * @param j the second index + * @param a the array + */ + private static void swap(int i, int j, double[] a) { - short c = a[i]; + double c = a[i]; a[i] = a[j]; a[j] = c; } - private static void qsort(short[]a, int start, int n) - { - // use an insertion sort on small arrays - if (n <= 7) - { - for (int i = start + 1; i < start + n; i++) - for (int j = i; j > 0 && a[j - 1] > a[j]; j--) - swap(j, j - 1, a); - return; - } - - int pm = n / 2; // small arrays, middle element - if (n > 7) - { - int pl = start; - int pn = start + n - 1; - - if (n > 40) - { // big arrays, pseudomedian of 9 - int s = n / 8; - pl = med3(pl, pl + s, pl + 2 * s, a); - pm = med3(pm - s, pm, pm + s, a); - pn = med3(pn - 2 * s, pn - s, pn, a); - } - pm = med3(pl, pm, pn, a); // mid-size, med of 3 - } - - int pa, pb, pc, pd, pv; - int r; - - pv = start; - swap(pv, pm, a); - pa = pb = start; - pc = pd = start + n - 1; - - for (;;) - { - while (pb <= pc && (r = a[pb] - a[pv]) <= 0) - { - if (r == 0) - { - swap(pa, pb, a); - pa++; - } - pb++; - } - while (pc >= pb && (r = a[pc] - a[pv]) >= 0) - { - if (r == 0) - { - swap(pc, pd, a); - pd--; - } - pc--; - } - if (pb > pc) - break; - swap(pb, pc, a); - pb++; - pc--; - } - int pn = start + n; - int s; - s = Math.min(pa - start, pb - pa); - vecswap(start, pb - s, s, a); - s = Math.min(pd - pc, pn - pd - 1); - vecswap(pb, pn - s, s, a); - if ((s = pb - pa) > 1) - qsort(a, start, s); - if ((s = pd - pc) > 1) - qsort(a, pn - s, s); - } - - private static void vecswap(int i, int j, int n, short[]a) + /** + * Swaps two ranges of an array. + * + * @param i the first range start + * @param j the second range start + * @param n the element count + * @param a the array + */ + private static void vecswap(int i, int j, int n, double[] a) { - for (; n > 0; i++, j++, n--) + for ( ; n > 0; i++, j++, n--) swap(i, j, a); } /** - * The bulk of the work for the object sort routines. In general, - * the code attempts to be simple rather than fast, the idea being - * that a good optimising JIT will be able to optimise it better - * than I can, and if I try it will make it more confusing for the - * JIT. + * Performs a recursive modified quicksort. + * + * @param a the array to sort + * @param from the start index (inclusive) + * @param count the number of elements to sort */ - private static void mergeSort(Object[]a, int from, int to, Comparator c) - { - // First presort the array in chunks of length 6 with insertion sort. - // mergesort would give too much overhead for this length. - for (int chunk = from; chunk < to; chunk += 6) - { - int end = Math.min(chunk + 6, to); - for (int i = chunk + 1; i < end; i++) - { - if (c.compare(a[i - 1], a[i]) > 0) - { - // not already sorted - int j = i; - Object elem = a[j]; - do - { - a[j] = a[j - 1]; - j--; - } - while (j > chunk && c.compare(a[j - 1], elem) > 0); - a[j] = elem; - } - } - } - - int len = to - from; - // If length is smaller or equal 6 we are done. - if (len <= 6) - return; - - Object[]src = a; - Object[]dest = new Object[len]; - Object[]t = null; // t is used for swapping src and dest - - // The difference of the fromIndex of the src and dest array. - int srcDestDiff = -from; - - // The merges are done in this loop - for (int size = 6; size < len; size <<= 1) - { - for (int start = from; start < to; start += size << 1) - { - // mid ist the start of the second sublist; - // end the start of the next sublist (or end of array). - int mid = start + size; - int end = Math.min(to, mid + size); - - // The second list is empty or the elements are already in - // order - no need to merge - if (mid >= end || c.compare(src[mid - 1], src[mid]) <= 0) - { - System.arraycopy(src, start, - dest, start + srcDestDiff, end - start); - - // The two halves just need swapping - no need to merge - } - else if (c.compare(src[start], src[end - 1]) > 0) - { - System.arraycopy(src, start, - dest, end - size + srcDestDiff, size); - System.arraycopy(src, mid, - dest, start + srcDestDiff, end - mid); - - } - else - { - // Declare a lot of variables to save repeating - // calculations. Hopefully a decent JIT will put these - // in registers and make this fast - int p1 = start; - int p2 = mid; - int i = start + srcDestDiff; - - // The main merge loop; terminates as soon as either - // half is ended - while (p1 < mid && p2 < end) - { - dest[i++] = - src[c.compare(src[p1], src[p2]) <= 0 ? p1++ : p2++]; - } - - // Finish up by copying the remainder of whichever half - // wasn't finished. - if (p1 < mid) - System.arraycopy(src, p1, dest, i, mid - p1); - else - System.arraycopy(src, p2, dest, i, end - p2); - } - } - // swap src and dest ready for the next merge - t = src; - src = dest; - dest = t; - from += srcDestDiff; - to += srcDestDiff; - srcDestDiff = -srcDestDiff; - } - - // make sure the result ends up back in the right place. Note - // that src and dest may have been swapped above, so src - // contains the sorted array. - if (src != a) - { - // Note that from == 0. - System.arraycopy(src, 0, a, srcDestDiff, to); - } + private static void qsort(double[] array, int from, int count) + { + // Use an insertion sort on small arrays. + if (count <= 7) + { + for (int i = from + 1; i < from + count; i++) + for (int j = i; + j > 0 && Double.compare(array[j - 1], array[j]) > 0; + j--) + { + swap(j, j - 1, array); + } + return; + } + + // Determine a good median element. + int mid = count / 2; + int lo = from; + int hi = from + count - 1; + + if (count > 40) + { // big arrays, pseudomedian of 9 + int s = count / 8; + lo = med3(lo, lo + s, lo + s + s, array); + mid = med3(mid - s, mid, mid + s, array); + hi = med3(hi - s - s, hi - s, hi, array); + } + mid = med3(lo, mid, hi, array); + + int a, b, c, d; + int comp; + + // Pull the median element out of the fray, and use it as a pivot. + swap(from, mid, array); + a = b = from + 1; + c = d = hi; + + // Repeatedly move b and c to each other, swapping elements so + // that all elements before index b are less than the pivot, and all + // elements after index c are greater than the pivot. a and b track + // the elements equal to the pivot. + while (true) + { + while (b <= c && (comp = Double.compare(array[b], array[from])) <= 0) + { + if (comp == 0) + { + swap(a, b, array); + a++; + } + b++; + } + while (c >= b && (comp = Double.compare(array[c], array[from])) >= 0) + { + if (comp == 0) + { + swap(c, d, array); + d--; + } + c--; + } + if (b > c) + break; + swap(b, c, array); + b++; + c--; + } + + // Swap pivot(s) back in place, the recurse on left and right sections. + int span; + span = Math.min(a - from, b - a); + vecswap(from, b - span, span, array); + + span = Math.min(d - c, hi - d - 1); + vecswap(b, hi - span + 1, span, array); + + span = b - a; + if (span > 1) + qsort(array, from, span); + + span = d - c; + if (span > 1) + qsort(array, hi - span + 1, span); } /** @@ -1972,18 +2136,19 @@ public class Arrays * guaranteed to be stable, that is, equal elements will not be reordered. * The sort algorithm is a mergesort with the merge omitted if the last * element of one half comes before the first element of the other half. This - * algorithm gives guaranteed O(nlog(n)) time, at the expense of making a + * algorithm gives guaranteed O(n*log(n)) time, at the expense of making a * copy of the array. * * @param a the array to be sorted - * @exception ClassCastException if any two elements are not mutually - * comparable - * @exception NullPointerException if an element is null (since - * null.compareTo cannot work) + * @throws ClassCastException if any two elements are not mutually + * comparable + * @throws NullPointerException if an element is null (since + * null.compareTo cannot work) + * @see Comparable */ - public static void sort(Object[]a) + public static void sort(Object[] a) { - mergeSort(a, 0, a.length, defaultComparator); + sort(a, 0, a.length, null); } /** @@ -1991,17 +2156,20 @@ public class Arrays * guaranteed to be stable, that is, equal elements will not be reordered. * The sort algorithm is a mergesort with the merge omitted if the last * element of one half comes before the first element of the other half. This - * algorithm gives guaranteed O(nlog(n)) time, at the expense of making a + * algorithm gives guaranteed O(n*log(n)) time, at the expense of making a * copy of the array. * * @param a the array to be sorted - * @param c a Comparator to use in sorting the array - * @exception ClassCastException if any two elements are not mutually - * comparable by the Comparator provided + * @param c a Comparator to use in sorting the array; or null to indicate + * the elements' natural order + * @throws ClassCastException if any two elements are not mutually + * comparable by the Comparator provided + * @throws NullPointerException if a null element is compared with natural + * ordering (only possible when c is null) */ - public static void sort(Object[]a, Comparator c) + public static void sort(Object[] a, Comparator c) { - mergeSort(a, 0, a.length, c); + sort(a, 0, a.length, c); } /** @@ -2009,24 +2177,23 @@ public class Arrays * guaranteed to be stable, that is, equal elements will not be reordered. * The sort algorithm is a mergesort with the merge omitted if the last * element of one half comes before the first element of the other half. This - * algorithm gives guaranteed O(nlog(n)) time, at the expense of making a + * algorithm gives guaranteed O(n*log(n)) time, at the expense of making a * copy of the array. * * @param a the array to be sorted - * @param fromIndex the index of the first element to be sorted. - * @param toIndex the index of the last element to be sorted plus one. - * @exception ClassCastException if any two elements are not mutually - * comparable by the Comparator provided - * @exception ArrayIndexOutOfBoundsException, if fromIndex and toIndex - * are not in range. - * @exception IllegalArgumentException if fromIndex > toIndex + * @param fromIndex the index of the first element to be sorted + * @param toIndex the index of the last element to be sorted plus one + * @throws ClassCastException if any two elements are not mutually + * comparable + * @throws NullPointerException if an element is null (since + * null.compareTo cannot work) + * @throws ArrayIndexOutOfBoundsException, if fromIndex and toIndex + * are not in range. + * @throws IllegalArgumentException if fromIndex > toIndex */ - public static void sort(Object[]a, int fromIndex, int toIndex) + public static void sort(Object[] a, int fromIndex, int toIndex) { - if (fromIndex > toIndex) - throw new IllegalArgumentException("fromIndex " + fromIndex - + " > toIndex " + toIndex); - mergeSort(a, fromIndex, toIndex, defaultComparator); + sort(a, fromIndex, toIndex, null); } /** @@ -2034,56 +2201,195 @@ public class Arrays * guaranteed to be stable, that is, equal elements will not be reordered. * The sort algorithm is a mergesort with the merge omitted if the last * element of one half comes before the first element of the other half. This - * algorithm gives guaranteed O(nlog(n)) time, at the expense of making a + * algorithm gives guaranteed O(n*log(n)) time, at the expense of making a * copy of the array. * * @param a the array to be sorted - * @param fromIndex the index of the first element to be sorted. - * @param toIndex the index of the last element to be sorted plus one. - * @param c a Comparator to use in sorting the array - * @exception ClassCastException if any two elements are not mutually - * comparable by the Comparator provided - * @exception ArrayIndexOutOfBoundsException, if fromIndex and toIndex - * are not in range. - * @exception IllegalArgumentException if fromIndex > toIndex + * @param fromIndex the index of the first element to be sorted + * @param toIndex the index of the last element to be sorted plus one + * @param c a Comparator to use in sorting the array; or null to indicate + * the elements' natural order + * @throws ClassCastException if any two elements are not mutually + * comparable by the Comparator provided + * @throws ArrayIndexOutOfBoundsException, if fromIndex and toIndex + * are not in range. + * @throws IllegalArgumentException if fromIndex > toIndex + * @throws NullPointerException if a null element is compared with natural + * ordering (only possible when c is null) */ - public static void sort(Object[]a, int fromIndex, int toIndex, Comparator c) + public static void sort(Object[] a, int fromIndex, int toIndex, Comparator c) { if (fromIndex > toIndex) throw new IllegalArgumentException("fromIndex " + fromIndex - + " > toIndex " + toIndex); - mergeSort(a, fromIndex, toIndex, c); + + " > toIndex " + toIndex); + + // In general, the code attempts to be simple rather than fast, the + // idea being that a good optimising JIT will be able to optimise it + // better than I can, and if I try it will make it more confusing for + // the JIT. First presort the array in chunks of length 6 with insertion + // sort. A mergesort would give too much overhead for this length. + for (int chunk = fromIndex; chunk < toIndex; chunk += 6) + { + int end = Math.min(chunk + 6, toIndex); + for (int i = chunk + 1; i < end; i++) + { + if (Collections.compare(a[i - 1], a[i], c) > 0) + { + // not already sorted + int j = i; + Object elem = a[j]; + do + { + a[j] = a[j - 1]; + j--; + } + while (j > chunk + && Collections.compare(a[j - 1], elem, c) > 0); + a[j] = elem; + } + } + } + + int len = toIndex - fromIndex; + // If length is smaller or equal 6 we are done. + if (len <= 6) + return; + + Object[] src = a; + Object[] dest = new Object[len]; + Object[] t = null; // t is used for swapping src and dest + + // The difference of the fromIndex of the src and dest array. + int srcDestDiff = -fromIndex; + + // The merges are done in this loop + for (int size = 6; size < len; size <<= 1) + { + for (int start = fromIndex; start < toIndex; start += size << 1) + { + // mid is the start of the second sublist; + // end the start of the next sublist (or end of array). + int mid = start + size; + int end = Math.min(toIndex, mid + size); + + // The second list is empty or the elements are already in + // order - no need to merge + if (mid >= end + || Collections.compare(src[mid - 1], src[mid], c) <= 0) + { + System.arraycopy(src, start, + dest, start + srcDestDiff, end - start); + + // The two halves just need swapping - no need to merge + } + else if (Collections.compare(src[start], src[end - 1], c) > 0) + { + System.arraycopy(src, start, + dest, end - size + srcDestDiff, size); + System.arraycopy(src, mid, + dest, start + srcDestDiff, end - mid); + + } + else + { + // Declare a lot of variables to save repeating + // calculations. Hopefully a decent JIT will put these + // in registers and make this fast + int p1 = start; + int p2 = mid; + int i = start + srcDestDiff; + + // The main merge loop; terminates as soon as either + // half is ended + while (p1 < mid && p2 < end) + { + dest[i++] = + src[(Collections.compare(src[p1], src[p2], c) <= 0 + ? p1++ : p2++)]; + } + + // Finish up by copying the remainder of whichever half + // wasn't finished. + if (p1 < mid) + System.arraycopy(src, p1, dest, i, mid - p1); + else + System.arraycopy(src, p2, dest, i, end - p2); + } + } + // swap src and dest ready for the next merge + t = src; + src = dest; + dest = t; + fromIndex += srcDestDiff; + toIndex += srcDestDiff; + srcDestDiff = -srcDestDiff; + } + + // make sure the result ends up back in the right place. Note + // that src and dest may have been swapped above, so src + // contains the sorted array. + if (src != a) + { + // Note that fromIndex == 0. + System.arraycopy(src, 0, a, srcDestDiff, toIndex); + } } /** * Returns a list "view" of the specified array. This method is intended to * make it easy to use the Collections API with existing array-based APIs and - * programs. + * programs. Changes in the list or the array show up in both places. The + * list does not support element addition or removal, but does permit + * value modification. The returned list implements both Serializable and + * RandomAccess. * * @param a the array to return a view of - * @returns a fixed-size list, changes to which "write through" to the array + * @return a fixed-size list, changes to which "write through" to the array + * @see Serializable + * @see RandomAccess + * @see Arrays.ArrayList */ - public static List asList(final Object[]a) + public static List asList(final Object[] a) { - if (a == null) - { - throw new NullPointerException(); - } - - return new ListImpl(a); + return new Arrays.ArrayList(a); } - /** - * Inner class used by asList(Object[]) to provide a list interface - * to an array. The methods are all simple enough to be self documenting. - * Note: When Sun fully specify serialized forms, this class will have to - * be renamed. + * Inner class used by {@link #asList(Object[])} to provide a list interface + * to an array. The name, though it clashes with java.util.ArrayList, is + * Sun's choice for Serialization purposes. Element addition and removal + * is prohibited, but values can be modified. + * + * @author Eric Blake <ebb9@email.byu.edu> + * @status updated to 1.4 */ - private static class ListImpl extends AbstractList - { - ListImpl(Object[]a) + private static final class ArrayList extends AbstractList + implements Serializable, RandomAccess + { + // We override the necessary methods, plus others which will be much + // more efficient with direct iteration rather than relying on iterator(). + + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = -2764017481108945198L; + + /** + * The array we are viewing. + * @serial the array + */ + private final Object[] a; + + /** + * Construct a list view of the array. + * @param a the array to view + * @throws NullPointerException if a is null + */ + ArrayList(Object[] a) { + // We have to explicitly check. + if (a == null) + throw new NullPointerException(); this.a = a; } @@ -2104,6 +2410,45 @@ public class Arrays return old; } - private Object[] a; + public boolean contains(Object o) + { + return lastIndexOf(o) >= 0; + } + + public int indexOf(Object o) + { + int size = a.length; + for (int i = 0; i < size; i++) + if (equals(o, a[i])) + return i; + return -1; + } + + public int lastIndexOf(Object o) + { + int i = a.length; + while (--i >= 0) + if (equals(o, a[i])) + return i; + return -1; + } + + public Object[] toArray() + { + return (Object[]) a.clone(); + } + + public Object[] toArray(Object[] array) + { + int size = a.length; + if (array.length < size) + array = (Object[]) + Array.newInstance(array.getClass().getComponentType(), size); + else if (array.length > size) + array[size] = null; + + System.arraycopy(a, 0, array, 0, size); + return array; + } } } diff --git a/libjava/java/util/BasicMapEntry.java b/libjava/java/util/BasicMapEntry.java index f858cb4ebd9..48fcc146664 100644 --- a/libjava/java/util/BasicMapEntry.java +++ b/libjava/java/util/BasicMapEntry.java @@ -1,6 +1,6 @@ /* BasicMapEntry.java -- a class providing a plain-vanilla implementation of the Map.Entry interface; could be used anywhere in java.util - Copyright (C) 1998, 2000 Free Software Foundation, Inc. + Copyright (C) 1998, 2000, 2001 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -29,52 +29,113 @@ executable file might be covered by the GNU General Public License. */ package java.util; /** - * A class which implements Map.Entry. It is shared by HashMap, TreeMap, and - * Hashtable. + * A class which implements Map.Entry. It is shared by HashMap, TreeMap, + * Hashtable, and Collections. It is not specified by the JDK, but makes + * life much easier. * - * @author Jon Zeppieri + * @author Jon Zeppieri + * @author Eric Blake <ebb9@email.byu.edu> */ class BasicMapEntry implements Map.Entry { + /** + * The key. Package visible for direct manipulation. + */ Object key; + + /** + * The value. Package visible for direct manipulation. + */ Object value; + /** + * Basic constructor initializes the fields. + * @param newKey the key + * @param newValue the value + */ BasicMapEntry(Object newKey, Object newValue) { key = newKey; value = newValue; } + /** + * Compares the specified object with this entry. Returns true only if + * the object is a mapping of identical key and value. In other words, + * this must be: + * <pre> + * (o instanceof Map.Entry) && + * (getKey() == null ? ((HashMap) o).getKey() == null + * : getKey().equals(((HashMap) o).getKey())) && + * (getValue() == null ? ((HashMap) o).getValue() == null + * : getValue().equals(((HashMap) o).getValue())) + * </pre> + * + * @param o the object to compare + * @return true if it is equal + */ public final boolean equals(Object o) { - if (!(o instanceof Map.Entry)) + if (! (o instanceof Map.Entry)) return false; + // Optimize for our own entries. + if (o instanceof BasicMapEntry) + { + BasicMapEntry e = (BasicMapEntry) o; + return (AbstractCollection.equals(key, e.key) + && AbstractCollection.equals(value, e.value)); + } Map.Entry e = (Map.Entry) o; - return (key == null ? e.getKey() == null : key.equals(e.getKey()) - && value == null ? e.getValue() == null - : value.equals(e.getValue())); + return (AbstractCollection.equals(key, e.getKey()) + && AbstractCollection.equals(value, e.getValue())); } + /** + * Get the key corresponding to this entry. + * + * @return the key + */ public final Object getKey() { return key; } + /** + * Get the value corresponding to this entry. If you already called + * Iterator.remove(), the behavior undefined, but in this case it works. + * + * @return the value + */ public final Object getValue() { return value; } + /** + * Returns the hash code of the entry. This is defined as the exclusive-or + * of the hashcodes of the key and value (using 0 for null). In other + * words, this must be: + * <pre> + * (getKey() == null ? 0 : getKey().hashCode()) ^ + * (getValue() == null ? 0 : getValue().hashCode()) + * </pre> + * + * @return the hash code + */ public final int hashCode() { - int kc = (key == null ? 0 : key.hashCode()); - int vc = (value == null ? 0 : value.hashCode()); - return kc ^ vc; + return (AbstractCollection.hashCode(key) + ^ AbstractCollection.hashCode(value)); } - /** - * sets the value of this Map.Entry. Note that this is overriden by - * Hashtable.Entry, which does not permit a null value. + /** + * Replaces the value with the specified object. This writes through + * to the map, unless you have already called Iterator.remove(). It + * may be overridden to restrict a null value. + * + * @param newVal the new value to store + * @return the old value + * @throws NullPointerException if the map forbids null values */ public Object setValue(Object newVal) { @@ -83,6 +144,12 @@ class BasicMapEntry implements Map.Entry return r; } + /** + * This provides a string representation of the entry. It is of the form + * "key=value", where string concatenation is used on key and value. + * + * @return the string representation + */ public final String toString() { return key + "=" + value; diff --git a/libjava/java/util/BitSet.java b/libjava/java/util/BitSet.java index 1da187558dd..366c0053333 100644 --- a/libjava/java/util/BitSet.java +++ b/libjava/java/util/BitSet.java @@ -1,6 +1,5 @@ -// BitSet - A vector of bits. - -/* Copyright (C) 1998, 1999, 2000 Free Software Foundation +/* BitSet.java -- A vector of bits. + Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -50,22 +49,32 @@ import java.io.Serializable; * while another thread is simultaneously modifying it, the results are * undefined. * - * @specnote Historically, there has been some confusion as to whether or not - * this class should be synchronized. From an efficiency perspective, - * it is very undesirable to synchronize it because multiple locks - * and explicit lock ordering are required to safely synchronize some - * methods. The JCL 1.2 supplement book specifies that as of JDK - * 1.2, the class is no longer synchronized. - * * @author Jochen Hoenicke * @author Tom Tromey <tromey@cygnus.com> - * @date October 23, 1998. - * @status API complete to JDK 1.3. + * @author Eric Blake <ebb9@email.byu.edu> + * @status updated to 1.4 */ public class BitSet implements Cloneable, Serializable { /** - * Create a new empty bit set. + * Compatible with JDK 1.0. + */ + private static final long serialVersionUID = 7997698588986878753L; + + /** + * A common mask. + */ + private static final int LONG_MASK = 0x3f; + + /** + * The actual bits. + * @serial the i'th bit is in bits[i/64] at position i%64 (where position + * 0 is the least significant). + */ + private long[] bits; + + /** + * Create a new empty bit set. All bits are initially false. */ public BitSet() { @@ -75,18 +84,15 @@ public class BitSet implements Cloneable, Serializable /** * Create a new empty bit set, with a given size. This * constructor reserves enough space to represent the integers - * from <code>0</code> to <code>nbits-1</code>. - * @param nbits the initial size of the bit set. - * @throws NegativeArraySizeException if the specified initial - * size is negative. - * @require nbits >= 0 + * from <code>0</code> to <code>nbits-1</code>. + * + * @param nbits the initial size of the bit set + * @throws NegativeArraySizeException if nbits < 0 */ public BitSet(int nbits) { - if (nbits < 0) - throw new NegativeArraySizeException(); - int length = nbits / 64; - if (nbits % 64 != 0) + int length = nbits >>> 6; + if ((nbits & LONG_MASK) != 0) ++length; bits = new long[length]; } @@ -95,8 +101,9 @@ public class BitSet implements Cloneable, Serializable * Performs the logical AND operation on this bit set and the * given <code>set</code>. This means it builds the intersection * of the two sets. The result is stored into this bit set. - * @param set the second bit set. - * @require set != null + * + * @param set the second bit set + * @throws NullPointerException if set is null */ public void and(BitSet bs) { @@ -104,63 +111,143 @@ public class BitSet implements Cloneable, Serializable int i; for (i = 0; i < max; ++i) bits[i] &= bs.bits[i]; - for (; i < bits.length; ++i) - bits[i] = 0; + while (i < bits.length) + bits[i++] = 0; } /** * Performs the logical AND operation on this bit set and the * complement of the given <code>set</code>. This means it * selects every element in the first set, that isn't in the - * second set. The result is stored into this bit set. - * @param set the second bit set. - * @require set != null - * @since JDK1.2 + * second set. The result is stored into this bit set. + * + * @param set the second bit set + * @throws NullPointerException if set is null + * @since 1.2 */ public void andNot(BitSet bs) { - int max = Math.min(bits.length, bs.bits.length); - int i; - for (i = 0; i < max; ++i) + int i = Math.min(bits.length, bs.bits.length); + while (--i >= 0) bits[i] &= ~bs.bits[i]; } /** + * Returns the number of bits set to true. + * + * @return the number of true bits + * @since 1.4 + */ + public int cardinality() + { + int card = 0; + for (int i = bits.length - 1; i >= 0; i--) + { + long a = bits[i]; + // Take care of common cases. + if (a == 0) + continue; + if (a == -1) + { + card += 64; + continue; + } + + // Successively collapse alternating bit groups into a sum. + a = ((a >> 1) & 0x5555555555555555L) + (a & 0x5555555555555555L); + a = ((a >> 2) & 0x3333333333333333L) + (a & 0x3333333333333333L); + int b = (int) ((a >>> 32) + a); + b = ((b >> 4) & 0x0f0f0f0f) + (b & 0x0f0f0f0f); + b = ((b >> 8) & 0x00ff00ff) + (b & 0x00ff00ff); + card += ((b >> 16) & 0x0000ffff) + (b & 0x0000ffff); + } + return card; + } + + /** + * Sets all bits in the set to false. + * + * @since 1.4 + */ + public void clear() + { + Arrays.fill(bits, 0); + } + + /** * Removes the integer <code>bitIndex</code> from this set. That is * the corresponding bit is cleared. If the index is not in the set, * this method does nothing. - * @param bitIndex a non-negative integer. - * @exception ArrayIndexOutOfBoundsException if the specified bit index - * is negative. - * @require bitIndex >= 0 + * + * @param bitIndex a non-negative integer + * @throws IndexOutOfBoundsException if bitIndex < 0 */ public void clear(int pos) { - if (pos < 0) - throw new IndexOutOfBoundsException(); - int bit = pos % 64; - int offset = pos / 64; + int offset = pos >>> 6; ensure(offset); - bits[offset] &= ~(1L << bit); + // ArrayIndexOutOfBoundsException subclasses IndexOutOfBoundsException, + // so we'll just let that be our exception. + bits[offset] &= ~(1L << pos); + } + + /** + * Sets the bits between from (inclusive) and to (exclusive) to false. + * + * @param from the start range (inclusive) + * @param to the end range (exclusive) + * @throws IndexOutOfBoundsException if from < 0 || from > to + * @since 1.4 + */ + public void clear(int from, int to) + { + if (from < 0 || from > to) + throw new IndexOutOfBoundsException(); + if (from == to) + return; + int lo_offset = from >>> 6; + int hi_offset = to >>> 6; + ensure(hi_offset); + if (lo_offset == hi_offset) + { + bits[hi_offset] &= ((1L << from) - 1) | (-1L << to); + return; + } + + bits[lo_offset] &= (1L << from) - 1; + bits[hi_offset] &= -1L << to; + for (int i = lo_offset + 1; i < hi_offset; i++) + bits[i] = 0; } /** * Create a clone of this bit set, that is an instance of the same * class and contains the same elements. But it doesn't change when * this bit set changes. + * * @return the clone of this object. */ public Object clone() { - BitSet bs = new BitSet(bits.length * 64); - System.arraycopy(bits, 0, bs.bits, 0, bits.length); - return bs; + try + { + BitSet bs = (BitSet) super.clone(); + bs.bits = (long[]) bits.clone(); + return bs; + } + catch (CloneNotSupportedException e) + { + // Impossible to get here. + return null; + } } /** * Returns true if the <code>obj</code> is a bit set that contains * exactly the same elements as this bit set, otherwise false. - * @return true if obj equals this bit set. + * + * @param obj the object to compare to + * @return true if obj equals this bit set */ public boolean equals(Object obj) { @@ -171,42 +258,124 @@ public class BitSet implements Cloneable, Serializable int i; for (i = 0; i < max; ++i) if (bits[i] != bs.bits[i]) - return false; + return false; // If one is larger, check to make sure all extra bits are 0. for (int j = i; j < bits.length; ++j) if (bits[j] != 0) - return false; + return false; for (int j = i; j < bs.bits.length; ++j) if (bs.bits[j] != 0) - return false; + return false; return true; } /** - * Returns true if the integer <code>bitIndex</code> is in this bit - * set, otherwise false. - * @param bitIndex a non-negative integer - * @return the value of the bit at the specified index. - * @exception ArrayIndexOutOfBoundsException if the specified bit index - * is negative. - * @require bitIndex >= 0 + * Sets the bit at the index to the opposite value. + * + * @param index the index of the bit + * @throws IndexOutOfBoundsException if index is negative + * @since 1.4 */ - public boolean get(int pos) + public void flip(int index) { - if (pos < 0) + int offset = index >>> 6; + ensure(offset); + // ArrayIndexOutOfBoundsException subclasses IndexOutOfBoundsException, + // so we'll just let that be our exception. + bits[offset] ^= 1L << index; + } + + /** + * Sets a range of bits to the opposite value. + * + * @param from the low index (inclusive) + * @param to the high index (exclusive) + * @throws IndexOutOfBoundsException if from > to || from < 0 + * @since 1.4 + */ + public void flip(int from, int to) + { + if (from < 0 || from > to) throw new IndexOutOfBoundsException(); + if (from == to) + return; + int lo_offset = from >>> 6; + int hi_offset = to >>> 6; + ensure(hi_offset); + if (lo_offset == hi_offset) + { + bits[hi_offset] ^= (-1L << from) & ((1L << to) - 1); + return; + } - int bit = pos % 64; - int offset = pos / 64; + bits[lo_offset] ^= -1L << from; + bits[hi_offset] ^= (1L << to) - 1; + for (int i = lo_offset + 1; i < hi_offset; i++) + bits[i] ^= -1; + } + /** + * Returns true if the integer <code>bitIndex</code> is in this bit + * set, otherwise false. + * + * @param pos a non-negative integer + * @return the value of the bit at the specified index + * @throws IndexOutOfBoundsException if the index is negative + */ + public boolean get(int pos) + { + int offset = pos >>> 6; if (offset >= bits.length) return false; + // ArrayIndexOutOfBoundsException subclasses IndexOutOfBoundsException, + // so we'll just let that be our exception. + return (bits[offset] & (1L << pos)) != 0; + } + + /** + * Returns a new <code>BitSet</code> composed of a range of bits from + * this one. + * + * @param from the low index (inclusive) + * @param to the high index (exclusive) + * @throws IndexOutOfBoundsException if from > to || from < 0 + * @since 1.4 + */ + public BitSet get(int from, int to) + { + if (from < 0 || from > to) + throw new IndexOutOfBoundsException(); + BitSet bs = new BitSet(to - from); + int lo_offset = from >>> 6; + if (lo_offset >= bits.length) + return bs; + + int lo_bit = from & LONG_MASK; + int hi_offset = to >>> 6; + if (lo_bit == 0) + { + int len = Math.min(hi_offset - lo_offset + 1, bits.length - lo_offset); + System.arraycopy(bits, lo_offset, bs.bits, 0, len); + if (hi_offset < bits.length) + bs.bits[hi_offset - lo_offset] &= (1L << to) - 1; + return bs; + } - return (bits[offset] & (1L << bit)) == 0 ? false : true; + int len = Math.min(hi_offset, bits.length - 1); + int reverse = ~lo_bit; + int i; + for (i = 0; lo_offset < len; lo_offset++, i++) + bs.bits[i] = ((bits[lo_offset] >>> lo_bit) + | (bits[lo_offset + 1] << reverse)); + if ((to & LONG_MASK) > lo_bit) + bs.bits[i++] = bits[lo_offset] >>> lo_bit; + if (hi_offset < bits.length) + bs.bits[i - 1] &= (1L << (to - from)) - 1; + return bs; } /** - * Returns a hash code value for this bit set. The hash code of + * Returns a hash code value for this bit set. The hash code of * two bit sets containing the same integers is identical. The algorithm * used to compute it is as follows: * @@ -233,21 +402,55 @@ public class BitSet implements Cloneable, Serializable * </pre> * * Note that the hash code values changes, if the set is changed. + * * @return the hash code value for this bit set. */ public int hashCode() { long h = 1234; - for (int i = bits.length - 1; i >= 0; --i) - h ^= bits[i] * (i + 1); + for (int i = bits.length; i > 0; ) + h ^= i * bits[--i]; return (int) ((h >> 32) ^ h); } /** + * Returns true if the specified BitSet and this one share at least one + * common true bit. + * + * @param set the set to check for intersection + * @return true if the sets intersect + * @throws NullPointerException if set is null + * @since 1.4 + */ + public boolean intersects(BitSet set) + { + int i = Math.min(bits.length, set.bits.length); + while (--i >= 0) + if ((bits[i] & set.bits[i]) != 0) + return true; + return false; + } + + /** + * Returns true if this set contains no true bits. + * + * @return true if all bits are false + * @since 1.4 + */ + public boolean isEmpty() + { + for (int i = bits.length - 1; i >= 0; i--) + if (bits[i] != 0) + return false; + return true; + } + + /** * Returns the logical number of bits actually used by this bit * set. It returns the index of the highest set bit plus one. * Note that this method doesn't return the number of set bits. - * @return the index of the highest set bit plus one. + * + * @return the index of the highest set bit plus one. */ public int length() { @@ -266,54 +469,186 @@ public class BitSet implements Cloneable, Serializable // b >= 0 checks if the highest bit is zero. while (b >= 0) { - --len; - b <<= 1; + --len; + b <<= 1; } return len; } /** + * Returns the index of the next false bit, from the specified bit + * (inclusive). + * + * @param from the start location + * @return the first false bit + * @throws IndexOutOfBoundsException if from is negative + * @since 1.4 + */ + public int nextClearBit(int from) + { + int offset = from >>> 6; + long mask = 1L << from; + while (offset < bits.length) + { + // ArrayIndexOutOfBoundsException subclasses IndexOutOfBoundsException, + // so we'll just let that be our exception. + long h = bits[offset]; + do + { + if ((h & mask) == 0) + return from; + mask <<= 1; + from++; + } + while (mask != 0); + mask = 1; + offset++; + } + return from; + } + + /** + * Returns the index of the next true bit, from the specified bit + * (inclusive). If there is none, -1 is returned. You can iterate over + * all true bits with this loop:<br> + * <pre> + * for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) + * { // operate on i here } + * </pre> + * + * @param from the start location + * @return the first true bit, or -1 + * @throws IndexOutOfBoundsException if from is negative + * @since 1.4 + */ + public int nextSetBit(int from) + { + int offset = from >>> 6; + long mask = 1L << from; + while (offset < bits.length) + { + // ArrayIndexOutOfBoundsException subclasses IndexOutOfBoundsException, + // so we'll just let that be our exception. + long h = bits[offset]; + do + { + if ((h & mask) != 0) + return from; + mask <<= 1; + from++; + } + while (mask != 0); + mask = 1; + offset++; + } + return -1; + } + + /** * Performs the logical OR operation on this bit set and the * given <code>set</code>. This means it builds the union * of the two sets. The result is stored into this bit set, which * grows as necessary. - * @param set the second bit set. - * @exception OutOfMemoryError if the current set can't grow. - * @require set != null + * + * @param bs the second bit set + * @throws NullPointerException if bs is null */ public void or(BitSet bs) { ensure(bs.bits.length - 1); - int i; - for (i = 0; i < bs.bits.length; ++i) + for (int i = bs.bits.length - 1; i >= 0; i--) bits[i] |= bs.bits[i]; } /** - * Add the integer <code>bitIndex</code> to this set. That is + * Add the integer <code>bitIndex</code> to this set. That is * the corresponding bit is set to true. If the index was already in * the set, this method does nothing. The size of this structure * is automatically increased as necessary. - * @param bitIndex a non-negative integer. - * @exception ArrayIndexOutOfBoundsException if the specified bit index - * is negative. - * @require bitIndex >= 0 + * + * @param pos a non-negative integer. + * @throws IndexOutOfBoundsException if pos is negative */ public void set(int pos) { - if (pos < 0) - throw new IndexOutOfBoundsException(); - int bit = pos % 64; - int offset = pos / 64; + int offset = pos >>> 6; ensure(offset); - bits[offset] |= 1L << bit; + // ArrayIndexOutOfBoundsException subclasses IndexOutOfBoundsException, + // so we'll just let that be our exception. + bits[offset] |= 1L << pos; + } + + /** + * Sets the bit at the given index to the specified value. The size of + * this structure is automatically increased as necessary. + * + * @param index the position to set + * @param value the value to set it to + * @throws IndexOutOfBoundsException if index is negative + * @since 1.4 + */ + public void set(int index, boolean value) + { + if (value) + set(index); + else + clear(index); + } + + /** + * Sets the bits between from (inclusive) and to (exclusive) to true. + * + * @param from the start range (inclusive) + * @param to the end range (exclusive) + * @throws IndexOutOfBoundsException if from < 0 || from > to + * @since 1.4 + */ + public void set(int from, int to) + { + if (from < 0 || from > to) + throw new IndexOutOfBoundsException(); + if (from == to) + return; + int lo_offset = from >>> 6; + int hi_offset = to >>> 6; + ensure(hi_offset); + if (lo_offset == hi_offset) + { + bits[hi_offset] |= (-1L << from) & ((1L << to) - 1); + return; + } + + bits[lo_offset] |= -1L << from; + bits[hi_offset] |= (1L << to) - 1; + for (int i = lo_offset + 1; i < hi_offset; i++) + bits[i] = -1; + } + + /** + * Sets the bits between from (inclusive) and to (exclusive) to the + * specified value. + * + * @param from the start range (inclusive) + * @param to the end range (exclusive) + * @param value the value to set it to + * @throws IndexOutOfBoundsException if from < 0 || from > to + * @since 1.4 + */ + public void set(int from, int to, boolean value) + { + if (value) + set(from, to); + else + clear(from, to); } /** * Returns the number of bits actually used by this bit set. Note - * that this method doesn't return the number of set bits. - * @returns the number of bits currently used. + * that this method doesn't return the number of set bits, and that + * future requests for larger bits will make this automatically grow. + * + * @return the number of bits currently used. */ public int size() { @@ -324,32 +659,32 @@ public class BitSet implements Cloneable, Serializable * Returns the string representation of this bit set. This * consists of a comma separated list of the integers in this set * surrounded by curly braces. There is a space after each comma. + * A sample string is thus "{1, 3, 53}". * @return the string representation. */ public String toString() { - String r = "{"; + StringBuffer r = new StringBuffer("{"); boolean first = true; for (int i = 0; i < bits.length; ++i) { - long bit = 1; - long word = bits[i]; - if (word == 0) - continue; - for (int j = 0; j < 64; ++j) - { - if ((word & bit) != 0) - { - if (!first) - r += ", "; - r += Integer.toString(64 * i + j); - first = false; - } - bit <<= 1; - } + long bit = 1; + long word = bits[i]; + if (word == 0) + continue; + for (int j = 0; j < 64; ++j) + { + if ((word & bit) != 0) + { + if (! first) + r.append(", "); + r.append(64 * i + j); + first = false; + } + bit <<= 1; + } } - - return r += "}"; + return r.append("}").toString(); } /** @@ -357,32 +692,30 @@ public class BitSet implements Cloneable, Serializable * given <code>set</code>. This means it builds the symmetric * remainder of the two sets (the elements that are in one set, * but not in the other). The result is stored into this bit set, - * which grows as necessary. - * @param set the second bit set. - * @exception OutOfMemoryError if the current set can't grow. - * @require set != null + * which grows as necessary. + * + * @param bs the second bit set + * @throws NullPointerException if bs is null */ public void xor(BitSet bs) { ensure(bs.bits.length - 1); - int i; - for (i = 0; i < bs.bits.length; ++i) + for (int i = bs.bits.length - 1; i >= 0; i--) bits[i] ^= bs.bits[i]; } - // Make sure the vector is big enough. + /** + * Make sure the vector is big enough. + * + * @param lastElt the size needed for the bits array + */ private final void ensure(int lastElt) { - if (lastElt + 1 > bits.length) + if (lastElt >= bits.length) { - long[] nd = new long[lastElt + 1]; - System.arraycopy(bits, 0, nd, 0, bits.length); - bits = nd; + long[] nd = new long[lastElt + 1]; + System.arraycopy(bits, 0, nd, 0, bits.length); + bits = nd; } } - - // The actual bits. - long[] bits; - - private static final long serialVersionUID = 7997698588986878753L; } diff --git a/libjava/java/util/Collections.java b/libjava/java/util/Collections.java index 8e77a802476..2f545028998 100644 --- a/libjava/java/util/Collections.java +++ b/libjava/java/util/Collections.java @@ -25,10 +25,6 @@ This exception does not however invalidate any other reasons why the executable file might be covered by the GNU General Public License. */ -// TO DO: -// ~ Serialization is very much broken. Blame Sun for not specifying it. -// ~ The synchronized* and unmodifiable* methods don't have doc-comments. - package java.util; import java.io.Serializable; @@ -40,10 +36,55 @@ import java.io.Serializable; * are unaware of collections, a method to return a list which consists of * multiple copies of one element, and methods which "wrap" collections to give * them extra properties, such as thread-safety and unmodifiability. + * <p> + * + * All methods which take a collection throw a {@link NullPointerException} if + * that collection is null. Algorithms which can change a collection may, but + * are not required, to throw the {@link UnsupportedOperationException} that + * the underlying collection would throw during an attempt at modification. + * For example, + * <code>Collections.singleton("").addAll(Collections.EMPTY_SET)<code> + * does not throw a exception, even though addAll is an unsupported operation + * on a singleton; the reason for this is that addAll did not attempt to + * modify the set. + * + * @author Original author unknown + * @author Bryce McKinlay + * @author Eric Blake <ebb9@email.byu.edu> + * @see Collection + * @see Set + * @see List + * @see Map + * @see Arrays + * @since 1.2 + * @status updated to 1.4 */ public class Collections { /** + * Constant used to decide cutoff for when a non-RandomAccess list should + * be treated as sequential-access. Basically, quadratic behavior is + * acceptible for small lists when the overhead is so small in the first + * place. I arbitrarily set it to 16, so it may need some tuning. + */ + private static final int LARGE_LIST_SIZE = 16; + + /** + * Determines if a list should be treated as a sequential-access one. + * Rather than the old method of JDK 1.3 of assuming only instanceof + * AbstractSequentialList should be sequential, this uses the new method + * of JDK 1.4 of assuming anything that does NOT implement RandomAccess + * and exceeds a large (unspecified) size should be sequential. + * + * @param l the list to check + * @return true if it should be treated as sequential-access + */ + private static boolean isSequential(List l) + { + return ! (l instanceof RandomAccess) && l.size() > LARGE_LIST_SIZE; + } + + /** * This class is non-instantiable. */ private Collections() @@ -51,200 +92,288 @@ public class Collections } /** - * An immutable, empty Set. - * Note: This implementation isn't Serializable, although it should be by the - * spec. + * An immutable, serializable, empty Set. + * @see Serializable */ - public static final Set EMPTY_SET = new AbstractSet() + public static final Set EMPTY_SET = new EmptySet(); + + private static final Iterator EMPTY_ITERATOR = new Iterator() + { + public boolean hasNext() + { + return false; + } + + public Object next() + { + throw new NoSuchElementException(); + } + + public void remove() + { + throw new UnsupportedOperationException(); + } + }; + + /** + * The implementation of {@link #EMPTY_SET}. This class name is required + * for compatibility with Sun's JDK serializability. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private static final class EmptySet extends AbstractSet + implements Serializable { + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = 1582296315990362920L; + + /** + * A private constructor adds overhead. + */ + EmptySet() + { + } + + /** + * The size: always 0! + */ public int size() { return 0; } - // This is really cheating! I think it's perfectly valid, though - the - // more conventional code is here, commented out, in case anyone disagrees. + /** + * Returns an iterator that does not iterate. + */ public Iterator iterator() { - return EMPTY_LIST.iterator(); + return EMPTY_ITERATOR; } + } // class EmptySet - // public Iterator iterator() { - // return new Iterator() { - // - // public boolean hasNext() { - // return false; - // } - // - // public Object next() { - // throw new NoSuchElementException(); - // } - // - // public void remove() { - // throw new UnsupportedOperationException(); - // } - // }; - // } - - }; + /** + * An immutable, serializable, empty List, which implements RandomAccess. + * @see Serializable + * @see RandomAccess + */ + public static final List EMPTY_LIST = new EmptyList(); /** - * An immutable, empty List. - * Note: This implementation isn't serializable, although it should be by the - * spec. + * The implementation of {@link #EMPTY_LIST}. This class name is required + * for compatibility with Sun's JDK serializability. + * + * @author Eric Blake <ebb9@email.byu.edu> */ - public static final List EMPTY_LIST = new AbstractList() + private static final class EmptyList extends AbstractList + implements Serializable, RandomAccess { + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = 8842843931221139166L; + + /** + * A private constructor adds overhead. + */ + EmptyList() + { + } + + /** + * The size is always 0. + */ public int size() { return 0; } + /** + * No matter the index, it is out of bounds. + */ public Object get(int index) { throw new IndexOutOfBoundsException(); } - }; + + /** + * Returns an iterator that does not iterate. Optional, but avoids + * allocation of an iterator in AbstractList. + */ + public Iterator iterator() + { + return EMPTY_ITERATOR; + } + } // class EmptyList + + /** + * An immutable, serializable, empty Map. + * @see Serializable + */ + public static final Map EMPTY_MAP = new EmptyMap(); /** - * An immutable, empty Map. - * Note: This implementation isn't serializable, although it should be by the - * spec. + * The implementation of {@link #EMPTY_MAP}. This class name is required + * for compatibility with Sun's JDK serializability. + * + * @author Eric Blake <ebb9@email.byu.edu> */ - public static final Map EMPTY_MAP = new AbstractMap() + private static final class EmptyMap extends AbstractMap + implements Serializable { + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = 6428348081105594320L; + + /** + * A private constructor adds overhead. + */ + EmptyMap() + { + } + + /** + * There are no entries. + */ public Set entrySet() { return EMPTY_SET; } - }; + + /** + * Size is always 0. + */ + public int size() + { + return 0; + } + + /** + * No entries. Technically, EMPTY_SET, while more specific than a general + * Collection, will work. Besides, that's what the JDK uses! + */ + public Collection values() + { + return EMPTY_SET; + } + } // class EmptyMap /** * Compare two objects with or without a Comparator. If c is null, uses the * natural ordering. Slightly slower than doing it inline if the JVM isn't * clever, but worth it for removing a duplicate of the search code. - * Note: This same code is used in Arrays (for sort as well as search) + * Note: This code is also used in Arrays (for sort as well as search). */ - private static int compare(Object o1, Object o2, Comparator c) + static final int compare(Object o1, Object o2, Comparator c) { - if (c == null) - { - return ((Comparable) o1).compareTo(o2); - } - else - { - return c.compare(o1, o2); - } - } - - /** - * The hard work for the search routines. If the Comparator given is null, - * uses the natural ordering of the elements. - */ - private static int search(List l, Object key, final Comparator c) - { - int pos = 0; - - // We use a linear search using an iterator if we can guess that the list - // is sequential-access. - if (l instanceof AbstractSequentialList) - { - ListIterator itr = l.listIterator(); - for (int i = l.size() - 1; i >= 0; --i) - { - final int d = compare(key, itr.next(), c); - if (d == 0) - { - return pos; - } - else if (d < 0) - { - return -pos - 1; - } - pos++; - } - - // We assume the list is random-access, and use a binary search - } - else - { - int low = 0; - int hi = l.size() - 1; - while (low <= hi) - { - pos = (low + hi) >> 1; - final int d = compare(key, l.get(pos), c); - if (d == 0) - { - return pos; - } - else if (d < 0) - { - hi = pos - 1; - } - else - { - low = ++pos; // This gets the insertion point right on the last loop - } - } - } - - // If we failed to find it, we do the same whichever search we did. - return -pos - 1; + return c == null ? ((Comparable) o1).compareTo(o2) : c.compare(o1, o2); } /** * Perform a binary search of a List for a key, using the natural ordering of * the elements. The list must be sorted (as by the sort() method) - if it is - * not, the behaviour of this method is undefined, and may be an infinite + * not, the behavior of this method is undefined, and may be an infinite * loop. Further, the key must be comparable with every item in the list. If - * the list contains the key more than once, any one of them may be found. To - * avoid pathological behaviour on sequential-access lists, a linear search - * is used if (l instanceof AbstractSequentialList). Note: although the + * the list contains the key more than once, any one of them may be found. + * <p> + * + * This algorithm behaves in log(n) time for {@link RandomAccess} lists, + * and uses a linear search with O(n) link traversals and log(n) comparisons + * with {@link AbstractSequentialList} lists. Note: although the * specification allows for an infinite loop if the list is unsorted, it will * not happen in this (Classpath) implementation. * * @param l the list to search (must be sorted) * @param key the value to search for - * @returns the index at which the key was found, or -n-1 if it was not - * found, where n is the index of the first value higher than key or - * a.length if there is no such value. - * @exception ClassCastException if key could not be compared with one of the - * elements of l - * @exception NullPointerException if a null element has compareTo called + * @return the index at which the key was found, or -n-1 if it was not + * found, where n is the index of the first value higher than key or + * a.length if there is no such value + * @throws ClassCastException if key could not be compared with one of the + * elements of l + * @throws NullPointerException if a null element has compareTo called + * @see #sort(List) */ public static int binarySearch(List l, Object key) { - return search(l, key, null); + return binarySearch(l, key, null); } /** * Perform a binary search of a List for a key, using a supplied Comparator. * The list must be sorted (as by the sort() method with the same Comparator) - * - if it is not, the behaviour of this method is undefined, and may be an + * - if it is not, the behavior of this method is undefined, and may be an * infinite loop. Further, the key must be comparable with every item in the * list. If the list contains the key more than once, any one of them may be - * found. To avoid pathological behaviour on sequential-access lists, a - * linear search is used if (l instanceof AbstractSequentialList). Note: - * although the specification allows for an infinite loop if the list is - * unsorted, it will not happen in this (Classpath) implementation. + * found. If the comparator is null, the elements' natural ordering is used. + * <p> + * + * This algorithm behaves in log(n) time for {@link RandomAccess} lists, + * and uses a linear search with O(n) link traversals and log(n) comparisons + * with {@link AbstractSequentialList} lists. Note: although the + * specification allows for an infinite loop if the list is unsorted, it will + * not happen in this (Classpath) implementation. * * @param l the list to search (must be sorted) * @param key the value to search for * @param c the comparator by which the list is sorted - * @returns the index at which the key was found, or -n-1 if it was not - * found, where n is the index of the first value higher than key or - * a.length if there is no such value. - * @exception ClassCastException if key could not be compared with one of the - * elements of l + * @return the index at which the key was found, or -n-1 if it was not + * found, where n is the index of the first value higher than key or + * a.length if there is no such value + * @throws ClassCastException if key could not be compared with one of the + * elements of l + * @throws NullPointerException if a null element is compared with natural + * ordering (only possible when c is null) + * @see #sort(List, Comparator) */ public static int binarySearch(List l, Object key, Comparator c) { - if (c == null) + int pos = 0; + int low = 0; + int hi = l.size() - 1; + + // We use a linear search with log(n) comparisons using an iterator + // if the list is sequential-access. + if (isSequential(l)) { - throw new NullPointerException(); + ListIterator itr = l.listIterator(); + int i = 0; + while (low <= hi) + { + pos = (low + hi) >> 1; + if (i < pos) + for ( ; i != pos; i++, itr.next()); + else + for ( ; i != pos; i--, itr.previous()); + final int d = compare(key, itr.next(), c); + if (d == 0) + return pos; + else if (d < 0) + hi = pos - 1; + else + // This gets the insertion point right on the last loop + low = ++pos; + } + } + else + { + while (low <= hi) + { + pos = (low + hi) >> 1; + final int d = compare(key, l.get(pos), c); + if (d == 0) + return pos; + else if (d < 0) + hi = pos - 1; + else + // This gets the insertion point right on the last loop + low = ++pos; + } } - return search(l, key, c); + + // If we failed to find it, we do the same whichever search we did. + return -pos - 1; } /** @@ -252,30 +381,26 @@ public class Collections * source list, the remaining elements are unaffected. This method runs in * linear time. * - * @param dest the destination list. - * @param source the source list. - * @exception IndexOutOfBoundsException if the destination list is shorter - * than the source list (the elements that can be copied will be, prior to - * the exception being thrown). - * @exception UnsupportedOperationException if dest.listIterator() does not - * support the set operation. + * @param dest the destination list + * @param source the source list + * @throws IndexOutOfBoundsException if the destination list is shorter + * than the source list (the destination will be unmodified) + * @throws UnsupportedOperationException if dest.listIterator() does not + * support the set operation */ public static void copy(List dest, List source) { + int pos = source.size(); + if (dest.size() < pos) + throw new IndexOutOfBoundsException("Source does not fit in dest"); + Iterator i1 = source.iterator(); ListIterator i2 = dest.listIterator(); - try + while (--pos >= 0) { - for (int i = source.size() - 1; i >= 0; --i) - { - i2.next(); - i2.set(i1.next()); - } - } - catch (NoSuchElementException x) - { - throw new IndexOutOfBoundsException("Source doesn't fit in dest."); + i2.next(); + i2.set(i1.next()); } } @@ -284,7 +409,7 @@ public class Collections * with legacy APIs that require an Enumeration as input. * * @param c the Collection to iterate over - * @returns an Enumeration backed by an Iterator over c + * @return an Enumeration backed by an Iterator over c */ public static Enumeration enumeration(Collection c) { @@ -308,8 +433,8 @@ public class Collections * * @param l the list to fill. * @param val the object to vill the list with. - * @exception UnsupportedOperationException if l.listIterator() does not - * support the set operation. + * @throws UnsupportedOperationException if l.listIterator() does not + * support the set operation. */ public static void fill(List l, Object val) { @@ -322,30 +447,81 @@ public class Collections } /** + * Returns the starting index where the specified sublist first occurs + * in a larger list, or -1 if there is no matching position. If + * <code>target.size() > source.size()</code>, this returns -1, + * otherwise this implementation uses brute force, checking for + * <code>source.sublist(i, i + target.size()).equals(target)</code> + * for all possible i. + * + * @param source the list to search + * @param target the sublist to search for + * @return the index where found, or -1 + * @since 1.4 + */ + public static int indexOfSubList(List source, List target) + { + int ssize = source.size(); + for (int i = 0, j = target.size(); j <= ssize; i++, j++) + if (source.subList(i, j).equals(target)) + return i; + return -1; + } + + /** + * Returns the starting index where the specified sublist last occurs + * in a larger list, or -1 if there is no matching position. If + * <code>target.size() > source.size()</code>, this returns -1, + * otherwise this implementation uses brute force, checking for + * <code>source.sublist(i, i + target.size()).equals(target)</code> + * for all possible i. + * + * @param source the list to search + * @param target the sublist to search for + * @return the index where found, or -1 + * @since 1.4 + */ + public static int lastIndexOfSubList(List source, List target) + { + int ssize = source.size(); + for (int i = ssize - target.size(), j = ssize; i >= 0; i--, j--) + if (source.subList(i, j).equals(target)) + return i; + return -1; + } + + /** + * Returns an array list holding the elements visited by a given + * Enumeration. This method exists for interoperability between legacy + * APIs and the new Collection API. + * + * @param e the enumeration to put in a list + * @return a list containing the enumeration elements + * @see ArrayList + * @since 1.4 + */ + public static List list(Enumeration e) + { + List l = new ArrayList(); + while (e.hasMoreElements()) + l.add(e.nextElement()); + return l; + } + + /** * Find the maximum element in a Collection, according to the natural * ordering of the elements. This implementation iterates over the * Collection, so it works in linear time. * * @param c the Collection to find the maximum element of - * @returns the maximum element of c + * @return the maximum element of c * @exception NoSuchElementException if c is empty * @exception ClassCastException if elements in c are not mutually comparable * @exception NullPointerException if null.compareTo is called */ public static Object max(Collection c) { - Iterator itr = c.iterator(); - Comparable max = (Comparable) itr.next(); // throws NoSuchElementException - int csize = c.size(); - for (int i = 1; i < csize; i++) - { - Object o = itr.next(); - if (max.compareTo(o) < 0) - { - max = (Comparable) o; - } - } - return max; + return max(c, null); } /** @@ -354,20 +530,23 @@ public class Collections * works in linear time. * * @param c the Collection to find the maximum element of - * @param order the Comparator to order the elements by - * @returns the maximum element of c - * @exception NoSuchElementException if c is empty - * @exception ClassCastException if elements in c are not mutually comparable + * @param order the Comparator to order the elements by, or null for natural + * ordering + * @return the maximum element of c + * @throws NoSuchElementException if c is empty + * @throws ClassCastException if elements in c are not mutually comparable + * @throws NullPointerException if null is compared by natural ordering + * (only possible when order is null) */ public static Object max(Collection c, Comparator order) { Iterator itr = c.iterator(); - Object max = itr.next(); // throws NoSuchElementException + Object max = itr.next(); // throws NoSuchElementException int csize = c.size(); for (int i = 1; i < csize; i++) { Object o = itr.next(); - if (order.compare(max, o) < 0) + if (compare(max, o, order) < 0) max = o; } return max; @@ -379,23 +558,14 @@ public class Collections * Collection, so it works in linear time. * * @param c the Collection to find the minimum element of - * @returns the minimum element of c - * @exception NoSuchElementException if c is empty - * @exception ClassCastException if elements in c are not mutually comparable - * @exception NullPointerException if null.compareTo is called + * @return the minimum element of c + * @throws NoSuchElementException if c is empty + * @throws ClassCastException if elements in c are not mutually comparable + * @throws NullPointerException if null.compareTo is called */ public static Object min(Collection c) { - Iterator itr = c.iterator(); - Comparable min = (Comparable) itr.next(); // throws NoSuchElementException - int csize = c.size(); - for (int i = 1; i < csize; i++) - { - Object o = itr.next(); - if (min.compareTo(o) > 0) - min = (Comparable) o; - } - return min; + return min(c, null); } /** @@ -404,10 +574,13 @@ public class Collections * works in linear time. * * @param c the Collection to find the minimum element of - * @param order the Comparator to order the elements by - * @returns the minimum element of c - * @exception NoSuchElementException if c is empty - * @exception ClassCastException if elements in c are not mutually comparable + * @param order the Comparator to order the elements by, or null for natural + * ordering + * @return the minimum element of c + * @throws NoSuchElementException if c is empty + * @throws ClassCastException if elements in c are not mutually comparable + * @throws NullPointerException if null is compared by natural ordering + * (only possible when order is null) */ public static Object min(Collection c, Comparator order) { @@ -417,7 +590,7 @@ public class Collections for (int i = 1; i < csize; i++) { Object o = itr.next(); - if (order.compare(min, o) > 0) + if (compare(min, o, order) > 0) min = o; } return min; @@ -426,54 +599,182 @@ public class Collections /** * Creates an immutable list consisting of the same object repeated n times. * The returned object is tiny, consisting of only a single reference to the - * object and a count of the number of elements. It is Serializable. + * object and a count of the number of elements. It is Serializable, and + * implements RandomAccess. You can use it in tandem with List.addAll for + * fast list construction. * * @param n the number of times to repeat the object * @param o the object to repeat - * @returns a List consisting of n copies of o - * @throws IllegalArgumentException if n < 0 + * @return a List consisting of n copies of o + * @throws IllegalArgumentException if n < 0 + * @see List#addAll(Collection) + * @see Serializable + * @see RandomAccess */ - // It's not Serializable, because the serialized form is unspecced. - // Also I'm only assuming that it should be because I don't think it's - // stated - I just would be amazed if it isn't... public static List nCopies(final int n, final Object o) { - // Check for insane arguments - if (n < 0) - { + return new CopiesList(n, o); + } + + /** + * The implementation of {@link #nCopies(int, Object)}. This class name + * is required for compatibility with Sun's JDK serializability. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private static final class CopiesList extends AbstractList + implements Serializable, RandomAccess + { + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = 2739099268398711800L; + + /** + * The count of elements in this list. + * @serial the list size + */ + private final int n; + + /** + * The repeated list element. + * @serial the list contents + */ + private final Object element; + + /** + * Constructs the list. + * + * @param n the count + * @param o the object + * @throws IllegalArgumentException if n < 0 + */ + CopiesList(int n, Object o) + { + if (n < 0) throw new IllegalArgumentException(); - } + this.n = n; + element = o; + } - // Create a minimal implementation of List - return new AbstractList() + /** + * The size is fixed. + */ + public int size() { - public int size() - { - return n; - } + return n; + } - public Object get(int index) - { - if (index < 0 || index >= n) - { - throw new IndexOutOfBoundsException(); - } - return o; - } - }; + /** + * The same element is returned. + */ + public Object get(int index) + { + if (index < 0 || index >= n) + throw new IndexOutOfBoundsException(); + return element; + } + + // The remaining methods are optional, but provide a performance + // advantage by not allocating unnecessary iterators in AbstractList. + /** + * This list only contains one element. + */ + public boolean contains(Object o) + { + return n > 0 && equals(o, element); + } + + /** + * The index is either 0 or -1. + */ + public int indexOf(Object o) + { + return (n > 0 && equals(o, element)) ? 0 : -1; + } + + /** + * The index is either n-1 or -1. + */ + public int lastIndexOf(Object o) + { + return equals(o, element) ? n - 1 : -1; + } + + /** + * A subList is just another CopiesList. + */ + public List subList(int from, int to) + { + if (from < 0 || to > n) + throw new IndexOutOfBoundsException(); + return new CopiesList(to - from, element); + } + + /** + * The array is easy. + */ + public Object[] toArray() + { + Object[] a = new Object[n]; + Arrays.fill(a, element); + return a; + } + + /** + * The string is easy to generate. + */ + public String toString() + { + StringBuffer r = new StringBuffer("{"); + for (int i = n - 1; --i > 0; ) + r.append(element).append(", "); + r.append(element).append("}"); + return r.toString(); + } + } // class CopiesList + + /** + * Replace all instances of one object with another in the specified list. + * The list does not change size. An element e is replaced if + * <code>oldval == null ? e == null : oldval.equals(e)</code>. + * + * @param list the list to iterate over + * @param oldval the element to replace + * @param newval the new value for the element + * @return true if a replacement occurred + * @throws UnsupportedOperationException if the list iterator does not allow + * for the set operation + * @throws ClassCastException newval is of a type which cannot be added + * to the list + * @throws IllegalArgumentException some other aspect of newval stops + * it being added to the list + * @since 1.4 + */ + public static boolean replaceAll(List list, Object oldval, Object newval) + { + ListIterator itr = list.listIterator(); + boolean replace_occured = false; + for (int i = list.size(); --i >= 0; ) + if (AbstractCollection.equals(oldval, itr.next())) + { + itr.set(newval); + replace_occured = true; + } + return replace_occured; } /** * Reverse a given list. This method works in linear time. * - * @param l the list to reverse. - * @exception UnsupportedOperationException if l.listIterator() does not - * support the set operation. + * @param l the list to reverse + * @throws UnsupportedOperationException if l.listIterator() does not + * support the set operation */ public static void reverse(List l) { ListIterator i1 = l.listIterator(); - int pos1 = 0; + int pos1 = 1; int pos2 = l.size(); ListIterator i2 = l.listIterator(pos2); while (pos1 < pos2) @@ -486,42 +787,152 @@ public class Collections } } - static class ReverseComparator implements Comparator, Serializable + /** + * Get a comparator that implements the reverse of natural ordering. In + * other words, this sorts Comparable objects opposite of how their + * compareTo method would sort. This makes it easy to sort into reverse + * order, by simply passing Collections.reverseOrder() to the sort method. + * The return value of this method is Serializable. + * + * @return a comparator that imposes reverse natural ordering + * @see Comparable + * @see Serializable + */ + public static Comparator reverseOrder() { + return rcInstance; + } + + /** + * The object for {@link #reverseOrder()}. + */ + static private final ReverseComparator rcInstance = new ReverseComparator(); + + /** + * The implementation of {@link #reverseOrder()}. This class name + * is required for compatibility with Sun's JDK serializability. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private static final class ReverseComparator + implements Comparator, Serializable + { + /** + * Compatible with JDK 1.4. + */ + static private final long serialVersionUID = 7207038068494060240L; + + /** + * A private constructor adds overhead. + */ + ReverseComparator() + { + } + + /** + * Compare two objects in reverse natural order. + * + * @param a the first object + * @param b the second object + * @return <, ==, or > 0 according to b.compareTo(a) + */ public int compare(Object a, Object b) { - return -((Comparable) a).compareTo(b); + return ((Comparable) b).compareTo(a); } } - - static ReverseComparator rcInstance = new ReverseComparator(); - + /** - * Get a comparator that implements the reverse of natural ordering. This is - * intended to make it easy to sort into reverse order, by simply passing - * Collections.reverseOrder() to the sort method. The return value of this - * method is Serializable. + * Rotate the elements in a list by a specified distance. After calling this + * method, the element now at index <code>i</code> was formerly at index + * <code>(i - distance) mod list.size()</code>. The list size is unchanged. + * <p> + * + * For example, suppose a list contains <code>[t, a, n, k, s]</code>. After + * either <code>Collections.rotate(l, 4)</code> or + * <code>Collections.rotate(l, -1)</code>, the new contents are + * <code>[s, t, a, n, k]</code>. This can be applied to sublists to rotate + * just a portion of the list. For example, to move element <code>a</code> + * forward two positions in the original example, use + * <code>Collections.rotate(l.subList(1, 3+1), -1)</code>, which will + * result in <code>[t, n, k, a, s]</code>. + * <p> + * + * If the list is small or implements {@link RandomAccess}, the + * implementation exchanges the first element to its destination, then the + * displaced element, and so on until a circuit has been completed. The + * process is repeated if needed on the second element, and so forth, until + * all elements have been swapped. For large non-random lists, the + * implementation breaks the list into two sublists at index + * <code>-distance mod size</code>, calls {@link #reverse(List)} on the + * pieces, then reverses the overall list. + * + * @param list the list to rotate + * @param distance the distance to rotate by; unrestricted in value + * @throws UnsupportedOperationException if the list does not support set + * @since 1.4 */ - public static Comparator reverseOrder() + public static void rotate(List list, int distance) { - return rcInstance; + int size = list.size(); + distance %= size; + if (distance == 0) + return; + if (distance < 0) + distance += size; + + if (isSequential(list)) + { + reverse(list); + reverse(list.subList(0, distance)); + reverse(list.subList(distance, size)); + } + else + { + // Determine the least common multiple of distance and size, as there + // are (distance / LCM) loops to cycle through. + int a = size; + int lcm = distance; + int b = a % lcm; + while (b != 0) + { + a = lcm; + lcm = b; + b = a % lcm; + } + + // Now, make the swaps. We must take the remainder every time through + // the inner loop so that we don't overflow i to negative values. + while (--lcm >= 0) + { + Object o = list.get(lcm); + for (int i = lcm + distance; i != lcm; i = (i + distance) % size) + o = list.set(i, o); + list.set(lcm, o); + } + } } /** * Shuffle a list according to a default source of randomness. The algorithm - * used would result in a perfectly fair shuffle (that is, each element would - * have an equal chance of ending up in any position) with a perfect source - * of randomness; in practice the results are merely very close to perfect. + * used iterates backwards over the list, swapping each element with an + * element randomly selected from the elements in positions less than or + * equal to it (using r.nextInt(int)). * <p> - * This method operates in linear time on a random-access list, but may take - * quadratic time on a sequential-access list. - * Note: this (classpath) implementation will never take quadratic time, but - * it does make a copy of the list. This is in line with the behaviour of the - * sort methods and seems preferable. * - * @param l the list to shuffle. - * @exception UnsupportedOperationException if l.listIterator() does not - * support the set operation. + * This algorithm would result in a perfectly fair shuffle (that is, each + * element would have an equal chance of ending up in any position) if r were + * a perfect source of randomness. In practice the results are merely very + * close to perfect. + * <p> + * + * This method operates in linear time. To do this on large lists which do + * not implement {@link RandomAccess}, a temporary array is used to acheive + * this speed, since it would be quadratic access otherwise. + * + * @param l the list to shuffle + * @throws UnsupportedOperationException if l.listIterator() does not + * support the set operation */ public static void shuffle(List l) { @@ -536,11 +947,12 @@ public class Collections shuffle(l, defaultRandom); } - /** Cache a single Random object for use by shuffle(List). This improves - * performance as well as ensuring that sequential calls to shuffle() will - * not result in the same shuffle order occurring: the resolution of - * System.currentTimeMillis() is not sufficient to guarantee a unique seed. - */ + /** + * Cache a single Random object for use by shuffle(List). This improves + * performance as well as ensuring that sequential calls to shuffle() will + * not result in the same shuffle order occurring: the resolution of + * System.currentTimeMillis() is not sufficient to guarantee a unique seed. + */ private static Random defaultRandom = null; /** @@ -549,149 +961,470 @@ public class Collections * element randomly selected from the elements in positions less than or * equal to it (using r.nextInt(int)). * <p> + * * This algorithm would result in a perfectly fair shuffle (that is, each * element would have an equal chance of ending up in any position) if r were * a perfect source of randomness. In practise (eg if r = new Random()) the * results are merely very close to perfect. * <p> - * This method operates in linear time on a random-access list, but may take - * quadratic time on a sequential-access list. - * Note: this (classpath) implementation will never take quadratic time, but - * it does make a copy of the list. This is in line with the behaviour of the - * sort methods and seems preferable. * - * @param l the list to shuffle. - * @param r the source of randomness to use for the shuffle. - * @exception UnsupportedOperationException if l.listIterator() does not - * support the set operation. + * This method operates in linear time. To do this on large lists which do + * not implement {@link RandomAccess}, a temporary array is used to acheive + * this speed, since it would be quadratic access otherwise. + * + * @param l the list to shuffle + * @param r the source of randomness to use for the shuffle + * @throws UnsupportedOperationException if l.listIterator() does not + * support the set operation */ public static void shuffle(List l, Random r) { - Object[] a = l.toArray(); // Dump l into an array int lsize = l.size(); ListIterator i = l.listIterator(lsize); + boolean sequential = isSequential(l); + Object[] a = null; // stores a copy of the list for the sequential case - // Iterate backwards over l - for (int pos = lsize - 1; pos >= 0; --pos) + if (sequential) + a = l.toArray(); + + for (int pos = lsize - 1; pos > 0; --pos) { // Obtain a random position to swap with. pos + 1 is used so that the // range of the random number includes the current position. int swap = r.nextInt(pos + 1); - // Swap the swapth element of the array with the next element of the - // list. - Object o = a[swap]; - a[swap] = a[pos]; - a[pos] = o; + // Swap the desired element. + Object o; + if (sequential) + { + o = a[swap]; + a[swap] = i.previous(); + } + else + o = l.set(swap, i.previous()); - // Set the element in the original list accordingly. - i.previous(); i.set(o); } } + /** * Obtain an immutable Set consisting of a single element. The return value * of this method is Serializable. * - * @param o the single element. - * @return an immutable Set containing only o. + * @param o the single element + * @return an immutable Set containing only o + * @see Serializable */ - // It's not serializable because the spec is broken. - public static Set singleton(final Object o) + public static Set singleton(Object o) { - return new AbstractSet() + return new SingletonSet(o); + } + + /** + * The implementation of {@link #singleton(Object)}. This class name + * is required for compatibility with Sun's JDK serializability. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private static final class SingletonSet extends AbstractSet + implements Serializable + { + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = 3193687207550431679L; + + + /** + * The single element; package visible for use in nested class. + * @serial the singleton + */ + final Object element; + + /** + * Construct a singleton. + * @param o the element + */ + SingletonSet(Object o) { - public int size() - { - return 1; - } + element = o; + } - public Iterator iterator() - { - return new Iterator() - { - private boolean hasNext = true; + /** + * The size: always 1! + */ + public int size() + { + return 1; + } - public boolean hasNext() - { - return hasNext; - } + /** + * Returns an iterator over the lone element. + */ + public Iterator iterator() + { + return new Iterator() + { + private boolean hasNext = true; + + public boolean hasNext() + { + return hasNext; + } + + public Object next() + { + if (hasNext) + { + hasNext = false; + return element; + } + else + throw new NoSuchElementException(); + } + + public void remove() + { + throw new UnsupportedOperationException(); + } + }; + } - public Object next() - { - if (hasNext) - { - hasNext = false; - return o; - } - else - { - throw new NoSuchElementException(); - } - } + // The remaining methods are optional, but provide a performance + // advantage by not allocating unnecessary iterators in AbstractSet. + /** + * The set only contains one element. + */ + public boolean contains(Object o) + { + return equals(o, element); + } - public void remove() - { - throw new UnsupportedOperationException(); - } - }; - } - }; - } + /** + * This is true if the other collection only contains the element. + */ + public boolean containsAll(Collection c) + { + Iterator i = c.iterator(); + int pos = c.size(); + while (--pos >= 0) + if (! equals(i.next(), element)) + return false; + return true; + } + + /** + * The hash is just that of the element. + */ + public int hashCode() + { + return hashCode(element); + } + + /** + * Returning an array is simple. + */ + public Object[] toArray() + { + return new Object[] {element}; + } + + /** + * Obvious string. + */ + public String toString() + { + return "[" + element + "]"; + } + } // class SingletonSet /** * Obtain an immutable List consisting of a single element. The return value - * of this method is Serializable. + * of this method is Serializable, and implements RandomAccess. + * + * @param o the single element + * @return an immutable List containing only o + * @see Serializable + * @see RandomAccess + * @since 1.3 + */ + public static List singletonList(Object o) + { + return new SingletonList(o); + } + + /** + * The implementation of {@link #singletonList(Object)}. This class name + * is required for compatibility with Sun's JDK serializability. * - * @param o the single element. - * @return an immutable List containing only o. + * @author Eric Blake <ebb9@email.byu.edu> */ - // It's not serializable because the spec is broken. - public static List singletonList(final Object o) + private static final class SingletonList extends AbstractList + implements Serializable, RandomAccess { - return new AbstractList() + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = 3093736618740652951L; + + /** + * The single element. + * @serial the singleton + */ + private final Object element; + + /** + * Construct a singleton. + * @param o the element + */ + SingletonList(Object o) { - public int size() - { - return 1; - } + element = o; + } - public Object get(int index) - { - if (index == 0) - { - throw new IndexOutOfBoundsException(); - } - else - { - return o; - } - } - }; - } + /** + * The size: always 1! + */ + public int size() + { + return 1; + } + + /** + * Only index 0 is valid. + */ + public Object get(int index) + { + if (index == 0) + return element; + throw new IndexOutOfBoundsException(); + } + + // The remaining methods are optional, but provide a performance + // advantage by not allocating unnecessary iterators in AbstractList. + /** + * The set only contains one element. + */ + public boolean contains(Object o) + { + return equals(o, element); + } + + /** + * This is true if the other collection only contains the element. + */ + public boolean containsAll(Collection c) + { + Iterator i = c.iterator(); + int pos = c.size(); + while (--pos >= 0) + if (! equals(i.next(), element)) + return false; + return true; + } + + /** + * Speed up the hashcode computation. + */ + public int hashCode() + { + return 31 + hashCode(element); + } + + /** + * Either the list has it or not. + */ + public int indexOf(Object o) + { + return equals(o, element) ? 0 : -1; + } + + /** + * Either the list has it or not. + */ + public int lastIndexOf(Object o) + { + return equals(o, element) ? 0 : -1; + } + + /** + * Sublists are limited in scope. + */ + public List subList(int from, int to) + { + if (from == to && (to == 0 || to == 1)) + return EMPTY_LIST; + if (from == 0 && to == 1) + return this; + if (from > to) + throw new IllegalArgumentException(); + throw new IndexOutOfBoundsException(); + } + + /** + * Returning an array is simple. + */ + public Object[] toArray() + { + return new Object[] {element}; + } + + /** + * Obvious string. + */ + public String toString() + { + return "[" + element + "]"; + } + } // class SingletonList /** - * Obtain an immutable Map consisting of a single key value pair. + * Obtain an immutable Map consisting of a single key-value pair. * The return value of this method is Serializable. * - * @param key the single key. - * @param value the single value. - * @return an immutable Map containing only the single key value pair. + * @param key the single key + * @param value the single value + * @return an immutable Map containing only the single key-value pair + * @see Serializable + * @since 1.3 */ - // It's not serializable because the spec is broken. - public static Map singletonMap(final Object key, final Object value) + public static Map singletonMap(Object key, Object value) { - return new AbstractMap() - { - public Set entrySet() - { - return singleton(new BasicMapEntry(key, value)); - } - }; + return new SingletonMap(key, value); } /** + * The implementation of {@link #singletonMap(Object)}. This class name + * is required for compatibility with Sun's JDK serializability. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private static final class SingletonMap extends AbstractMap + implements Serializable + { + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = -6979724477215052911L; + + /** + * The single key. + * @serial the singleton key + */ + private final Object k; + + /** + * The corresponding value. + * @serial the singleton value + */ + private final Object v; + + /** + * Cache the entry set. + */ + private transient Set entries; + + /** + * Construct a singleton. + * @param key the key + * @param value the value + */ + SingletonMap(Object key, Object value) + { + k = key; + v = value; + } + + /** + * There is a single immutable entry. + */ + public Set entrySet() + { + if (entries == null) + entries = singleton(new BasicMapEntry(k, v) + { + public Object setValue(Object o) + { + throw new UnsupportedOperationException(); + } + }); + return entries; + } + + // The remaining methods are optional, but provide a performance + // advantage by not allocating unnecessary iterators in AbstractMap. + /** + * Single entry. + */ + public boolean containsKey(Object key) + { + return equals(key, k); + } + + /** + * Single entry. + */ + public boolean containsValue(Object value) + { + return equals(value, v); + } + + /** + * Single entry. + */ + public Object get(Object key) + { + return equals(key, k) ? v : null; + } + + /** + * Calculate the hashcode directly. + */ + public int hashCode() + { + return hashCode(k) ^ hashCode(v); + } + + /** + * Return the keyset. + */ + public Set keySet() + { + if (keys == null) + keys = singleton(k); + return keys; + } + + /** + * The size: always 1! + */ + public int size() + { + return 1; + } + + /** + * Return the values. Technically, a singleton, while more specific than + * a general Collection, will work. Besides, that's what the JDK uses! + */ + public Collection values() + { + if (values == null) + values = singleton(v); + return values; + } + + /** + * Obvious string. + */ + public String toString() + { + return "{" + k + "=" + v + "}"; + } + } // class SingletonMap + + /** * Sort a list according to the natural ordering of its elements. The list * must be modifiable, but can be of fixed size. The sort algorithm is * precisely that used by Arrays.sort(Object[]), which offers guaranteed @@ -700,19 +1433,14 @@ public class Collections * the array. * * @param l the List to sort - * @exception ClassCastException if some items are not mutually comparable - * @exception UnsupportedOperationException if the List is not modifiable + * @throws ClassCastException if some items are not mutually comparable + * @throws UnsupportedOperationException if the List is not modifiable + * @throws NullPointerException if some element is null + * @see Arrays#sort(Object[]) */ public static void sort(List l) { - Object[] a = l.toArray(); - Arrays.sort(a); - ListIterator i = l.listIterator(); - for (int pos = 0; pos < a.length; pos++) - { - i.next(); - i.set(a[pos]); - } + sort(l, null); } /** @@ -724,1102 +1452,1897 @@ public class Collections * the array. * * @param l the List to sort - * @param c the Comparator specifying the ordering for the elements - * @exception ClassCastException if c will not compare some pair of items - * @exception UnsupportedOperationException if the List is not modifiable + * @param c the Comparator specifying the ordering for the elements, or + * null for natural ordering + * @throws ClassCastException if c will not compare some pair of items + * @throws UnsupportedOperationException if the List is not modifiable + * @throws NullPointerException if null is compared by natural ordering + * (only possible when c is null) + * @see Arrays#sort(Object[], Comparator) */ public static void sort(List l, Comparator c) { Object[] a = l.toArray(); Arrays.sort(a, c); - ListIterator i = l.listIterator(); - for (int pos = 0; pos < a.length; pos++) + ListIterator i = l.listIterator(a.length); + for (int pos = a.length; --pos >= 0; ) { - i.next(); + i.previous(); i.set(a[pos]); } } - // All the methods from here on in require doc-comments. - - public static Collection synchronizedCollection(Collection c) - { - return new SynchronizedCollection(c); - } - public static List synchronizedList(List l) - { - return new SynchronizedList(l); - } - public static Map synchronizedMap(Map m) - { - return new SynchronizedMap(m); - } - public static Set synchronizedSet(Set s) - { - return new SynchronizedSet(s); - } - public static SortedMap synchronizedSortedMap(SortedMap m) - { - return new SynchronizedSortedMap(m); - } - public static SortedSet synchronizedSortedSet(SortedSet s) - { - return new SynchronizedSortedSet(s); - } - public static Collection unmodifiableCollection(Collection c) - { - return new UnmodifiableCollection(c); - } - public static List unmodifiableList(List l) - { - return new UnmodifiableList(l); - } - public static Map unmodifiableMap(Map m) - { - return new UnmodifiableMap(m); - } - public static Set unmodifiableSet(Set s) - { - return new UnmodifiableSet(s); - } - public static SortedMap unmodifiableSortedMap(SortedMap m) - { - return new UnmodifiableSortedMap(m); - } - public static SortedSet unmodifiableSortedSet(SortedSet s) + /** + * Swaps the elements at the specified positions within the list. Equal + * positions have no effect. + * + * @param l the list to work on + * @param i the first index to swap + * @param j the second index + * @throws UnsupportedOperationException if list.set is not supported + * @throws IndexOutOfBoundsException if either i or j is < 0 or >= + * list.size() + * @since 1.4 + */ + public static void swap(List l, int i, int j) { - return new UnmodifiableSortedSet(s); + l.set(i, l.set(j, l.get(i))); } - // Sun's spec will need to be checked for the precise names of these - // classes, for serializability's sake. However, from what I understand, - // serialization is broken for these classes anyway. - - // Note: although this code is largely uncommented, it is all very - // mechanical and there's nothing really worth commenting. - // When serialization of these classes works, we'll need doc-comments on - // them to document the serialized form. - - private static class UnmodifiableIterator implements Iterator + /** + * Returns a synchronized (thread-safe) collection wrapper backed by the + * given collection. Notice that element access through the iterators + * is thread-safe, but if the collection can be structurally modified + * (adding or removing elements) then you should synchronize around the + * iteration to avoid non-deterministic behavior:<br> + * <pre> + * Collection c = Collections.synchronizedCollection(new Collection(...)); + * ... + * synchronized (c) + * { + * Iterator i = c.iterator(); + * while (i.hasNext()) + * foo(i.next()); + * } + * </pre><p> + * + * Since the collection might be a List or a Set, and those have incompatible + * equals and hashCode requirements, this relies on Object's implementation + * rather than passing those calls on to the wrapped collection. The returned + * Collection implements Serializable, but can only be serialized if + * the collection it wraps is likewise Serializable. + * + * @param c the collection to wrap + * @return a synchronized view of the collection + * @see Serializable + */ + public static Collection synchronizedCollection(Collection c) { - private Iterator i; - - public UnmodifiableIterator(Iterator i) - { - this.i = i; - } - - public Object next() - { - return i.next(); - } - public boolean hasNext() - { - return i.hasNext(); - } - public void remove() - { - throw new UnsupportedOperationException(); - } + return new SynchronizedCollection(c); } - private static class UnmodifiableListIterator extends UnmodifiableIterator - implements ListIterator + /** + * The implementation of {@link #synchronizedCollection(Collection)}. This + * class name is required for compatibility with Sun's JDK serializability. + * Package visible, so that collections such as the one for + * Hashtable.values() can specify which object to synchronize on. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + static class SynchronizedCollection + implements Collection, Serializable { - // This is stored both here and in the superclass, to avoid excessive - // casting. - private ListIterator li; - - public UnmodifiableListIterator(ListIterator li) + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = 3053995032091335093L; + + /** + * The wrapped collection. Package visible for use by subclasses. + * @serial the real collection + */ + final Collection c; + + /** + * The object to synchronize on. When an instance is created via public + * methods, it will be this; but other uses like SynchronizedMap.values() + * must specify another mutex. Package visible for use by subclasses. + * @serial the lock + */ + final Object mutex; + + /** + * Wrap a given collection. + * @param c the collection to wrap + * @throws NullPointerException if c is null + */ + SynchronizedCollection(Collection c) { - super(li); - this.li = li; - } - - public boolean hasPrevious() - { - return li.hasPrevious(); - } - public Object previous() - { - return li.previous(); - } - public int nextIndex() - { - return li.nextIndex(); - } - public int previousIndex() - { - return li.previousIndex(); - } - public void add(Object o) - { - throw new UnsupportedOperationException(); - } - public void set(Object o) - { - throw new UnsupportedOperationException(); + this.c = c; + mutex = this; + if (c == null) + throw new NullPointerException(); } - } - - private static class UnmodifiableCollection implements Collection, - Serializable - { - Collection c; - public UnmodifiableCollection(Collection c) + /** + * Called only by trusted code to specify the mutex as well as the + * collection. + * @param sync the mutex + * @param c the collection + */ + SynchronizedCollection(Object sync, Collection c) { this.c = c; + mutex = sync; } public boolean add(Object o) { - throw new UnsupportedOperationException(); + synchronized (mutex) + { + return c.add(o); + } } - public boolean addAll(Collection c) + + public boolean addAll(Collection col) { - throw new UnsupportedOperationException(); + synchronized (mutex) + { + return c.addAll(col); + } } + public void clear() { - throw new UnsupportedOperationException(); + synchronized (mutex) + { + c.clear(); + } } + public boolean contains(Object o) { - return c.contains(o); + synchronized (mutex) + { + return c.contains(o); + } } + public boolean containsAll(Collection c1) { - return c.containsAll(c1); + synchronized (mutex) + { + return c.containsAll(c1); + } } + public boolean isEmpty() { - return c.isEmpty(); + synchronized (mutex) + { + return c.isEmpty(); + } } + public Iterator iterator() { - return new UnmodifiableIterator(c.iterator()); + synchronized (mutex) + { + return new SynchronizedIterator(mutex, c.iterator()); + } } + public boolean remove(Object o) { - throw new UnsupportedOperationException(); + synchronized (mutex) + { + return c.remove(o); + } } - public boolean removeAll(Collection c) + + public boolean removeAll(Collection col) { - throw new UnsupportedOperationException(); + synchronized (mutex) + { + return c.removeAll(col); + } } - public boolean retainAll(Collection c) + + public boolean retainAll(Collection col) { - throw new UnsupportedOperationException(); + synchronized (mutex) + { + return c.retainAll(col); + } } + public int size() { - return c.size(); + synchronized (mutex) + { + return c.size(); + } } + public Object[] toArray() { - return c.toArray(); + synchronized (mutex) + { + return c.toArray(); + } } - public Object[] toArray(Object[]a) + + public Object[] toArray(Object[] a) { - return c.toArray(a); + synchronized (mutex) + { + return c.toArray(a); + } } + public String toString() { - return c.toString(); + synchronized (mutex) + { + return c.toString(); + } + } + } // class SynchronizedCollection + + /** + * The implementation of the various iterator methods in the + * synchronized classes. These iterators must "sync" on the same object + * as the collection they iterate over. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private static class SynchronizedIterator implements Iterator + { + /** + * The object to synchronize on. Package visible for use by subclass. + */ + final Object mutex; + + /** + * The wrapped iterator. + */ + private final Iterator i; + + /** + * Only trusted code creates a wrapper, with the specified sync. + * @param sync the mutex + * @param i the wrapped iterator + */ + SynchronizedIterator(Object sync, Iterator i) + { + this.i = i; + mutex = sync; + } + + public Object next() + { + synchronized (mutex) + { + return i.next(); + } } + + public boolean hasNext() + { + synchronized (mutex) + { + return i.hasNext(); + } + } + + public void remove() + { + synchronized (mutex) + { + i.remove(); + } + } + } // class SynchronizedIterator + + /** + * Returns a synchronized (thread-safe) list wrapper backed by the + * given list. Notice that element access through the iterators + * is thread-safe, but if the list can be structurally modified + * (adding or removing elements) then you should synchronize around the + * iteration to avoid non-deterministic behavior:<br> + * <pre> + * List l = Collections.synchronizedList(new List(...)); + * ... + * synchronized (l) + * { + * Iterator i = l.iterator(); + * while (i.hasNext()) + * foo(i.next()); + * } + * </pre><p> + * + * The returned List implements Serializable, but can only be serialized if + * the list it wraps is likewise Serializable. In addition, if the wrapped + * list implements RandomAccess, this does too. + * + * @param l the list to wrap + * @return a synchronized view of the list + * @see Serializable + * @see RandomAccess + */ + public static List synchronizedList(List l) + { + if (l instanceof RandomAccess) + return new SynchronizedRandomAccessList(l); + return new SynchronizedList(l); } - private static class UnmodifiableList extends UnmodifiableCollection + /** + * The implementation of {@link #synchronizedList(List)} for sequential + * lists. This class name is required for compatibility with Sun's JDK + * serializability. Package visible, so that lists such as Vector.subList() + * can specify which object to synchronize on. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + static class SynchronizedList extends SynchronizedCollection implements List { - // This is stored both here and in the superclass, to avoid excessive - // casting. - List l; - - public UnmodifiableList(List l) + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = -7754090372962971524L; + + /** + * The wrapped list; stored both here and in the superclass to avoid + * excessive casting. Package visible for use by subclass. + * @serial the wrapped list + */ + final List list; + + /** + * Wrap a given list. + * @param l the list to wrap + * @throws NullPointerException if l is null + */ + SynchronizedList(List l) { super(l); - this.l = l; + list = l; + } + + /** + * Called only by trusted code to specify the mutex as well as the list. + * @param sync the mutex + * @param l the list + */ + SynchronizedList(Object sync, List l) + { + super(sync, l); + list = l; } public void add(int index, Object o) { - throw new UnsupportedOperationException(); + synchronized (mutex) + { + list.add(index, o); + } } + public boolean addAll(int index, Collection c) { - throw new UnsupportedOperationException(); + synchronized (mutex) + { + return list.addAll(index, c); + } } + public boolean equals(Object o) { - return l.equals(o); + synchronized (mutex) + { + return list.equals(o); + } } + public Object get(int index) { - return l.get(index); + synchronized (mutex) + { + return list.get(index); + } } + public int hashCode() { - return l.hashCode(); + synchronized (mutex) + { + return list.hashCode(); + } } + public int indexOf(Object o) { - return l.indexOf(o); + synchronized (mutex) + { + return list.indexOf(o); + } } + public int lastIndexOf(Object o) { - return l.lastIndexOf(o); + synchronized (mutex) + { + return list.lastIndexOf(o); + } } + public ListIterator listIterator() { - return new UnmodifiableListIterator(l.listIterator()); + synchronized (mutex) + { + return new SynchronizedListIterator(mutex, list.listIterator()); + } } + public ListIterator listIterator(int index) { - return new UnmodifiableListIterator(l.listIterator(index)); + synchronized (mutex) + { + return new SynchronizedListIterator(mutex, list.listIterator(index)); + } } + public Object remove(int index) { - throw new UnsupportedOperationException(); + synchronized (mutex) + { + return list.remove(index); + } } + public Object set(int index, Object o) { - throw new UnsupportedOperationException(); + synchronized (mutex) + { + return list.set(index, o); + } } + public List subList(int fromIndex, int toIndex) { - return new UnmodifiableList(l.subList(fromIndex, toIndex)); + synchronized (mutex) + { + return new SynchronizedList(mutex, list.subList(fromIndex, toIndex)); + } } - } + } // class SynchronizedList - private static class UnmodifiableSet extends UnmodifiableCollection - implements Set + /** + * The implementation of {@link #synchronizedList(List)} for random-access + * lists. This class name is required for compatibility with Sun's JDK + * serializability. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private static final class SynchronizedRandomAccessList + extends SynchronizedList implements RandomAccess { - public UnmodifiableSet(Set s) + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = 1530674583602358482L; + + /** + * Wrap a given list. + * @param l the list to wrap + * @throws NullPointerException if l is null + */ + SynchronizedRandomAccessList(List l) { - super(s); + super(l); } - public boolean equals(Object o) + + /** + * Called only by trusted code to specify the mutex as well as the + * collection. + * @param sync the mutex + * @param l the list + */ + SynchronizedRandomAccessList(Object sync, List l) { - return c.equals(o); + super(sync, l); } - public int hashCode() + + public List subList(int fromIndex, int toIndex) { - return c.hashCode(); + synchronized (mutex) + { + return new SynchronizedRandomAccessList(mutex, + list.subList(fromIndex, + toIndex)); + } } - } + } // class SynchronizedRandomAccessList - private static class UnmodifiableSortedSet extends UnmodifiableSet - implements SortedSet + /** + * The implementation of {@link SynchronizedList#listIterator()}. This + * iterator must "sync" on the same object as the list it iterates over. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private static final class SynchronizedListIterator + extends SynchronizedIterator implements ListIterator { - // This is stored both here and in the superclass, to avoid excessive - // casting. - private SortedSet ss; - - public UnmodifiableSortedSet(SortedSet ss) + /** + * The wrapped iterator, stored both here and in the superclass to + * avoid excessive casting. + */ + private final ListIterator li; + + /** + * Only trusted code creates a wrapper, with the specified sync. + * @param sync the mutex + * @param li the wrapped iterator + */ + SynchronizedListIterator(Object sync, ListIterator li) { - super(ss); - this.ss = ss; + super(sync, li); + this.li = li; } - public Comparator comparator() + public void add(Object o) { - return ss.comparator(); + synchronized (mutex) + { + li.add(o); + } } - public Object first() + public boolean hasPrevious() { - return ss.first(); + synchronized (mutex) + { + return li.hasPrevious(); + } } - public Object last() + + public int nextIndex() { - return ss.last(); + synchronized (mutex) + { + return li.nextIndex(); + } } - public SortedSet headSet(Object toElement) + + public Object previous() { - return new UnmodifiableSortedSet(ss.headSet(toElement)); + synchronized (mutex) + { + return li.previous(); + } } - public SortedSet tailSet(Object fromElement) + + public int previousIndex() { - return new UnmodifiableSortedSet(ss.tailSet(fromElement)); + synchronized (mutex) + { + return li.previousIndex(); + } } - public SortedSet subSet(Object fromElement, Object toElement) + + public void set(Object o) { - return new UnmodifiableSortedSet(ss.subSet(fromElement, toElement)); + synchronized (mutex) + { + li.set(o); + } } + } // class SynchronizedListIterator + + /** + * Returns a synchronized (thread-safe) map wrapper backed by the given + * map. Notice that element access through the collection views and their + * iterators are thread-safe, but if the map can be structurally modified + * (adding or removing elements) then you should synchronize around the + * iteration to avoid non-deterministic behavior:<br> + * <pre> + * Map m = Collections.synchronizedMap(new Map(...)); + * ... + * Set s = m.keySet(); // safe outside a synchronized block + * synchronized (m) // synch on m, not s + * { + * Iterator i = s.iterator(); + * while (i.hasNext()) + * foo(i.next()); + * } + * </pre><p> + * + * The returned Map implements Serializable, but can only be serialized if + * the map it wraps is likewise Serializable. + * + * @param m the map to wrap + * @return a synchronized view of the map + * @see Serializable + */ + public static Map synchronizedMap(Map m) + { + return new SynchronizedMap(m); } - private static class UnmodifiableMap implements Map, Serializable + /** + * The implementation of {@link #synchronizedMap(Map)}. This + * class name is required for compatibility with Sun's JDK serializability. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private static class SynchronizedMap implements Map, Serializable { - Map m; + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = 1978198479659022715L; + + /** + * The wrapped map. + * @serial the real map + */ + private final Map m; + + /** + * The object to synchronize on. When an instance is created via public + * methods, it will be this; but other uses like + * SynchronizedSortedMap.subMap() must specify another mutex. Package + * visible for use by subclass. + * @serial the lock + */ + final Object mutex; + + /** + * Cache the entry set. + */ + private transient Set entries; + + /** + * Cache the key set. + */ + private transient Set keys; + + /** + * Cache the value collection. + */ + private transient Collection values; + + /** + * Wrap a given map. + * @param m the map to wrap + * @throws NullPointerException if m is null + */ + SynchronizedMap(Map m) + { + this.m = m; + mutex = this; + if (m == null) + throw new NullPointerException(); + } - public UnmodifiableMap(Map m) + /** + * Called only by trusted code to specify the mutex as well as the map. + * @param sync the mutex + * @param m the map + */ + SynchronizedMap(Object sync, Map m) { this.m = m; + mutex = sync; } public void clear() { - throw new UnsupportedOperationException(); + synchronized (mutex) + { + m.clear(); + } } + public boolean containsKey(Object key) { - return m.containsKey(key); + synchronized (mutex) + { + return m.containsKey(key); + } } + public boolean containsValue(Object value) { - return m.containsValue(value); + synchronized (mutex) + { + return m.containsValue(value); + } } // This is one of the ickiest cases of nesting I've ever seen. It just - // means "return an UnmodifiableSet, except that the iterator() method - // returns an UnmodifiableIterator whos next() method returns an - // unmodifiable wrapper around its normal return value". + // means "return a SynchronizedSet, except that the iterator() method + // returns an SynchronizedIterator whose next() method returns a + // synchronized wrapper around its normal return value". public Set entrySet() { - return new UnmodifiableSet(m.entrySet()) - { - public Iterator iterator() - { - return new UnmodifiableIterator(c.iterator()) - { - public Object next() - { - final Map.Entry e = (Map.Entry) super.next(); - return new Map.Entry() - { - public Object getKey() - { - return e.getKey(); - } - public Object getValue() - { - return e.getValue(); - } - public Object setValue(Object value) - { - throw new UnsupportedOperationException(); - } - public int hashCode() - { - return e.hashCode(); - } - public boolean equals(Object o) - { - return e.equals(o); - } - }; - } - }; - } - }; + // Define this here to spare some nesting. + class SynchronizedMapEntry implements Map.Entry + { + final Map.Entry e; + SynchronizedMapEntry(Object o) + { + e = (Map.Entry) o; + } + public boolean equals(Object o) + { + synchronized (mutex) + { + return e.equals(o); + } + } + public Object getKey() + { + synchronized (mutex) + { + return e.getKey(); + } + } + public Object getValue() + { + synchronized (mutex) + { + return e.getValue(); + } + } + public int hashCode() + { + synchronized (mutex) + { + return e.hashCode(); + } + } + public Object setValue(Object value) + { + synchronized (mutex) + { + return e.setValue(value); + } + } + public String toString() + { + synchronized (mutex) + { + return e.toString(); + } + } + } // class SynchronizedMapEntry + + // Now the actual code. + if (entries == null) + synchronized (mutex) + { + entries = new SynchronizedSet(mutex, m.entrySet()) + { + public Iterator iterator() + { + synchronized (super.mutex) + { + return new SynchronizedIterator(super.mutex, c.iterator()) + { + public Object next() + { + synchronized (super.mutex) + { + return new SynchronizedMapEntry(super.next()); + } + } + }; + } + } + }; + } + return entries; } + public boolean equals(Object o) { - return m.equals(o); + synchronized (mutex) + { + return m.equals(o); + } } + public Object get(Object key) { - return m.get(key); - } - public Object put(Object key, Object value) - { - throw new UnsupportedOperationException(); + synchronized (mutex) + { + return m.get(key); + } } + public int hashCode() { - return m.hashCode(); + synchronized (mutex) + { + return m.hashCode(); + } } + public boolean isEmpty() { - return m.isEmpty(); + synchronized (mutex) + { + return m.isEmpty(); + } } + public Set keySet() { - return new UnmodifiableSet(m.keySet()); + if (keys == null) + synchronized (mutex) + { + keys = new SynchronizedSet(mutex, m.keySet()); + } + return keys; } - public void putAll(Map m) + + public Object put(Object key, Object value) { - throw new UnsupportedOperationException(); + synchronized (mutex) + { + return m.put(key, value); + } } + + public void putAll(Map map) + { + synchronized (mutex) + { + m.putAll(map); + } + } + public Object remove(Object o) { - throw new UnsupportedOperationException(); + synchronized (mutex) + { + return m.remove(o); + } } + public int size() { - return m.size(); + synchronized (mutex) + { + return m.size(); + } } - public Collection values() + + public String toString() { - return new UnmodifiableCollection(m.values()); + synchronized (mutex) + { + return m.toString(); + } } - public String toString() + + public Collection values() { - return m.toString(); + if (values == null) + synchronized (mutex) + { + values = new SynchronizedCollection(mutex, m.values()); + } + return values; } - } + } // class SynchronizedMap - private static class UnmodifiableSortedMap extends UnmodifiableMap - implements SortedMap + /** + * Returns a synchronized (thread-safe) set wrapper backed by the given + * set. Notice that element access through the iterator is thread-safe, but + * if the set can be structurally modified (adding or removing elements) + * then you should synchronize around the iteration to avoid + * non-deterministic behavior:<br> + * <pre> + * Set s = Collections.synchronizedSet(new Set(...)); + * ... + * synchronized (s) + * { + * Iterator i = s.iterator(); + * while (i.hasNext()) + * foo(i.next()); + * } + * </pre><p> + * + * The returned Set implements Serializable, but can only be serialized if + * the set it wraps is likewise Serializable. + * + * @param s the set to wrap + * @return a synchronized view of the set + * @see Serializable + */ + public static Set synchronizedSet(Set s) { - // This is stored both here and in the superclass, to avoid excessive - // casting. - private SortedMap sm; + return new SynchronizedSet(s); + } - public UnmodifiableSortedMap(SortedMap sm) + /** + * The implementation of {@link #synchronizedSet(Set)}. This class + * name is required for compatibility with Sun's JDK serializability. + * Package visible, so that sets such as Hashtable.keySet() + * can specify which object to synchronize on. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + static class SynchronizedSet extends SynchronizedCollection + implements Set + { + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = 487447009682186044L; + + /** + * Wrap a given set. + * @param s the set to wrap + * @throws NullPointerException if s is null + */ + SynchronizedSet(Set s) { - super(sm); - this.sm = sm; + super(s); } - public Comparator comparator() + /** + * Called only by trusted code to specify the mutex as well as the set. + * @param sync the mutex + * @param s the set + */ + SynchronizedSet(Object sync, Set s) { - return sm.comparator(); + super(sync, s); } - public Object firstKey() + + public boolean equals(Object o) { - return sm.firstKey(); + synchronized (mutex) + { + return c.equals(o); + } } - public Object lastKey() + + public int hashCode() { - return sm.lastKey(); + synchronized (mutex) + { + return c.hashCode(); + } } - public SortedMap headMap(Object toKey) + } // class SynchronizedSet + + /** + * Returns a synchronized (thread-safe) sorted map wrapper backed by the + * given map. Notice that element access through the collection views, + * subviews, and their iterators are thread-safe, but if the map can be + * structurally modified (adding or removing elements) then you should + * synchronize around the iteration to avoid non-deterministic behavior:<br> + * <pre> + * SortedMap m = Collections.synchronizedSortedMap(new SortedMap(...)); + * ... + * Set s = m.keySet(); // safe outside a synchronized block + * SortedMap m2 = m.headMap(foo); // safe outside a synchronized block + * Set s2 = m2.keySet(); // safe outside a synchronized block + * synchronized (m) // synch on m, not m2, s or s2 + * { + * Iterator i = s.iterator(); + * while (i.hasNext()) + * foo(i.next()); + * i = s2.iterator(); + * while (i.hasNext()) + * bar(i.next()); + * } + * </pre><p> + * + * The returned SortedMap implements Serializable, but can only be + * serialized if the map it wraps is likewise Serializable. + * + * @param m the sorted map to wrap + * @return a synchronized view of the sorted map + * @see Serializable + */ + public static SortedMap synchronizedSortedMap(SortedMap m) + { + return new SynchronizedSortedMap(m); + } + + /** + * The implementation of {@link #synchronizedSortedMap(SortedMap)}. This + * class name is required for compatibility with Sun's JDK serializability. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private static final class SynchronizedSortedMap extends SynchronizedMap + implements SortedMap + { + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = -8798146769416483793L; + + /** + * The wrapped map; stored both here and in the superclass to avoid + * excessive casting. + * @serial the wrapped map + */ + private final SortedMap sm; + + /** + * Wrap a given map. + * @param sm the map to wrap + * @throws NullPointerException if sm is null + */ + SynchronizedSortedMap(SortedMap sm) { - return new UnmodifiableSortedMap(sm.headMap(toKey)); + super(sm); + this.sm = sm; } - public SortedMap tailMap(Object fromKey) + + /** + * Called only by trusted code to specify the mutex as well as the map. + * @param sync the mutex + * @param sm the map + */ + SynchronizedSortedMap(Object sync, SortedMap sm) { - return new UnmodifiableSortedMap(sm.tailMap(fromKey)); + super(sync, sm); + this.sm = sm; } - public SortedMap subMap(Object fromKey, Object toKey) + + public Comparator comparator() { - return new UnmodifiableSortedMap(sm.subMap(fromKey, toKey)); + synchronized (mutex) + { + return sm.comparator(); + } } - } - - // All the "Synchronized" wrapper objects include a "sync" field which - // specifies what object to synchronize on. That way, nested wrappers such as - // UnmodifiableMap.keySet synchronize on the right things. - private static class SynchronizedIterator implements Iterator - { - Object sync; - private Iterator i; + public Object firstKey() + { + synchronized (mutex) + { + return sm.firstKey(); + } + } - public SynchronizedIterator(Object sync, Iterator i) + public SortedMap headMap(Object toKey) { - this.sync = sync; - this.i = i; + synchronized (mutex) + { + return new SynchronizedSortedMap(mutex, sm.headMap(toKey)); + } } - public Object next() + public Object lastKey() { - synchronized(sync) - { - return i.next(); - } + synchronized (mutex) + { + return sm.lastKey(); + } } - public boolean hasNext() + + public SortedMap subMap(Object fromKey, Object toKey) { - synchronized(sync) - { - return i.hasNext(); - } + synchronized (mutex) + { + return new SynchronizedSortedMap(mutex, sm.subMap(fromKey, toKey)); + } } - public void remove() + + public SortedMap tailMap(Object fromKey) { - synchronized(sync) - { - i.remove(); - } + synchronized (mutex) + { + return new SynchronizedSortedMap(mutex, sm.tailMap(fromKey)); + } } + } // class SynchronizedSortedMap + + /** + * Returns a synchronized (thread-safe) sorted set wrapper backed by the + * given set. Notice that element access through the iterator and through + * subviews are thread-safe, but if the set can be structurally modified + * (adding or removing elements) then you should synchronize around the + * iteration to avoid non-deterministic behavior:<br> + * <pre> + * SortedSet s = Collections.synchronizedSortedSet(new SortedSet(...)); + * ... + * SortedSet s2 = s.headSet(foo); // safe outside a synchronized block + * synchronized (s) // synch on s, not s2 + * { + * Iterator i = s2.iterator(); + * while (i.hasNext()) + * foo(i.next()); + * } + * </pre><p> + * + * The returned SortedSet implements Serializable, but can only be + * serialized if the set it wraps is likewise Serializable. + * + * @param s the sorted set to wrap + * @return a synchronized view of the sorted set + * @see Serializable + */ + public static SortedSet synchronizedSortedSet(SortedSet s) + { + return new SynchronizedSortedSet(s); } - private static class SynchronizedListIterator extends SynchronizedIterator - implements ListIterator + /** + * The implementation of {@link #synchronizedSortedSet(SortedSet)}. This + * class name is required for compatibility with Sun's JDK serializability. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private static final class SynchronizedSortedSet extends SynchronizedSet + implements SortedSet { - // This is stored both here and in the superclass, to avoid excessive - // casting. - private ListIterator li; + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = 8695801310862127406L; + + /** + * The wrapped set; stored both here and in the superclass to avoid + * excessive casting. + * @serial the wrapped set + */ + private final SortedSet ss; + + /** + * Wrap a given set. + * @param ss the set to wrap + * @throws NullPointerException if ss is null + */ + SynchronizedSortedSet(SortedSet ss) + { + super(ss); + this.ss = ss; + } - public SynchronizedListIterator(Object sync, ListIterator li) + /** + * Called only by trusted code to specify the mutex as well as the set. + * @param sync the mutex + * @param l the list + */ + SynchronizedSortedSet(Object sync, SortedSet ss) { - super(sync, li); - this.li = li; + super(sync, ss); + this.ss = ss; } - public boolean hasPrevious() + public Comparator comparator() { - synchronized(sync) - { - return li.hasPrevious(); - } + synchronized (mutex) + { + return ss.comparator(); + } } - public Object previous() + + public Object first() { - synchronized(sync) - { - return li.previous(); - } + synchronized (mutex) + { + return ss.first(); + } } - public int nextIndex() + + public SortedSet headSet(Object toElement) { - synchronized(sync) - { - return li.nextIndex(); - } + synchronized (mutex) + { + return new SynchronizedSortedSet(mutex, ss.headSet(toElement)); + } } - public int previousIndex() + + public Object last() { - synchronized(sync) - { - return li.previousIndex(); - } + synchronized (mutex) + { + return ss.last(); + } } - public void add(Object o) + + public SortedSet subSet(Object fromElement, Object toElement) { - synchronized(sync) - { - li.add(o); - } + synchronized (mutex) + { + return new SynchronizedSortedSet(mutex, + ss.subSet(fromElement, toElement)); + } } - public void set(Object o) + + public SortedSet tailSet(Object fromElement) { - synchronized(sync) - { - li.set(o); - } + synchronized (mutex) + { + return new SynchronizedSortedSet(mutex, ss.tailSet(fromElement)); + } } - } + } // class SynchronizedSortedSet /** - * Package visible, so that collections such as the one for - * Hashtable.values() can specify which object to synchronize on. + * Returns an unmodifiable view of the given collection. This allows + * "read-only" access, although changes in the backing collection show up + * in this view. Attempts to modify the collection directly or via iterators + * will fail with {@link UnsupportedOperationException}. + * <p> + * + * Since the collection might be a List or a Set, and those have incompatible + * equals and hashCode requirements, this relies on Object's implementation + * rather than passing those calls on to the wrapped collection. The returned + * Collection implements Serializable, but can only be serialized if + * the collection it wraps is likewise Serializable. + * + * @param c the collection to wrap + * @return a read-only view of the collection + * @see Serializable */ - static class SynchronizedCollection implements Collection, - Serializable + public static Collection unmodifiableCollection(Collection c) { - Object sync; - Collection c; + return new UnmodifiableCollection(c); + } - public SynchronizedCollection(Collection c) - { - this.sync = this; - this.c = c; - } - public SynchronizedCollection(Object sync, Collection c) + /** + * The implementation of {@link #unmodifiableCollection(Collection)}. This + * class name is required for compatibility with Sun's JDK serializability. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private static class UnmodifiableCollection + implements Collection, Serializable + { + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = 1820017752578914078L; + + /** + * The wrapped collection. Package visible for use by subclasses. + * @serial the real collection + */ + final Collection c; + + /** + * Wrap a given collection. + * @param c the collection to wrap + * @throws NullPointerException if c is null + */ + UnmodifiableCollection(Collection c) { this.c = c; - this.sync = sync; + if (c == null) + throw new NullPointerException(); } public boolean add(Object o) { - synchronized(sync) - { - return c.add(o); - } + throw new UnsupportedOperationException(); } - public boolean addAll(Collection col) + + public boolean addAll(Collection c) { - synchronized(sync) - { - return c.addAll(col); - } + throw new UnsupportedOperationException(); } + public void clear() { - synchronized(sync) - { - c.clear(); - } + throw new UnsupportedOperationException(); } + public boolean contains(Object o) { - synchronized(sync) - { - return c.contains(o); - } + return c.contains(o); } + public boolean containsAll(Collection c1) { - synchronized(sync) - { - return c.containsAll(c1); - } + return c.containsAll(c1); } + public boolean isEmpty() { - synchronized(sync) - { - return c.isEmpty(); - } + return c.isEmpty(); } + public Iterator iterator() { - synchronized(sync) - { - return new SynchronizedIterator(sync, c.iterator()); - } + return new UnmodifiableIterator(c.iterator()); } + public boolean remove(Object o) { - synchronized(sync) - { - return c.remove(o); - } + throw new UnsupportedOperationException(); } - public boolean removeAll(Collection col) + + public boolean removeAll(Collection c) { - synchronized(sync) - { - return c.removeAll(col); - } + throw new UnsupportedOperationException(); } - public boolean retainAll(Collection col) + + public boolean retainAll(Collection c) { - synchronized(sync) - { - return c.retainAll(col); - } + throw new UnsupportedOperationException(); } + public int size() { - synchronized(sync) - { - return c.size(); - } + return c.size(); } + public Object[] toArray() { - synchronized(sync) - { - return c.toArray(); - } + return c.toArray(); } - public Object[] toArray(Object[]a) + + public Object[] toArray(Object[] a) { - synchronized(sync) - { - return c.toArray(a); - } + return c.toArray(a); } + public String toString() { - synchronized(sync) - { - return c.toString(); - } + return c.toString(); } - } + } // class UnmodifiableCollection - private static class SynchronizedList extends SynchronizedCollection - implements List + /** + * The implementation of the various iterator methods in the + * unmodifiable classes. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private static class UnmodifiableIterator implements Iterator { - // This is stored both here and in the superclass, to avoid excessive - // casting. - List l; + /** + * The wrapped iterator. + */ + private final Iterator i; - public SynchronizedList(Object sync, List l) + /** + * Only trusted code creates a wrapper. + * @param i the wrapped iterator + */ + UnmodifiableIterator(Iterator i) { - super(sync, l); - this.l = l; + this.i = i; + } + + public Object next() + { + return i.next(); + } + + public boolean hasNext() + { + return i.hasNext(); } - public SynchronizedList(List l) + + public void remove() + { + throw new UnsupportedOperationException(); + } + } // class UnmodifiableIterator + + /** + * Returns an unmodifiable view of the given list. This allows + * "read-only" access, although changes in the backing list show up + * in this view. Attempts to modify the list directly, via iterators, or + * via sublists, will fail with {@link UnsupportedOperationException}. + * <p> + * + * The returned List implements Serializable, but can only be serialized if + * the list it wraps is likewise Serializable. In addition, if the wrapped + * list implements RandomAccess, this does too. + * + * @param l the list to wrap + * @return a read-only view of the list + * @see Serializable + * @see RandomAccess + */ + public static List unmodifiableList(List l) + { + if (l instanceof RandomAccess) + return new UnmodifiableRandomAccessList(l); + return new UnmodifiableList(l); + } + + /** + * The implementation of {@link #unmodifiableList(List)} for sequential + * lists. This class name is required for compatibility with Sun's JDK + * serializability. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private static class UnmodifiableList extends UnmodifiableCollection + implements List + { + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = -283967356065247728L; + + + /** + * The wrapped list; stored both here and in the superclass to avoid + * excessive casting. Package visible for use by subclass. + * @serial the wrapped list + */ + final List list; + + /** + * Wrap a given list. + * @param l the list to wrap + * @throws NullPointerException if l is null + */ + UnmodifiableList(List l) { super(l); - this.l = l; + list = l; } public void add(int index, Object o) { - synchronized(sync) - { - l.add(index, o); - } + throw new UnsupportedOperationException(); } + public boolean addAll(int index, Collection c) { - synchronized(sync) - { - return l.addAll(index, c); - } + throw new UnsupportedOperationException(); } + public boolean equals(Object o) { - synchronized(sync) - { - return l.equals(o); - } + return list.equals(o); } + public Object get(int index) { - synchronized(sync) - { - return l.get(index); - } + return list.get(index); } + public int hashCode() { - synchronized(sync) - { - return l.hashCode(); - } + return list.hashCode(); } + public int indexOf(Object o) { - synchronized(sync) - { - return l.indexOf(o); - } + return list.indexOf(o); } + public int lastIndexOf(Object o) { - synchronized(sync) - { - return l.lastIndexOf(o); - } + return list.lastIndexOf(o); } + public ListIterator listIterator() { - synchronized(sync) - { - return new SynchronizedListIterator(sync, l.listIterator()); - } + return new UnmodifiableListIterator(list.listIterator()); } + public ListIterator listIterator(int index) { - synchronized(sync) - { - return new SynchronizedListIterator(sync, l.listIterator(index)); - } + return new UnmodifiableListIterator(list.listIterator(index)); } + public Object remove(int index) { - synchronized(sync) - { - return l.remove(index); - } - } - public boolean remove(Object o) - { - synchronized(sync) - { - return l.remove(o); - } + throw new UnsupportedOperationException(); } + public Object set(int index, Object o) { - synchronized(sync) - { - return l.set(index, o); - } + throw new UnsupportedOperationException(); } + public List subList(int fromIndex, int toIndex) { - synchronized(sync) - { - return new SynchronizedList(l.subList(fromIndex, toIndex)); - } + return unmodifiableList(list.subList(fromIndex, toIndex)); } - } + } // class UnmodifiableList /** - * Package visible, so that sets such as the one for Hashtable.keySet() - * can specify which object to synchronize on. + * The implementation of {@link #unmodifiableList(List)} for random-access + * lists. This class name is required for compatibility with Sun's JDK + * serializability. + * + * @author Eric Blake <ebb9@email.byu.edu> */ - static class SynchronizedSet extends SynchronizedCollection - implements Set + private static final class UnmodifiableRandomAccessList + extends UnmodifiableList implements RandomAccess { - public SynchronizedSet(Object sync, Set s) + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = -2542308836966382001L; + + /** + * Wrap a given list. + * @param l the list to wrap + * @throws NullPointerException if l is null + */ + UnmodifiableRandomAccessList(List l) { - super(sync, s); - } - public SynchronizedSet(Set s) - { - super(s); - } - - public boolean equals(Object o) - { - synchronized(sync) - { - return c.equals(o); - } - } - public int hashCode() - { - synchronized(sync) - { - return c.hashCode(); - } + super(l); } - } + } // class UnmodifiableRandomAccessList - private static class SynchronizedSortedSet extends SynchronizedSet - implements SortedSet + /** + * The implementation of {@link UnmodifiableList#listIterator()}. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private static final class UnmodifiableListIterator + extends UnmodifiableIterator implements ListIterator { - // This is stored both here and in the superclass, to avoid excessive - // casting. - private SortedSet ss; - - public SynchronizedSortedSet(Object sync, SortedSet ss) - { - super(sync, ss); - this.ss = ss; - } - public SynchronizedSortedSet(SortedSet ss) + /** + * The wrapped iterator, stored both here and in the superclass to + * avoid excessive casting. + */ + private final ListIterator li; + + /** + * Only trusted code creates a wrapper. + * @param li the wrapped iterator + */ + UnmodifiableListIterator(ListIterator li) { - super(ss); - this.ss = ss; + super(li); + this.li = li; } - public Comparator comparator() + public void add(Object o) { - synchronized(sync) - { - return ss.comparator(); - } + throw new UnsupportedOperationException(); } - public Object first() + + public boolean hasPrevious() { - synchronized(sync) - { - return ss.first(); - } + return li.hasPrevious(); } - public Object last() + + public int nextIndex() { - synchronized(sync) - { - return ss.last(); - } + return li.nextIndex(); } - public SortedSet headSet(Object toElement) + + public Object previous() { - synchronized(sync) - { - return new SynchronizedSortedSet(sync, ss.headSet(toElement)); - } + return li.previous(); } - public SortedSet tailSet(Object fromElement) + + public int previousIndex() { - synchronized(sync) - { - return new SynchronizedSortedSet(sync, ss.tailSet(fromElement)); - } + return li.previousIndex(); } - public SortedSet subSet(Object fromElement, Object toElement) + + public void set(Object o) { - synchronized(sync) - { - return new SynchronizedSortedSet(sync, - ss.subSet(fromElement, toElement)); - } + throw new UnsupportedOperationException(); } - } + } // class UnmodifiableListIterator - private static class SynchronizedMap implements Map, Serializable + /** + * Returns an unmodifiable view of the given map. This allows "read-only" + * access, although changes in the backing map show up in this view. + * Attempts to modify the map directly, or via collection views or their + * iterators will fail with {@link UnsupportedOperationException}. + * <p> + * + * The returned Map implements Serializable, but can only be serialized if + * the map it wraps is likewise Serializable. + * + * @param m the map to wrap + * @return a read-only view of the map + * @see Serializable + */ + public static Map unmodifiableMap(Map m) { - Object sync; - Map m; + return new UnmodifiableMap(m); + } - public SynchronizedMap(Object sync, Map m) - { - this.sync = sync; - this.m = m; - } - public SynchronizedMap(Map m) + /** + * The implementation of {@link #unmodifiableMap(Map)}. This + * class name is required for compatibility with Sun's JDK serializability. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private static class UnmodifiableMap implements Map, Serializable + { + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = -1034234728574286014L; + + /** + * The wrapped map. + * @serial the real map + */ + private final Map m; + + /** + * Cache the entry set. + */ + private transient Set entries; + + /** + * Cache the key set. + */ + private transient Set keys; + + /** + * Cache the value collection. + */ + private transient Collection values; + + /** + * Wrap a given map. + * @param m the map to wrap + * @throws NullPointerException if m is null + */ + UnmodifiableMap(Map m) { this.m = m; - this.sync = this; + if (m == null) + throw new NullPointerException(); } public void clear() { - synchronized(sync) - { - m.clear(); - } + throw new UnsupportedOperationException(); } + public boolean containsKey(Object key) { - synchronized(sync) - { - return m.containsKey(key); - } + return m.containsKey(key); } + public boolean containsValue(Object value) { - synchronized(sync) - { - return m.containsValue(value); - } + return m.containsValue(value); } - // This is one of the ickiest cases of nesting I've ever seen. It just - // means "return a SynchronizedSet, except that the iterator() method - // returns an SynchronizedIterator whos next() method returns a - // synchronized wrapper around its normal return value". public Set entrySet() { - synchronized(sync) + if (entries == null) + entries = new UnmodifiableEntrySet(m.entrySet()); + return entries; + } + + /** + * The implementation of {@link UnmodifiableMap#entrySet()}. This class + * name is required for compatibility with Sun's JDK serializability. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private static final class UnmodifiableEntrySet extends UnmodifiableSet + implements Serializable + { + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = 7854390611657943733L; + + /** + * Wrap a given set. + * @param s the set to wrap + */ + UnmodifiableEntrySet(Set s) + { + super(s); + } + + // The iterator must return unmodifiable map entries. + public Iterator iterator() { - return new SynchronizedSet(sync, m.entrySet()) + return new UnmodifiableIterator(c.iterator()) { - public Iterator iterator() - { - synchronized(SynchronizedMap.this.sync) + public Object next() + { + final Map.Entry e = (Map.Entry) super.next(); + return new Map.Entry() { - return new SynchronizedIterator(SynchronizedMap.this.sync, - c.iterator()) - { - public Object next() - { - synchronized(SynchronizedMap.this.sync) - { - final Map.Entry e = (Map.Entry) super.next(); - return new Map.Entry() - { - public Object getKey() - { - synchronized(SynchronizedMap.this.sync) - { - return e.getKey(); - } - } - public Object getValue() - { - synchronized(SynchronizedMap.this.sync) - { - return e.getValue(); - } - } - public Object setValue(Object value) - { - synchronized(SynchronizedMap.this.sync) - { - return e.setValue(value); - } - } - public int hashCode() - { - synchronized(SynchronizedMap.this.sync) - { - return e.hashCode(); - } - } - public boolean equals(Object o) - { - synchronized(SynchronizedMap.this.sync) - { - return e.equals(o); - } - } - }; - } - } - }; - } - } + public boolean equals(Object o) + { + return e.equals(o); + } + public Object getKey() + { + return e.getKey(); + } + public Object getValue() + { + return e.getValue(); + } + public int hashCode() + { + return e.hashCode(); + } + public Object setValue(Object value) + { + throw new UnsupportedOperationException(); + } + public String toString() + { + return e.toString(); + } + }; + } }; } - } + } // class UnmodifiableEntrySet + public boolean equals(Object o) { - synchronized(sync) - { - return m.equals(o); - } + return m.equals(o); } + public Object get(Object key) { - synchronized(sync) - { - return m.get(key); - } + return m.get(key); } + public Object put(Object key, Object value) { - synchronized(sync) - { - return m.put(key, value); - } + throw new UnsupportedOperationException(); } + public int hashCode() { - synchronized(sync) - { - return m.hashCode(); - } + return m.hashCode(); } + public boolean isEmpty() { - synchronized(sync) - { - return m.isEmpty(); - } + return m.isEmpty(); } + public Set keySet() { - synchronized(sync) - { - return new SynchronizedSet(sync, m.keySet()); - } + if (keys == null) + keys = new UnmodifiableSet(m.keySet()); + return keys; } - public void putAll(Map map) + + public void putAll(Map m) { - synchronized(sync) - { - m.putAll(map); - } + throw new UnsupportedOperationException(); } + public Object remove(Object o) { - synchronized(sync) - { - return m.remove(o); - } + throw new UnsupportedOperationException(); } public int size() { - synchronized(sync) - { - return m.size(); - } + return m.size(); } - public Collection values() + + public String toString() { - synchronized(sync) - { - return new SynchronizedCollection(sync, m.values()); - } + return m.toString(); } - public String toString() + + public Collection values() { - synchronized(sync) - { - return m.toString(); - } + if (values == null) + values = new UnmodifiableCollection(m.values()); + return values; } + } // class UnmodifiableMap + + /** + * Returns an unmodifiable view of the given set. This allows + * "read-only" access, although changes in the backing set show up + * in this view. Attempts to modify the set directly or via iterators + * will fail with {@link UnsupportedOperationException}. + * <p> + * + * The returned Set implements Serializable, but can only be serialized if + * the set it wraps is likewise Serializable. + * + * @param s the set to wrap + * @return a read-only view of the set + * @see Serializable + */ + public static Set unmodifiableSet(Set s) + { + return new UnmodifiableSet(s); } - private static class SynchronizedSortedMap extends SynchronizedMap - implements SortedMap + /** + * The implementation of {@link #unmodifiableSet(Set)}. This class + * name is required for compatibility with Sun's JDK serializability. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private static class UnmodifiableSet extends UnmodifiableCollection + implements Set { - // This is stored both here and in the superclass, to avoid excessive - // casting. - private SortedMap sm; + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = -9215047833775013803L; + + /** + * Wrap a given set. + * @param s the set to wrap + * @throws NullPointerException if s is null + */ + UnmodifiableSet(Set s) + { + super(s); + } - public SynchronizedSortedMap(Object sync, SortedMap sm) + public boolean equals(Object o) { - super(sync, sm); - this.sm = sm; + return c.equals(o); + } + + public int hashCode() + { + return c.hashCode(); } - public SynchronizedSortedMap(SortedMap sm) + } // class UnmodifiableSet + + /** + * Returns an unmodifiable view of the given sorted map. This allows + * "read-only" access, although changes in the backing map show up in this + * view. Attempts to modify the map directly, via subviews, via collection + * views, or iterators, will fail with {@link UnsupportedOperationException}. + * <p> + * + * The returned SortedMap implements Serializable, but can only be + * serialized if the map it wraps is likewise Serializable. + * + * @param m the map to wrap + * @return a read-only view of the map + * @see Serializable + */ + public static SortedMap unmodifiableSortedMap(SortedMap m) + { + return new UnmodifiableSortedMap(m); + } + + /** + * The implementation of {@link #unmodifiableSortedMap(SortedMap)}. This + * class name is required for compatibility with Sun's JDK serializability. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private static class UnmodifiableSortedMap extends UnmodifiableMap + implements SortedMap + { + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = -8806743815996713206L; + + /** + * The wrapped map; stored both here and in the superclass to avoid + * excessive casting. + * @serial the wrapped map + */ + private final SortedMap sm; + + /** + * Wrap a given map. + * @param sm the map to wrap + * @throws NullPointerException if sm is null + */ + UnmodifiableSortedMap(SortedMap sm) { super(sm); this.sm = sm; @@ -1827,36 +3350,114 @@ public class Collections public Comparator comparator() { - synchronized(sync) - { - return sm.comparator(); - } + return sm.comparator(); } + public Object firstKey() { - synchronized(sync) - { - return sm.firstKey(); - } + return sm.firstKey(); + } + + public SortedMap headMap(Object toKey) + { + return new UnmodifiableSortedMap(sm.headMap(toKey)); } + public Object lastKey() { - synchronized(sync) - { - return sm.lastKey(); - } + return sm.lastKey(); } - public SortedMap headMap(Object toKey) + + public SortedMap subMap(Object fromKey, Object toKey) { - return new SynchronizedSortedMap(sync, sm.headMap(toKey)); + return new UnmodifiableSortedMap(sm.subMap(fromKey, toKey)); } + public SortedMap tailMap(Object fromKey) { - return new SynchronizedSortedMap(sync, sm.tailMap(fromKey)); + return new UnmodifiableSortedMap(sm.tailMap(fromKey)); } - public SortedMap subMap(Object fromKey, Object toKey) + } // class UnmodifiableSortedMap + + /** + * Returns an unmodifiable view of the given sorted set. This allows + * "read-only" access, although changes in the backing set show up + * in this view. Attempts to modify the set directly, via subsets, or via + * iterators, will fail with {@link UnsupportedOperationException}. + * <p> + * + * The returns SortedSet implements Serializable, but can only be + * serialized if the set it wraps is likewise Serializable. + * + * @param s the set to wrap + * @return a read-only view of the set + * @see Serializable + */ + public static SortedSet unmodifiableSortedSet(SortedSet s) + { + return new UnmodifiableSortedSet(s); + } + + /** + * The implementation of {@link #synchronizedSortedMap(SortedMap)}. This + * class name is required for compatibility with Sun's JDK serializability. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private static class UnmodifiableSortedSet extends UnmodifiableSet + implements SortedSet + { + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = -4929149591599911165L; + + /** + * The wrapped set; stored both here and in the superclass to avoid + * excessive casting. + * @serial the wrapped set + */ + private SortedSet ss; + + /** + * Wrap a given set. + * @param ss the set to wrap + * @throws NullPointerException if ss is null + */ + UnmodifiableSortedSet(SortedSet ss) { - return new SynchronizedSortedMap(sync, sm.subMap(fromKey, toKey)); + super(ss); + this.ss = ss; } - } -} + + public Comparator comparator() + { + return ss.comparator(); + } + + public Object first() + { + return ss.first(); + } + + public SortedSet headSet(Object toElement) + { + return new UnmodifiableSortedSet(ss.headSet(toElement)); + } + + public Object last() + { + return ss.last(); + } + + public SortedSet subSet(Object fromElement, Object toElement) + { + return new UnmodifiableSortedSet(ss.subSet(fromElement, toElement)); + } + + public SortedSet tailSet(Object fromElement) + { + return new UnmodifiableSortedSet(ss.tailSet(fromElement)); + } + } // class UnmodifiableSortedSet +} // class Collections diff --git a/libjava/java/util/Dictionary.java b/libjava/java/util/Dictionary.java index 7530e083351..ef727c71c8b 100644 --- a/libjava/java/util/Dictionary.java +++ b/libjava/java/util/Dictionary.java @@ -1,6 +1,6 @@ /* Dictionary.java -- an abstract (and essentially worthless) class which is Hashtable's superclass - Copyright (C) 1998 Free Software Foundation, Inc. + Copyright (C) 1998, 2001 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -35,49 +35,88 @@ package java.util; * This is an abstract class which has really gone by the wayside. * People at Javasoft are probably embarrassed by it. At this point, * it might as well be an interface rather than a class, but it remains - * this poor, laugable skeleton for the sake of backwards compatibility. + * this poor, laughable skeleton for the sake of backwards compatibility. * At any rate, this was what came before the <pre>Map</pre> interface * in the Collections framework. * - * @author Jon Zeppieri + * @author Jon Zeppieri + * @author Eric Blake <ebb9@email.byu.edu> + * @see Map + * @see Hashtable + * @since 1.0 + * @status updated to 1.4 */ public abstract class Dictionary extends Object { - /** returns an Enumeration of the values in this Dictionary */ + /** + * Sole constructor (often called implicitly). + */ + public Dictionary() + { + } + + /** + * Returns an Enumeration of the values in this Dictionary. + * + * @return an Enumeration of the values + * @see #keys() + */ public abstract Enumeration elements(); /** - * returns the value associated with the supplied key, or null - * if no such value exists + * Returns the value associated with the supplied key, or null + * if no such value exists. Since Dictionaries are not allowed null keys + * or elements, a null result always means the key is not present. * - * @param key the key to use to fetch the value + * @param key the key to use to fetch the value + * @return the mapped value + * @throws NullPointerException if key is null + * @see #put(Object, Object) */ public abstract Object get(Object key); - /** returns true IFF there are no elements in this Dictionary (size() == 0) */ + /** + * Returns true when there are no elements in this Dictionary. + * + * @return <code>size() == 0</code> + */ public abstract boolean isEmpty(); - /** returns an Enumeration of the keys in this Dictionary */ + /** + * Returns an Enumeration of the keys in this Dictionary + * + * @return an Enumeration of the keys + * @see #elements() + */ public abstract Enumeration keys(); /** - * inserts a new value into this Dictionary, located by the - * supllied key; note: Dictionary's subclasses (all 1 of them) - * do not support null keys or values (I can only assume this - * would have been more general) + * Inserts a new value into this Dictionary, located by the + * supplied key. Dictionary does not support null keys or values, so + * a null return can safely be interpreted as adding a new key. * - * @param key the key which locates the value - * @param value the value to put into the Dictionary + * @param key the key which locates the value + * @param value the value to put into the Dictionary + * @return the previous value of the key, or null if there was none + * @throws NullPointerException if key or value is null + * @see #get(Object) */ public abstract Object put(Object key, Object value); /** - * removes fro the Dictionary the value located by the given key + * Removes from the Dictionary the value located by the given key. A null + * return safely means that the key was not mapped in the Dictionary. * - * @param key the key used to locate the value to be removed + * @param key the key used to locate the value to be removed + * @return the value associated with the removed key + * @throws NullPointerException if key is null */ public abstract Object remove(Object key); - /** returns the number of values currently in this Dictionary */ + /** + * Returns the number of values currently in this Dictionary. + * + * @return the number of keys in the Dictionary + */ public abstract int size(); } diff --git a/libjava/java/util/HashMap.java b/libjava/java/util/HashMap.java index 3b351058a95..dcf7e7e340b 100644 --- a/libjava/java/util/HashMap.java +++ b/libjava/java/util/HashMap.java @@ -53,14 +53,16 @@ import java.io.ObjectOutputStream; * <p> * * Under ideal circumstances (no collisions), HashMap offers O(1) - * performance on most operations (<pre>containsValue()</pre> is, + * performance on most operations (<code>containsValue()</code> is, * of course, O(n)). In the worst case (all keys map to the same * hash code -- very unlikely), most operations are O(n). * <p> * * HashMap is part of the JDK1.2 Collections API. It differs from * Hashtable in that it accepts the null key and null values, and it - * does not support "Enumeration views." + * does not support "Enumeration views." Also, it is not synchronized; + * if you plan to use it in multiple threads, consider using:<br> + * <code>Map m = Collections.synchronizedMap(new HashMap(...));</code> * <p> * * The iterators are <i>fail-fast</i>, meaning that any structural @@ -81,6 +83,7 @@ import java.io.ObjectOutputStream; * @see IdentityHashMap * @see Hashtable * @since 1.2 + * @status updated to 1.4 */ public class HashMap extends AbstractMap implements Map, Cloneable, Serializable @@ -88,19 +91,16 @@ public class HashMap extends AbstractMap /** * Default number of buckets. This is the value the JDK 1.3 uses. Some * early documentation specified this value as 101. That is incorrect. + * Package visible for use by HashSet. */ static final int DEFAULT_CAPACITY = 11; /** * The default load factor; this is explicitly specified by the spec. + * Package visible for use by HashSet. */ static final float DEFAULT_LOAD_FACTOR = 0.75f; - /** "enum" of iterator types. */ - static final int KEYS = 0, - VALUES = 1, - ENTRIES = 2; - /** * Compatible with JDK 1.2. */ @@ -108,41 +108,54 @@ public class HashMap extends AbstractMap /** * The rounded product of the capacity and the load factor; when the number - * of elements exceeds the threshold, the HashMap calls <pre>rehash()</pre>. - * @serial + * of elements exceeds the threshold, the HashMap calls + * <code>rehash()</code>. + * @serial the threshold for rehashing */ - int threshold; + private int threshold; /** * Load factor of this HashMap: used in computing the threshold. - * @serial + * Package visible for use by HashSet. + * @serial the load factor */ final float loadFactor; /** * Array containing the actual key-value mappings. + * Package visible for use by nested and subclasses. */ transient HashEntry[] buckets; /** * Counts the number of modifications this HashMap has undergone, used * by Iterators to know when to throw ConcurrentModificationExceptions. + * Package visible for use by nested and subclasses. */ transient int modCount; /** * The size of this HashMap: denotes the number of key-value pairs. + * Package visible for use by nested and subclasses. */ transient int size; /** + * The cache for {@link #entrySet()}. + */ + private transient Set entries; + + /** * Class to represent an entry in the hash table. Holds a single key-value - * pair. This is extended again in LinkedHashMap. See {@link clone()} - * for why this must be Cloneable. + * pair. Package visible for use by subclass. + * + * @author Eric Blake <ebb9@email.byu.edu> */ - static class HashEntry extends BasicMapEntry implements Cloneable + static class HashEntry extends BasicMapEntry { - /** The next entry in the linked list. */ + /** + * The next entry in the linked list. Package visible for use by subclass. + */ HashEntry next; /** @@ -158,7 +171,8 @@ public class HashMap extends AbstractMap /** * Called when this entry is removed from the map. This version simply * returns the value, but in LinkedHashMap, it must also do bookkeeping. - * @return the value of this key as it is removed. + * + * @return the value of this key as it is removed */ Object cleanup() { @@ -182,9 +196,8 @@ public class HashMap extends AbstractMap * * Every element in Map m will be put into this new HashMap. * - * @param m a Map whose key / value pairs will be put into - * the new HashMap. <b>NOTE: key / value pairs - * are not cloned in this constructor.</b> + * @param m a Map whose key / value pairs will be put into the new HashMap. + * <b>NOTE: key / value pairs are not cloned in this constructor.</b> * @throws NullPointerException if m is null */ public HashMap(Map m) @@ -197,8 +210,8 @@ public class HashMap extends AbstractMap * Construct a new HashMap with a specific inital capacity and * default load factor of 0.75. * - * @param initialCapacity the initial capacity of this HashMap (>=0) - * @throws IllegalArgumentException if (initialCapacity < 0) + * @param initialCapacity the initial capacity of this HashMap (>=0) + * @throws IllegalArgumentException if (initialCapacity < 0) */ public HashMap(int initialCapacity) { @@ -208,10 +221,10 @@ public class HashMap extends AbstractMap /** * Construct a new HashMap with a specific inital capacity and load factor. * - * @param initialCapacity the initial capacity (>=0) - * @param loadFactor the load factor (>0, not NaN) - * @throws IllegalArgumentException if (initialCapacity < 0) || - * ! (loadFactor > 0.0) + * @param initialCapacity the initial capacity (>=0) + * @param loadFactor the load factor (> 0, not NaN) + * @throws IllegalArgumentException if (initialCapacity < 0) || + * ! (loadFactor > 0.0) */ public HashMap(int initialCapacity, float loadFactor) { @@ -229,7 +242,8 @@ public class HashMap extends AbstractMap } /** - * Returns the number of kay-value mappings currently in this Map + * Returns the number of kay-value mappings currently in this Map. + * * @return the size */ public int size() @@ -238,7 +252,8 @@ public class HashMap extends AbstractMap } /** - * Returns true if there are no key-value mappings currently in this Map + * Returns true if there are no key-value mappings currently in this Map. + * * @return <code>size() == 0</code> */ public boolean isEmpty() @@ -247,29 +262,31 @@ public class HashMap extends AbstractMap } /** - * Returns true if this HashMap contains a value <pre>o</pre>, such that - * <pre>o.equals(value)</pre>. + * Return the value in this HashMap associated with the supplied key, + * or <code>null</code> if the key maps to nothing. NOTE: Since the value + * could also be null, you must use containsKey to see if this key + * actually maps to something. * - * @param value the value to search for in this HashMap - * @return true if at least one key maps to the value + * @param key the key for which to fetch an associated value + * @return what the key maps to, if present + * @see #put(Object, Object) + * @see #containsKey(Object) */ - public boolean containsValue(Object value) + public Object get(Object key) { - for (int i = buckets.length - 1; i >= 0; i--) + int idx = hash(key); + HashEntry e = buckets[idx]; + while (e != null) { - HashEntry e = buckets[i]; - while (e != null) - { - if (value == null ? e.value == null : value.equals(e.value)) - return true; - e = e.next; - } + if (equals(key, e.key)) + return e.value; + e = e.next; } - return false; + return null; } /** - * Returns true if the supplied object <pre>equals()</pre> a key + * Returns true if the supplied object <code>equals()</code> a key * in this HashMap. * * @param key the key to search for in this HashMap @@ -282,7 +299,7 @@ public class HashMap extends AbstractMap HashEntry e = buckets[idx]; while (e != null) { - if (key == null ? e.key == null : key.equals(e.key)) + if (equals(key, e.key)) return true; e = e.next; } @@ -290,30 +307,6 @@ public class HashMap extends AbstractMap } /** - * Return the value in this HashMap associated with the supplied key, - * or <pre>null</pre> if the key maps to nothing. NOTE: Since the value - * could also be null, you must use containsKey to see if this key - * actually maps to something. - * - * @param key the key for which to fetch an associated value - * @return what the key maps to, if present - * @see #put(Object, Object) - * @see #containsKey(Object) - */ - public Object get(Object key) - { - int idx = hash(key); - HashEntry e = buckets[idx]; - while (e != null) - { - if (key == null ? e.key == null : key.equals(e.key)) - return e.value; - e = e.next; - } - return null; - } - - /** * Puts the supplied value into the Map, mapped by the supplied key. * The value may be retrieved by any object which <code>equals()</code> * this key. NOTE: Since the prior value could also be null, you must @@ -328,13 +321,12 @@ public class HashMap extends AbstractMap */ public Object put(Object key, Object value) { - modCount++; int idx = hash(key); HashEntry e = buckets[idx]; while (e != null) { - if (key == null ? e.key == null : key.equals(e.key)) + if (equals(key, e.key)) // Must use this method for necessary bookkeeping in LinkedHashMap. return e.setValue(value); else @@ -342,6 +334,7 @@ public class HashMap extends AbstractMap } // At this point, we know we need to add a new entry. + modCount++; if (++size > threshold) { rehash(); @@ -355,27 +348,36 @@ public class HashMap extends AbstractMap } /** - * Helper method for put, that creates and adds a new Entry. This is - * overridden in LinkedHashMap for bookkeeping purposes. + * Copies all elements of the given map into this hashtable. If this table + * already has a mapping for a key, the new mapping replaces the current + * one. * - * @param key the key of the new Entry - * @param value the value - * @param idx the index in buckets where the new Entry belongs - * @param callRemove Whether to call the removeEldestEntry method. - * @see #put(Object, Object) + * @param m the map to be hashed into this */ - void addEntry(Object key, Object value, int idx, boolean callRemove) + public void putAll(Map m) { - HashEntry e = new HashEntry(key, value); + Iterator itr = m.entrySet().iterator(); - e.next = buckets[idx]; - buckets[idx] = e; + for (int msize = m.size(); msize > 0; msize--) + { + Map.Entry e = (Map.Entry) itr.next(); + // Optimize in case the Entry is one of our own. + if (e instanceof BasicMapEntry) + { + BasicMapEntry entry = (BasicMapEntry) e; + put(entry.key, entry.value); + } + else + { + put(e.getKey(), e.getValue()); + } + } } - + /** * Removes from the HashMap and returns the value which is mapped by the * supplied key. If the key maps to nothing, then the HashMap remains - * unchanged, and <pre>null</pre> is returned. NOTE: Since the value + * unchanged, and <code>null</code> is returned. NOTE: Since the value * could also be null, you must use containsKey to see if you are * actually removing a mapping. * @@ -384,15 +386,15 @@ public class HashMap extends AbstractMap */ public Object remove(Object key) { - modCount++; int idx = hash(key); HashEntry e = buckets[idx]; HashEntry last = null; while (e != null) { - if (key == null ? e.key == null : key.equals(e.key)) + if (equals(key, e.key)) { + modCount++; if (last == null) buckets[idx] = e.next; else @@ -408,40 +410,39 @@ public class HashMap extends AbstractMap } /** - * Copies all elements of the given map into this hashtable. If this table - * already has a mapping for a key, the new mapping replaces the current - * one. - * - * @param m the map to be hashed into this + * Clears the Map so it has no keys. This is O(1). */ - public void putAll(Map m) + public void clear() { - Iterator itr = m.entrySet().iterator(); - - for (int msize = m.size(); msize > 0; msize--) + if (size != 0) { - Map.Entry e = (Map.Entry) itr.next(); - // Optimize in case the Entry is one of our own. - if (e instanceof BasicMapEntry) - { - BasicMapEntry entry = (BasicMapEntry) e; - put(entry.key, entry.value); - } - else - { - put(e.getKey(), e.getValue()); - } + modCount++; + Arrays.fill(buckets, null); + size = 0; } } - + /** - * Clears the Map so it has no keys. This is O(1). + * Returns true if this HashMap contains a value <code>o</code>, such that + * <code>o.equals(value)</code>. + * + * @param value the value to search for in this HashMap + * @return true if at least one key maps to the value + * @see containsKey(Object) */ - public void clear() + public boolean containsValue(Object value) { - modCount++; - Arrays.fill(buckets, null); - size = 0; + for (int i = buckets.length - 1; i >= 0; i--) + { + HashEntry e = buckets[i]; + while (e != null) + { + if (equals(value, e.value)) + return true; + e = e.next; + } + } + return false; } /** @@ -463,6 +464,8 @@ public class HashMap extends AbstractMap } copy.buckets = new HashEntry[buckets.length]; copy.putAllInternal(this); + // Clear the entry cache. AbstractMap.clone() does the others. + copy.entries = null; return copy; } @@ -477,41 +480,43 @@ public class HashMap extends AbstractMap */ public Set keySet() { - // Create an AbstractSet with custom implementations of those methods that - // can be overridden easily and efficiently. - return new AbstractSet() - { - public int size() - { - return size; - } - - public Iterator iterator() - { - // Cannot create the iterator directly, because of LinkedHashMap. - return HashMap.this.iterator(KEYS); - } - - public void clear() - { - HashMap.this.clear(); - } - - public boolean contains(Object o) - { - return HashMap.this.containsKey(o); - } - - public boolean remove(Object o) + if (keys == null) + // Create an AbstractSet with custom implementations of those methods + // that can be overridden easily and efficiently. + keys = new AbstractSet() { - // Test against the size of the HashMap to determine if anything - // really got removed. This is necessary because the return value of - // HashMap.remove() is ambiguous in the null case. - int oldsize = size; - HashMap.this.remove(o); - return (oldsize != size); - } - }; + public int size() + { + return size; + } + + public Iterator iterator() + { + // Cannot create the iterator directly, because of LinkedHashMap. + return HashMap.this.iterator(KEYS); + } + + public void clear() + { + HashMap.this.clear(); + } + + public boolean contains(Object o) + { + return containsKey(o); + } + + public boolean remove(Object o) + { + // Test against the size of the HashMap to determine if anything + // really got removed. This is neccessary because the return value + // of HashMap.remove() is ambiguous in the null case. + int oldsize = size; + HashMap.this.remove(o); + return oldsize != size; + } + }; + return keys; } /** @@ -526,33 +531,34 @@ public class HashMap extends AbstractMap */ public Collection values() { - // We don't bother overriding many of the optional methods, as doing so - // wouldn't provide any significant performance advantage. - return new AbstractCollection() - { - public int size() - { - return size; - } - - public Iterator iterator() - { - // Cannot create the iterator directly, because of LinkedHashMap. - return HashMap.this.iterator(VALUES); - } - - public void clear() + if (values == null) + // We don't bother overriding many of the optional methods, as doing so + // wouldn't provide any significant performance advantage. + values = new AbstractCollection() { - HashMap.this.clear(); - } - }; + public int size() + { + return size; + } + + public Iterator iterator() + { + // Cannot create the iterator directly, because of LinkedHashMap. + return HashMap.this.iterator(VALUES); + } + + public void clear() + { + HashMap.this.clear(); + } + }; + return values; } /** * Returns a "set view" of this HashMap's entries. The set is backed by * the HashMap, so changes in one show up in the other. The set supports - * element removal, but not element addition. - * <p> + * element removal, but not element addition.<p> * * Note that the iterators for all three views, from keySet(), entrySet(), * and values(), traverse the HashMap in the same sequence. @@ -564,53 +570,62 @@ public class HashMap extends AbstractMap */ public Set entrySet() { - // Create an AbstractSet with custom implementations of those methods that - // can be overridden easily and efficiently. - return new AbstractSet() - { - public int size() - { - return size; - } - - public Iterator iterator() - { - // Cannot create the iterator directly, because of LinkedHashMap. - return HashMap.this.iterator(ENTRIES); - } - - public void clear() - { - HashMap.this.clear(); - } - - public boolean contains(Object o) - { - return getEntry(o) != null; - } - - public boolean remove(Object o) + if (entries == null) + // Create an AbstractSet with custom implementations of those methods + // that can be overridden easily and efficiently. + entries = new AbstractSet() { - HashEntry e = getEntry(o); - if (e != null) - { - HashMap.this.remove(e.key); - return true; - } - return false; - } - }; + public int size() + { + return size; + } + + public Iterator iterator() + { + // Cannot create the iterator directly, because of LinkedHashMap. + return HashMap.this.iterator(ENTRIES); + } + + public void clear() + { + HashMap.this.clear(); + } + + public boolean contains(Object o) + { + return getEntry(o) != null; + } + + public boolean remove(Object o) + { + HashEntry e = getEntry(o); + if (e != null) + { + HashMap.this.remove(e.key); + return true; + } + return false; + } + }; + return entries; } - /** Helper method that returns an index in the buckets array for `key; - * based on its hashCode(). + /** + * Helper method for put, that creates and adds a new Entry. This is + * overridden in LinkedHashMap for bookkeeping purposes. * - * @param key the key - * @return the bucket number + * @param key the key of the new Entry + * @param value the value + * @param idx the index in buckets where the new Entry belongs + * @param callRemove whether to call the removeEldestEntry method + * @see #put(Object, Object) */ - int hash(Object key) + void addEntry(Object key, Object value, int idx, boolean callRemove) { - return (key == null) ? 0 : Math.abs(key.hashCode() % buckets.length); + HashEntry e = new HashEntry(key, value); + + e.next = buckets[idx]; + buckets[idx] = e; } /** @@ -638,6 +653,52 @@ public class HashMap extends AbstractMap } /** + * Helper method that returns an index in the buckets array for `key' + * based on its hashCode(). Package visible for use by subclasses. + * + * @param key the key + * @return the bucket number + */ + final int hash(Object key) + { + return key == null ? 0 : Math.abs(key.hashCode() % buckets.length); + } + + /** + * Generates a parameterized iterator. Must be overrideable, since + * LinkedHashMap iterates in a different order. + * + * @param type {@link #KEYS}, {@link #VALUES}, or {@link #ENTRIES} + * @return the appropriate iterator + */ + Iterator iterator(int type) + { + return new HashIterator(type); + } + + /** + * A simplified, more efficient internal implementation of putAll(). The + * Map constructor and clone() should not call putAll or put, in order to + * be compatible with the JDK implementation with respect to subclasses. + * + * @param m the map to initialize this from + */ + void putAllInternal(Map m) + { + Iterator itr = m.entrySet().iterator(); + int msize = m.size(); + this.size = msize; + + for (; msize > 0; msize--) + { + Map.Entry e = (Map.Entry) itr.next(); + Object key = e.getKey(); + int idx = hash(key); + addEntry(key, e.getValue(), idx, false); + } + } + + /** * Increases the size of the HashMap and rehashes all keys to new array * indices; this is called when the addition of a new value would cause * size() > threshold. Note that the existing Entry objects are reused in @@ -682,35 +743,6 @@ public class HashMap extends AbstractMap } /** - * Generates a parameterized iterator. Must be overrideable, since - * LinkedHashMap iterates in a different order. - * @param type {@link #KEYS}, {@link #VALUES}, or {@link #ENTRIES} - * @return the appropriate iterator - */ - Iterator iterator(int type) - { - return new HashIterator(type); - } - - /** - * A simplified, more efficient internal implementation of putAll(). The - * Map constructor and clone() should not call putAll or put, in order to - * be compatible with the JDK implementation with respect to subclasses. - */ - void putAllInternal(Map m) - { - Iterator itr = m.entrySet().iterator(); - - for (int msize = m.size(); msize > 0; msize--) - { - Map.Entry e = (Map.Entry) itr.next(); - Object key = e.getKey(); - int idx = hash(key); - addEntry(key, e.getValue(), idx, false); - } - } - - /** * Serializes this object to the given stream. * * @param s the stream to write to @@ -757,9 +789,6 @@ public class HashMap extends AbstractMap // Read and use capacity. buckets = new HashEntry[s.readInt()]; int len = s.readInt(); - // Already happens automatically. - // size = 0; - // modCount = 0; // Read and use key/value pairs. for ( ; len > 0; len--) @@ -773,29 +802,29 @@ public class HashMap extends AbstractMap * * @author Jon Zeppieri */ - class HashIterator implements Iterator + private final class HashIterator implements Iterator { /** * The type of this Iterator: {@link #KEYS}, {@link #VALUES}, * or {@link #ENTRIES}. */ - final int type; + private final int type; /** * The number of modifications to the backing HashMap that we know about. */ - int knownMod = modCount; + private int knownMod = modCount; /** The number of elements remaining to be returned by next(). */ - int count = size; + private int count = size; /** Current index in the physical hash table. */ - int idx = buckets.length; + private int idx = buckets.length; /** The last Entry returned by a next() call. */ - HashEntry last; + private HashEntry last; /** * The next entry that should be returned by next(). It is set to something * if we're iterating through a bucket that contains multiple linked * entries. It is null if next() needs to find a new bucket. */ - HashEntry next; + private HashEntry next; /** * Construct a new HashIterator with the supplied type. @@ -840,14 +869,14 @@ public class HashMap extends AbstractMap last = e; if (type == VALUES) return e.value; - else if (type == KEYS) + if (type == KEYS) return e.key; return e; } /** * Removes from the backing HashMap the last element which was fetched - * with the <pre>next()</pre> method. + * with the <code>next()</code> method. * @throws ConcurrentModificationException if the HashMap was modified * @throws IllegalStateException if called when there is no last element */ @@ -859,8 +888,8 @@ public class HashMap extends AbstractMap throw new IllegalStateException(); HashMap.this.remove(last.key); - knownMod++; last = null; + knownMod++; } } } diff --git a/libjava/java/util/HashSet.java b/libjava/java/util/HashSet.java index 2b5c31dc7fc..9d9c3315263 100644 --- a/libjava/java/util/HashSet.java +++ b/libjava/java/util/HashSet.java @@ -1,5 +1,5 @@ -/* HashSet.java -- a class providing a HashMap-backet Set - Copyright (C) 1998, 1999 Free Software Foundation, Inc. +/* HashSet.java -- a class providing a HashMap-backed Set + Copyright (C) 1998, 1999, 2001 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -33,87 +33,115 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; /** - * This class provides a HashMap-backed implementation of the - * Set interface. - * - * Each element in the Set is a key in the backing HashMap; each key - * maps to a static token, denoting that the key does, in fact, exist. + * This class provides a HashMap-backed implementation of the Set interface. + * <p> * * Most operations are O(1), assuming no hash collisions. In the worst - * case (where all hases collide), operations are O(n). + * case (where all hashes collide), operations are O(n). Setting the + * initial capacity too low will force many resizing operations, but + * setting the initial capacity too high (or loadfactor too low) leads + * to wasted memory and slower iteration. + * <p> + * + * HashSet accepts the null key and null values. It is not synchronized, + * so if you need multi-threaded access, consider using:<br> + * <code>Set s = Collections.synchronizedSet(new HashSet(...));</code> + * <p> * - * HashSet is a part of the JDK1.2 Collections API. + * The iterators are <i>fail-fast</i>, meaning that any structural + * modification, except for <code>remove()</code> called on the iterator + * itself, cause the iterator to throw a + * {@link ConcurrentModificationException} rather than exhibit + * non-deterministic behavior. * - * @author Jon Zeppieri + * @author Jon Zeppieri + * @author Eric Blake <ebb9@email.byu.edu> + * @see Collection + * @see Set + * @see TreeSet + * @see Collections#synchronizedSet(Set) + * @see HashMap + * @see LinkedHashSet + * @since 1.2 + * @status updated to 1.4 */ public class HashSet extends AbstractSet implements Set, Cloneable, Serializable { - /** the HashMap which backs this Set */ - transient HashMap map; - static final long serialVersionUID = -5024744406713321676L; + /** + * Compatible with JDK 1.2. + */ + private static final long serialVersionUID = -5024744406713321676L; /** - * construct a new, empty HashSet whose backing HashMap has the default - * capacity and loadFacor + * The HashMap which backs this Set. + */ + private transient HashMap map; + + /** + * Construct a new, empty HashSet whose backing HashMap has the default + * capacity (11) and loadFacor (0.75). */ public HashSet() { - map = new HashMap(); + this(HashMap.DEFAULT_CAPACITY, HashMap.DEFAULT_LOAD_FACTOR); } /** - * construct a new, empty HashSet whose backing HashMap has the supplied - * capacity and the default load factor + * Construct a new, empty HashSet whose backing HashMap has the supplied + * capacity and the default load factor (0.75). * - * @param initialCapacity the initial capacity of the backing - * HashMap + * @param initialCapacity the initial capacity of the backing HashMap + * @throws IllegalArgumentException if the capacity is negative */ public HashSet(int initialCapacity) { - map = new HashMap(initialCapacity); + this(initialCapacity, HashMap.DEFAULT_LOAD_FACTOR); } /** - * construct a new, empty HashSet whose backing HashMap has the supplied - * capacity and load factor + * Construct a new, empty HashSet whose backing HashMap has the supplied + * capacity and load factor. * - * @param initialCapacity the initial capacity of the backing - * HashMap - * @param loadFactor the load factor of the backing HashMap + * @param initialCapacity the initial capacity of the backing HashMap + * @param loadFactor the load factor of the backing HashMap + * @throws IllegalArgumentException if either argument is negative, or + * if loadFactor is POSITIVE_INFINITY or NaN */ public HashSet(int initialCapacity, float loadFactor) { - map = new HashMap(initialCapacity, loadFactor); + map = init(initialCapacity, loadFactor); } /** - * construct a new HashSet with the same elements as are in the supplied - * collection (eliminating any duplicates, of course; the backing HashMap - * will have the default capacity and load factor + * Construct a new HashSet with the same elements as are in the supplied + * collection (eliminating any duplicates, of course). The backing storage + * has twice the size of the collection, or the default size of 11, + * whichever is greater; and the default load factor (0.75). * - * @param c a collection containing the elements with - * which this set will be initialized + * @param c a collection of initial set elements + * @throws NullPointerException if c is null */ public HashSet(Collection c) { - map = new HashMap(); + this(Math.max(2 * c.size(), HashMap.DEFAULT_CAPACITY)); addAll(c); } /** - * adds the given Object to the set if it is not already in the Set, - * returns true if teh element was added, false otherwise + * Adds the given Object to the set if it is not already in the Set. + * This set permits a null element. * - * @param o the Object to add to this Set + * @param o the Object to add to this Set + * @return true if the set did not already contain o */ public boolean add(Object o) { - return (map.put(o, Boolean.TRUE) == null); + return map.put(o, "") == null; } /** - * empties this Set of all elements; this is a fast operation [O(1)] + * Empties this Set of all elements; this takes constant time. */ public void clear() { @@ -121,53 +149,67 @@ public class HashSet extends AbstractSet } /** - * returns a shallow copy of this Set (the Set itself is cloned; its - * elements are not) + * Returns a shallow copy of this Set. The Set itself is cloned; its + * elements are not. + * + * @return a shallow clone of the set */ public Object clone() { HashSet copy = null; try { - copy = (HashSet) super.clone(); + copy = (HashSet) super.clone(); } catch (CloneNotSupportedException x) { + // Impossible to get here. } copy.map = (HashMap) map.clone(); return copy; } /** - * returns true if the supplied element is in this Set, false otherwise + * Returns true if the supplied element is in this Set. * - * @param o the Object whose presence in this Set we are testing for + * @param o the Object to look for + * @return true if it is in the set */ public boolean contains(Object o) { return map.containsKey(o); } - /** - * returns true if this set has no elements in it (size() == 0) + /** + * Returns true if this set has no elements in it. + * + * @return <code>size() == 0</code>. */ public boolean isEmpty() { - return map.isEmpty(); + return map.size == 0; } /** - * returns an Iterator over the elements of this Set; the Iterator allows - * removal of elements + * Returns an Iterator over the elements of this Set, which visits the + * elements in no particular order. For this class, the Iterator allows + * removal of elements. The iterator is fail-fast, and will throw a + * ConcurrentModificationException if the set is modified externally. + * + * @return a set iterator + * @see ConcurrentModificationException */ public Iterator iterator() { - return map.keySet().iterator(); + // Avoid creating intermediate keySet() object by using non-public API. + return map.iterator(HashMap.KEYS); } /** - * removes the supplied Object from this Set if it is in the Set; returns - * true if an element was removed, false otherwise + * Removes the supplied Object from this Set if it is in the Set. + * + * @param o the object to remove + * @return true if an element was removed */ public boolean remove(Object o) { @@ -175,18 +217,42 @@ public class HashSet extends AbstractSet } /** - * returns the number of elements in this Set + * Returns the number of elements in this Set (its cardinality). + * + * @return the size of the set */ public int size() { - return map.size(); + return map.size; } - /** Serialize this Object in a manner which is binary-compatible with the - * JDK */ + /** + * Helper method which initializes the backing Map. Overridden by + * LinkedHashSet for correct semantics. + * + * @param capacity the initial capacity + * @param load the initial load factor + * @return the backing HashMap + */ + HashMap init(int capacity, float load) + { + return new HashMap(capacity, load); + } + + /** + * Serializes this object to the given stream. + * + * @param s the stream to write to + * @throws IOException if the underlying stream fails + * @serialData the <i>capacity</i> (int) and <i>loadFactor</i> (float) + * of the backing store, followed by the set size (int), + * then a listing of its elements (Object) in no order + */ private void writeObject(ObjectOutputStream s) throws IOException { - Iterator it = iterator(); + s.defaultWriteObject(); + // Avoid creating intermediate keySet() object by using non-public API. + Iterator it = map.iterator(HashMap.KEYS); s.writeInt(map.buckets.length); s.writeFloat(map.loadFactor); s.writeInt(map.size); @@ -194,25 +260,23 @@ public class HashSet extends AbstractSet s.writeObject(it.next()); } - /** Deserialize this Object in a manner which is binary-compatible with - * the JDK */ - private void readObject(ObjectInputStream s) throws IOException, - ClassNotFoundException + /** + * Deserializes this object from the given stream. + * + * @param s the stream to read from + * @throws ClassNotFoundException if the underlying stream fails + * @throws IOException if the underlying stream fails + * @serialData the <i>capacity</i> (int) and <i>loadFactor</i> (float) + * of the backing store, followed by the set size (int), + * then a listing of its elements (Object) in no order + */ + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException { - int i, size, capacity; - float loadFactor; - Object element; - - capacity = s.readInt(); - loadFactor = s.readFloat(); - size = s.readInt(); + s.defaultReadObject(); - map = new HashMap(capacity, loadFactor); - - for (i = 0; i < size; i++) - { - element = s.readObject(); - map.put(element, Boolean.TRUE); - } + map = init(s.readInt(), s.readFloat()); + for (int size = s.readInt(); size > 0; size--) + map.put(s.readObject(), ""); } } diff --git a/libjava/java/util/Hashtable.java b/libjava/java/util/Hashtable.java index 2a90244a4d2..01332698c6f 100644 --- a/libjava/java/util/Hashtable.java +++ b/libjava/java/util/Hashtable.java @@ -66,7 +66,9 @@ import java.io.ObjectOutputStream; * Unlike HashMap, Hashtable does not accept `null' as a key value. Also, * all accesses are synchronized: in a single thread environment, this is * expensive, but in a multi-thread environment, this saves you the effort - * of extra synchronization. + * of extra synchronization. However, the old-style enumerators are not + * synchronized, because they can lead to unspecified behavior even if + * they were synchronized. You have been warned. * <p> * * The iterators are <i>fail-fast</i>, meaning that any structural @@ -84,6 +86,7 @@ import java.io.ObjectOutputStream; * @see IdentityHashMap * @see LinkedHashMap * @since 1.0 + * @status updated to 1.4 */ public class Hashtable extends Dictionary implements Map, Cloneable, Serializable @@ -93,6 +96,12 @@ public class Hashtable extends Dictionary */ private static final int DEFAULT_CAPACITY = 11; + /** An "enum" of iterator types. */ + // Package visible for use by nested classes. + static final int KEYS = 0, + VALUES = 1, + ENTRIES = 2; + /** * The default load factor; this is explicitly specified by the spec. */ @@ -106,39 +115,57 @@ public class Hashtable extends Dictionary /** * The rounded product of the capacity and the load factor; when the number * of elements exceeds the threshold, the Hashtable calls - * <pre>rehash()</pre>. + * <code>rehash()</code>. * @serial */ - int threshold; + private int threshold; /** * Load factor of this Hashtable: used in computing the threshold. * @serial */ - final float loadFactor; + private final float loadFactor; /** * Array containing the actual key-value mappings. */ + // Package visible for use by nested classes. transient HashEntry[] buckets; /** * Counts the number of modifications this Hashtable has undergone, used * by Iterators to know when to throw ConcurrentModificationExceptions. */ + // Package visible for use by nested classes. transient int modCount; /** * The size of this Hashtable: denotes the number of key-value pairs. */ + // Package visible for use by nested classes. transient int size; /** + * The cache for {@link #keySet()}. + */ + private transient Set keys; + + /** + * The cache for {@link #values()}. + */ + private transient Collection values; + + /** + * The cache for {@link #entrySet()}. + */ + private transient Set entries; + + /** * Class to represent an entry in the hash table. Holds a single key-value * pair. A Hashtable Entry is identical to a HashMap Entry, except that * `null' is not allowed for keys and values. */ - static class HashEntry extends BasicMapEntry + private static final class HashEntry extends BasicMapEntry { /** The next entry in the linked list. */ HashEntry next; @@ -159,7 +186,7 @@ public class Hashtable extends Dictionary * @return the prior value * @throws NullPointerException if <code>newVal</code> is null */ - public final Object setValue(Object newVal) + public Object setValue(Object newVal) { if (newVal == null) throw new NullPointerException(); @@ -193,15 +220,15 @@ public class Hashtable extends Dictionary public Hashtable(Map m) { this(Math.max(m.size() * 2, DEFAULT_CAPACITY), DEFAULT_LOAD_FACTOR); - putAll(m); + putAllInternal(m); } /** * Construct a new Hashtable with a specific inital capacity and * default load factor of 0.75. * - * @param initialCapacity the initial capacity of this Hashtable (>=0) - * @throws IllegalArgumentException if (initialCapacity < 0) + * @param initialCapacity the initial capacity of this Hashtable (>= 0) + * @throws IllegalArgumentException if (initialCapacity < 0) */ public Hashtable(int initialCapacity) { @@ -212,10 +239,10 @@ public class Hashtable extends Dictionary * Construct a new Hashtable with a specific initial capacity and * load factor. * - * @param initialCapacity the initial capacity (>=0) - * @param loadFactor the load factor (>0, not NaN) - * @throws IllegalArgumentException if (initialCapacity < 0) || - * ! (loadFactor > 0.0) + * @param initialCapacity the initial capacity (>= 0) + * @param loadFactor the load factor (> 0, not NaN) + * @throws IllegalArgumentException if (initialCapacity < 0) || + * ! (loadFactor > 0.0) */ public Hashtable(int initialCapacity, float loadFactor) { @@ -251,30 +278,36 @@ public class Hashtable extends Dictionary } /** - * Return an enumeration of the keys of this table. + * Return an enumeration of the keys of this table. There's no point + * in synchronizing this, as you have already been warned that the + * enumeration is not specified to be thread-safe. + * * @return the keys * @see #elements() * @see #keySet() */ - public synchronized Enumeration keys() + public Enumeration keys() { - return new Enumerator(Enumerator.KEYS); + return new Enumerator(KEYS); } /** - * Return an enumeration of the values of this table. + * Return an enumeration of the values of this table. There's no point + * in synchronizing this, as you have already been warned that the + * enumeration is not specified to be thread-safe. + * * @return the values * @see #keys() * @see #values() */ - public synchronized Enumeration elements() + public Enumeration elements() { - return new Enumerator(Enumerator.VALUES); + return new Enumerator(VALUES); } /** - * Returns true if this Hashtable contains a value <pre>o</pre>, - * such that <pre>o.equals(value)</pre>. This is the same as + * Returns true if this Hashtable contains a value <code>o</code>, + * such that <code>o.equals(value)</code>. This is the same as * <code>containsValue()</code>, and is O(n). * <p> * @@ -284,48 +317,46 @@ public class Hashtable extends Dictionary * * @param value the value to search for in this Hashtable * @return true if at least one key maps to the value - * @throws NullPointerException if <pre>value</pre> is null + * @throws NullPointerException if <code>value</code> is null * @see #containsValue(Object) * @see #containsKey(Object) */ public synchronized boolean contains(Object value) { - // Check if value is null in case Hashtable is empty. + // Check if value is null. if (value == null) throw new NullPointerException(); - - for (int i = buckets.length - 1; i >= 0; i--) - { - HashEntry e = buckets[i]; - while (e != null) - { - if (value.equals(e.value)) - return true; - e = e.next; - } - } - return false; + return containsValue(value); } /** - * Returns true if this Hashtable contains a value <pre>o</pre>, such that - * <pre>o.equals(value)</pre>. This is the new API for the old - * <code>contains()</code>. + * Returns true if this Hashtable contains a value <code>o</code>, such that + * <code>o.equals(value)</code>. This is the new API for the old + * <code>contains()</code>, except that it is forgiving of null. * * @param value the value to search for in this Hashtable * @return true if at least one key maps to the value - * @throws NullPointerException if <pre>value</pre> is null * @see #contains(Object) * @see #containsKey(Object) * @since 1.2 */ public boolean containsValue(Object value) { - return contains(value); + for (int i = buckets.length - 1; i >= 0; i--) + { + HashEntry e = buckets[i]; + while (e != null) + { + if (AbstractCollection.equals(value, e.value)) + return true; + e = e.next; + } + } + return false; } /** - * Returns true if the supplied object <pre>equals()</pre> a key + * Returns true if the supplied object <code>equals()</code> a key * in this Hashtable. * * @param key the key to search for in this Hashtable @@ -348,7 +379,7 @@ public class Hashtable extends Dictionary /** * Return the value in this Hashtable associated with the supplied key, - * or <pre>null</pre> if the key maps to nothing. + * or <code>null</code> if the key maps to nothing. * * @param key the key for which to fetch an associated value * @return what the key maps to, if present @@ -383,7 +414,6 @@ public class Hashtable extends Dictionary */ public synchronized Object put(Object key, Object value) { - modCount++; int idx = hash(key); HashEntry e = buckets[idx]; @@ -407,6 +437,7 @@ public class Hashtable extends Dictionary } // At this point, we know we need to add a new entry. + modCount++; if (++size > threshold) { rehash(); @@ -425,15 +456,18 @@ public class Hashtable extends Dictionary /** * Removes from the table and returns the value which is mapped by the * supplied key. If the key maps to nothing, then the table remains - * unchanged, and <pre>null</pre> is returned. + * unchanged, and <code>null</code> is returned. + * <b>NOTE:</b>Map.remove and Dictionary.remove disagree whether null + * is a valid parameter; at the moment, this implementation obeys Map.remove, + * and silently ignores null. * * @param key the key used to locate the value to remove * @return whatever the key mapped to, if present - * @throws NullPointerException if key is null */ public synchronized Object remove(Object key) { - modCount++; + if (key == null) + return null; int idx = hash(key); HashEntry e = buckets[idx]; HashEntry last = null; @@ -442,6 +476,7 @@ public class Hashtable extends Dictionary { if (key.equals(e.key)) { + modCount++; if (last == null) buckets[idx] = e.next; else @@ -488,9 +523,12 @@ public class Hashtable extends Dictionary */ public synchronized void clear() { - modCount++; - Arrays.fill(buckets, null); - size = 0; + if (size > 0) + { + modCount++; + Arrays.fill(buckets, null); + size = 0; + } } /** @@ -511,36 +549,18 @@ public class Hashtable extends Dictionary // This is impossible. } copy.buckets = new HashEntry[buckets.length]; - - for (int i = buckets.length - 1; i >= 0; i--) - { - HashEntry e = buckets[i]; - HashEntry last = null; - - while (e != null) - { - if (last == null) - { - last = new HashEntry(e.key, e.value); - copy.buckets[i] = last; - } - else - { - last.next = new HashEntry(e.key, e.value); - last = last.next; - } - e = e.next; - } - } + copy.putAllInternal(this); + // Clear the caches. + copy.keys = null; + copy.values = null; + copy.entries = null; return copy; } /** - * Converts this Hashtable to a String, surrounded by braces (<pre>'{'</pre> - * and <pre>'}'</pre>), key/value pairs listed with an equals sign between, - * (<pre>'='</pre>), and pairs separated by comma and space - * (<pre>", "</pre>). - * <p> + * Converts this Hashtable to a String, surrounded by braces, and with + * key/value pairs listed with an equals sign between, separated by a + * comma and space. For example, <code>"{a=1, b=2}"</code>.<p> * * NOTE: if the <code>toString()</code> method of any key or value * throws an exception, this will fail for the same reason. @@ -552,7 +572,7 @@ public class Hashtable extends Dictionary // Since we are already synchronized, and entrySet().iterator() // would repeatedly re-lock/release the monitor, we directly use the // unsynchronized HashIterator instead. - Iterator entries = new HashIterator(HashIterator.ENTRIES); + Iterator entries = new HashIterator(ENTRIES); StringBuffer r = new StringBuffer("{"); for (int pos = size; pos > 0; pos--) { @@ -568,9 +588,11 @@ public class Hashtable extends Dictionary * Returns a "set view" of this Hashtable's keys. The set is backed by * the hashtable, so changes in one show up in the other. The set supports * element removal, but not element addition. The set is properly - * synchronized on the original hashtable. The set will throw a - * {@link NullPointerException} if null is passed to <code>contains</code>, - * <code>remove</code>, or related methods. + * synchronized on the original hashtable. Sun has not documented the + * proper interaction of null with this set, but has inconsistent behavior + * in the JDK. Therefore, in this implementation, contains, remove, + * containsAll, retainAll, removeAll, and equals just ignore a null key + * rather than throwing a {@link NullPointerException}. * * @return a set view of the keys * @see #values() @@ -579,50 +601,56 @@ public class Hashtable extends Dictionary */ public Set keySet() { - // Create a synchronized AbstractSet with custom implementations of those - // methods that can be overridden easily and efficiently. - Set r = new AbstractSet() - { - public int size() + if (keys == null) { - return size; - } + // Create a synchronized AbstractSet with custom implementations of + // those methods that can be overridden easily and efficiently. + Set r = new AbstractSet() + { + public int size() + { + return size; + } - public Iterator iterator() - { - return new HashIterator(HashIterator.KEYS); - } + public Iterator iterator() + { + return new HashIterator(KEYS); + } - public void clear() - { - Hashtable.this.clear(); - } + public void clear() + { + Hashtable.this.clear(); + } - public boolean contains(Object o) - { - return Hashtable.this.containsKey(o); - } + public boolean contains(Object o) + { + if (o == null) + return false; + return containsKey(o); + } - public boolean remove(Object o) - { - return (Hashtable.this.remove(o) != null); + public boolean remove(Object o) + { + return Hashtable.this.remove(o) != null; + } + }; + // We must specify the correct object to synchronize upon, hence the + // use of a non-public API + keys = new Collections.SynchronizedSet(this, r); } - }; - - // We must specify the correct object to synchronize upon, hence the - // use of a non-public API - return new Collections.SynchronizedSet(this, r); + return keys; } - /** * Returns a "collection view" (or "bag view") of this Hashtable's values. * The collection is backed by the hashtable, so changes in one show up * in the other. The collection supports element removal, but not element * addition. The collection is properly synchronized on the original - * hashtable. The collection will throw a {@link NullPointerException} - * if null is passed to <code>contains</code> or related methods, but not - * if passed to <code>remove</code> or related methods. + * hashtable. Sun has not documented the proper interaction of null with + * this set, but has inconsistent behavior in the JDK. Therefore, in this + * implementation, contains, remove, containsAll, retainAll, removeAll, and + * equals just ignore a null value rather than throwing a + * {@link NullPointerException}. * * @return a bag view of the values * @see #keySet() @@ -631,46 +659,45 @@ public class Hashtable extends Dictionary */ public Collection values() { - // We don't bother overriding many of the optional methods, as doing so - // wouldn't provide any significant performance advantage. - Collection r = new AbstractCollection() - { - public int size() - { - return size; - } - - public Iterator iterator() + if (values == null) { - return new HashIterator(HashIterator.VALUES); - } + // We don't bother overriding many of the optional methods, as doing so + // wouldn't provide any significant performance advantage. + Collection r = new AbstractCollection() + { + public int size() + { + return size; + } - public void clear() - { - Hashtable.this.clear(); - } + public Iterator iterator() + { + return new HashIterator(VALUES); + } - // Override this so that we check for null - public boolean contains(Object o) - { - return Hashtable.this.contains(o); + public void clear() + { + Hashtable.this.clear(); + } + }; + // We must specify the correct object to synchronize upon, hence the + // use of a non-public API + values = new Collections.SynchronizedCollection(this, r); } - }; - - // We must specify the correct object to synchronize upon, hence the - // use of a non-public API - return new Collections.SynchronizedCollection(this, r); + return values; } /** * Returns a "set view" of this Hashtable's entries. The set is backed by * the hashtable, so changes in one show up in the other. The set supports * element removal, but not element addition. The set is properly - * synchronized on the original hashtable. The set will throw a - * {@link NullPointerException} if the Map.Entry passed to - * <code>contains</code>, <code>remove</code>, or related methods returns - * null for <code>getKey</code>, but not if the Map.Entry is null or - * returns null for <code>getValue</code>. + * synchronized on the original hashtable. Sun has not documented the + * proper interaction of null with this set, but has inconsistent behavior + * in the JDK. Therefore, in this implementation, contains, remove, + * containsAll, retainAll, removeAll, and equals just ignore a null entry, + * or an entry with a null key or value, rather than throwing a + * {@link NullPointerException}. However, calling entry.setValue(null) + * will fail. * <p> * * Note that the iterators for all three views, from keySet(), entrySet(), @@ -684,49 +711,52 @@ public class Hashtable extends Dictionary */ public Set entrySet() { - // Create an AbstractSet with custom implementations of those methods that - // can be overridden easily and efficiently. - Set r = new AbstractSet() - { - public int size() + if (entries == null) { - return size; - } + // Create an AbstractSet with custom implementations of those methods + // that can be overridden easily and efficiently. + Set r = new AbstractSet() + { + public int size() + { + return size; + } - public Iterator iterator() - { - return new HashIterator(HashIterator.ENTRIES); - } + public Iterator iterator() + { + return new HashIterator(ENTRIES); + } - public void clear() - { - Hashtable.this.clear(); - } + public void clear() + { + Hashtable.this.clear(); + } - public boolean contains(Object o) - { - return getEntry(o) != null; - } + public boolean contains(Object o) + { + return getEntry(o) != null; + } - public boolean remove(Object o) - { - HashEntry e = getEntry(o); - if (e != null) + public boolean remove(Object o) { - Hashtable.this.remove(e.key); - return true; + HashEntry e = getEntry(o); + if (e != null) + { + Hashtable.this.remove(e.key); + return true; + } + return false; } - return false; + }; + // We must specify the correct object to synchronize upon, hence the + // use of a non-public API + entries = new Collections.SynchronizedSet(this, r); } - }; - - // We must specify the correct object to synchronize upon, hence the - // use of a non-public API - return new Collections.SynchronizedSet(this, r); + return entries; } /** - * Returns true if this Hashtable equals the supplied Object <pre>o</pre>. + * Returns true if this Hashtable equals the supplied Object <code>o</code>. * As specified by Map, this is: * <pre> * (o instanceof Map) && entrySet().equals(((Map) o).entrySet()); @@ -759,7 +789,7 @@ public class Hashtable extends Dictionary // Since we are already synchronized, and entrySet().iterator() // would repeatedly re-lock/release the monitor, we directly use the // unsynchronized HashIterator instead. - Iterator itr = new HashIterator(HashIterator.ENTRIES); + Iterator itr = new HashIterator(ENTRIES); int hashcode = 0; for (int pos = size; pos > 0; pos--) hashcode += itr.next().hashCode(); @@ -782,23 +812,25 @@ public class Hashtable extends Dictionary /** * Helper method for entrySet(), which matches both key and value - * simultaneously. + * simultaneously. Ignores null, as mentioned in entrySet(). * * @param o the entry to match * @return the matching entry, if found, or null - * @throws NullPointerException if me.getKey() returns null * @see #entrySet() */ private HashEntry getEntry(Object o) { - if (!(o instanceof Map.Entry)) + if (! (o instanceof Map.Entry)) + return null; + Object key = ((Map.Entry) o).getKey(); + if (key == null) return null; - Map.Entry me = (Map.Entry) o; - int idx = hash(me.getKey()); + + int idx = hash(key); HashEntry e = buckets[idx]; while (e != null) { - if (e.equals(me)) + if (o.equals(e)) return e; e = e.next; } @@ -806,6 +838,30 @@ public class Hashtable extends Dictionary } /** + * A simplified, more efficient internal implementation of putAll(). The + * Map constructor and clone() should not call putAll or put, in order to + * be compatible with the JDK implementation with respect to subclasses. + * + * @param m the map to initialize this from + */ + void putAllInternal(Map m) + { + Iterator itr = m.entrySet().iterator(); + int msize = m.size(); + this.size = msize; + + for (; msize > 0; msize--) + { + Map.Entry e = (Map.Entry) itr.next(); + Object key = e.getKey(); + int idx = hash(key); + HashEntry he = new HashEntry(key, e.getValue()); + he.next = buckets[idx]; + buckets[idx] = he; + } + } + + /** * Increases the size of the Hashtable and rehashes all keys to new array * indices; this is called when the addition of a new value would cause * size() > threshold. Note that the existing Entry objects are reused in @@ -813,7 +869,8 @@ public class Hashtable extends Dictionary * <p> * * This is not specified, but the new size is twice the current size plus - * one; this number is not always prime, unfortunately. + * one; this number is not always prime, unfortunately. This implementation + * is not synchronized, as it is only invoked from synchronized methods. */ protected void rehash() { @@ -854,8 +911,8 @@ public class Hashtable extends Dictionary * * @param s the stream to write to * @throws IOException if the underlying stream fails - * @serialData the <i>capacity</i>(int) that is the length of the - * bucket array, the <i>size</i>(int) of the hash map + * @serialData the <i>capacity</i> (int) that is the length of the + * bucket array, the <i>size</i> (int) of the hash map * are emitted first. They are followed by size entries, * each consisting of a key (Object) and a value (Object). */ @@ -870,7 +927,7 @@ public class Hashtable extends Dictionary // Since we are already synchronized, and entrySet().iterator() // would repeatedly re-lock/release the monitor, we directly use the // unsynchronized HashIterator instead. - Iterator it = new HashIterator(HashIterator.ENTRIES); + Iterator it = new HashIterator(ENTRIES); while (it.hasNext()) { HashEntry entry = (HashEntry) it.next(); @@ -885,8 +942,8 @@ public class Hashtable extends Dictionary * @param s the stream to read from * @throws ClassNotFoundException if the underlying stream fails * @throws IOException if the underlying stream fails - * @serialData the <i>capacity</i>(int) that is the length of the - * bucket array, the <i>size</i>(int) of the hash map + * @serialData the <i>capacity</i> (int) that is the length of the + * bucket array, the <i>size</i> (int) of the hash map * are emitted first. They are followed by size entries, * each consisting of a key (Object) and a value (Object). */ @@ -901,7 +958,8 @@ public class Hashtable extends Dictionary int len = s.readInt(); // Read and use key/value pairs. - for ( ; len > 0; len--) + // TODO: should we be defensive programmers, and check for illegal nulls? + while (--len >= 0) put(s.readObject(), s.readObject()); } @@ -916,13 +974,8 @@ public class Hashtable extends Dictionary * * @author Jon Zeppieri */ - class HashIterator implements Iterator + private final class HashIterator implements Iterator { - /** "enum" of iterator types. */ - static final int KEYS = 0, - VALUES = 1, - ENTRIES = 2; - /** * The type of this Iterator: {@link #KEYS}, {@link #VALUES}, * or {@link #ENTRIES}. @@ -988,14 +1041,14 @@ public class Hashtable extends Dictionary last = e; if (type == VALUES) return e.value; - else if (type == KEYS) + if (type == KEYS) return e.key; return e; } /** * Removes from the backing Hashtable the last element which was fetched - * with the <pre>next()</pre> method. + * with the <code>next()</code> method. * @throws ConcurrentModificationException if the hashtable was modified * @throws IllegalStateException if called when there is no last element */ @@ -1007,10 +1060,10 @@ public class Hashtable extends Dictionary throw new IllegalStateException(); Hashtable.this.remove(last.key); - knownMod++; last = null; + knownMod++; } - } + } // class HashIterator /** @@ -1027,21 +1080,21 @@ public class Hashtable extends Dictionary * * @author Jon Zeppieri */ - class Enumerator implements Enumeration + private final class Enumerator implements Enumeration { - /** "enum" of iterator types. */ - static final int KEYS = 0, - VALUES = 1; - /** * The type of this Iterator: {@link #KEYS} or {@link #VALUES}. */ - int type; + final int type; + /** The number of elements remaining to be returned by next(). */ + int count = size; /** Current index in the physical hash table. */ - int idx; - /** The last Entry returned by nextEntry(). */ - HashEntry last; - /** Entry which will be returned by the next nextElement() call. */ + int idx = buckets.length; + /** + * Entry which will be returned by the next nextElement() call. It is + * set if we are iterating through a bucket with multiple entries, or null + * if we must look in the next bucket. + */ HashEntry next; /** @@ -1051,25 +1104,6 @@ public class Hashtable extends Dictionary Enumerator(int type) { this.type = type; - this.idx = buckets.length; - } - - /** - * Helper method to find the next entry. - * @return the next entry, or null - */ - private HashEntry nextEntry() - { - HashEntry e = null; - - if (last != null) - e = last.next; - - while (e == null && idx > 0) - e = buckets[--idx]; - - last = e; - return e; } /** @@ -1078,10 +1112,7 @@ public class Hashtable extends Dictionary */ public boolean hasMoreElements() { - if (next != null) - return true; - next = nextEntry(); - return next != null; + return count > 0; } /** @@ -1091,19 +1122,16 @@ public class Hashtable extends Dictionary */ public Object nextElement() { - HashEntry e; - if (next != null) - { - e = next; - next = null; - } - else - e = nextEntry(); - if (e == null) + if (count == 0) throw new NoSuchElementException("Hashtable Enumerator"); - if (type == VALUES) - return e.value; - return e.key; + count--; + HashEntry e = next; + + while (e == null) + e = buckets[--idx]; + + next = e.next; + return type == VALUES ? e.value : e.key; } - } + } // class Enumerator } diff --git a/libjava/java/util/IdentityHashMap.java b/libjava/java/util/IdentityHashMap.java index da028ed9c02..d6a2f7c7c2d 100644 --- a/libjava/java/util/IdentityHashMap.java +++ b/libjava/java/util/IdentityHashMap.java @@ -31,403 +31,888 @@ import java.io.*; /** * This class provides a hashtable-backed implementation of the - * Map interface. Unlike HashMap, it uses object identity to - * do its hashing. Also, it uses a linear-probe hash table. + * Map interface, but uses object identity to do its hashing. In fact, + * it uses object identity for comparing values, as well. It uses a + * linear-probe hash table, which may have faster performance + * than the chaining employed by HashMap. + * <p> + * + * <em>WARNING: This is not a general purpose map. Because it uses + * System.identityHashCode and ==, instead of hashCode and equals, for + * comparison, it violated Map's general contract, and may cause + * undefined behavior when compared to other maps which are not + * IdentityHashMaps. This is designed only for the rare cases when + * identity semantics are needed.</em> An example use is + * topology-preserving graph transformations, such as deep cloning, + * or as proxy object mapping such as in debugging. + * <p> + * + * This map permits <code>null</code> keys and values, and does not + * guarantee that elements will stay in the same order over time. The + * basic operations (<code>get</code> and <code>put</code>) take + * constant time, provided System.identityHashCode is decent. You can + * tune the behavior by specifying the expected maximum size. As more + * elements are added, the map may need to allocate a larger table, + * which can be expensive. + * <p> + * + * This implementation is unsynchronized. If you want multi-thread + * access to be consistent, you must synchronize it, perhaps by using + * <code>Collections.synchronizedMap(new IdentityHashMap(...));</code>. + * The iterators are <i>fail-fast</i>, meaning that a structural modification + * made to the map outside of an iterator's remove method cause the + * iterator, and in the case of the entrySet, the Map.Entry, to + * fail with a {@link ConcurrentModificationException}. * * @author Tom Tromey <tromey@redhat.com> + * @author Eric Blake <ebb9@email.byu.edu> + * @see System#identityHashCode(Object) + * @see Collection + * @see Map + * @see HashMap + * @see TreeMap + * @see LinkedHashMap + * @see WeakHashMap * @since 1.4 + * @status updated to 1.4 */ public class IdentityHashMap extends AbstractMap implements Map, Serializable, Cloneable { + /** The default capacity. */ private static final int DEFAULT_CAPACITY = 21; - /** Create a new IdentityHashMap with the default capacity (21 - * entries). + /** + * This object is used to mark deleted items. Package visible for use by + * nested classes. + */ + static final Object tombstone = new Object(); + + /** + * This object is used to mark empty slots. We need this because + * using null is ambiguous. Package visible for use by nested classes. + */ + static final Object emptyslot = new Object(); + + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = 8188218128353913216L; + + /** + * The number of mappings in the table. Package visible for use by nested + * classes. + * @serial + */ + int size; + + /** + * The table itself. Package visible for use by nested classes. */ - public IdentityHashMap () + transient Object[] table; + + /** + * The number of structural modifications made so far. Package visible for + * use by nested classes. + */ + transient int modCount; + + /** + * The cache for {@link #entrySet()}. + */ + private transient Set entries; + + /** + * The threshold for rehashing, which is 75% of (table.length / 2). + */ + private transient int threshold; + + /** + * Create a new IdentityHashMap with the default capacity (21 entries). + */ + public IdentityHashMap() { - this (DEFAULT_CAPACITY); + this(DEFAULT_CAPACITY); } - /** Create a new IdentityHashMap with the indicated number of + /** + * Create a new IdentityHashMap with the indicated number of * entries. If the number of elements added to this hash map * exceeds this maximum, the map will grow itself; however, that * incurs a performance penalty. - * @param max Initial size + * + * @param max initial size + * @throws IllegalArgumentException if max is negative */ - public IdentityHashMap (int max) + public IdentityHashMap(int max) { if (max < 0) - throw new IllegalArgumentException (); + throw new IllegalArgumentException(); + // Need at least two slots, or hash() will break. + if (max < 2) + max = 2; table = new Object[2 * max]; - Arrays.fill (table, emptyslot); - size = 0; + Arrays.fill(table, emptyslot); + // This is automatically set. + // size = 0; + threshold = max / 4 * 3; } - /** Create a new IdentityHashMap whose contents are taken from the + /** + * Create a new IdentityHashMap whose contents are taken from the * given Map. - * @param m The map whose elements are to be put in this map. + * + * @param m The map whose elements are to be put in this map + * @throws NullPointerException if m is null */ - public IdentityHashMap (Map m) + public IdentityHashMap(Map m) { - int len = 2 * Math.max (m.size (), DEFAULT_CAPACITY); - table = new Object[len]; - Arrays.fill (table, emptyslot); - putAll (m); + this(Math.max(m.size() * 2, DEFAULT_CAPACITY)); + putAll(m); } - public void clear () + /** + * Remove all mappings from this map. + */ + public void clear() { - Arrays.fill (table, emptyslot); - size = 0; + if (size != 0) + { + modCount++; + Arrays.fill(table, emptyslot); + size = 0; + } } /** * Creates a shallow copy where keys and values are not cloned. */ - public Object clone () + public Object clone() { - try + try { - IdentityHashMap copy = (IdentityHashMap) super.clone (); - copy.table = (Object[]) table.clone (); - return copy; + IdentityHashMap copy = (IdentityHashMap) super.clone(); + copy.table = (Object[]) table.clone(); + copy.entries = null; // invalidate the cache + return copy; } - catch (CloneNotSupportedException e) + catch (CloneNotSupportedException e) { - // Can't happen. - return null; + // Can't happen. + return null; } } - public boolean containsKey (Object key) + /** + * Tests whether the specified key is in this map. Unlike normal Maps, + * this test uses <code>entry == key</code> instead of + * <code>entry == null ? key == null : entry.equals(key)</code>. + * + * @param key the key to look for + * @return true if the key is contained in the map + * @see #containsValue(Object) + * @see #get(Object) + */ + public boolean containsKey(Object key) { - int h = getHash (key); - int save = h; - while (true) - { - if (table[h] == key) - return true; - if (table[h] == emptyslot) - return false; - h += 2; - if (h >= table.length) - h = 0; - if (h == save) - return false; - } + return key == table[hash(key)]; } - public boolean containsValue (Object value) + /** + * Returns true if this HashMap contains the value. Unlike normal maps, + * this test uses <code>entry == value</code> instead of + * <code>entry == null ? value == null : entry.equals(value)</code>. + * + * @param value the value to search for in this HashMap + * @return true if at least one key maps to the value + * @see #containsKey(Object) + */ + public boolean containsValue(Object value) { - for (int i = 1; i < table.length; i += 2) + for (int i = table.length - 1; i > 0; i -= 2) if (table[i] == value) - return true; + return true; return false; } - public Set entrySet () + /** + * Returns a "set view" of this Map's entries. The set is backed by + * the Map, so changes in one show up in the other. The set supports + * element removal, but not element addition. + * <p> + * + * <em>The semantics of this set, and of its contained entries, are + * different from the contract of Set and Map.Entry in order to make + * IdentityHashMap work. This means that while you can compare these + * objects between IdentityHashMaps, comparing them with regular sets + * or entries is likely to have undefined behavior.</em> The entries + * in this set are reference-based, rather than the normal object + * equality. Therefore, <code>e1.equals(e2)</code> returns + * <code>e1.getKey() == e2.getKey() && e1.getValue() == e2.getValue()</code>, + * and <code>e.hashCode()</code> returns + * <code>System.identityHashCode(e.getKey()) ^ + * System.identityHashCode(e.getValue())</code>. + * <p> + * + * Note that the iterators for all three views, from keySet(), entrySet(), + * and values(), traverse the Map in the same sequence. + * + * @return a set view of the entries + * @see #keySet() + * @see #values() + * @see Map.Entry + */ + public Set entrySet() { - return new AbstractSet () - { - public int size () + if (entries == null) + entries = new AbstractSet() { - return size; - } - - public Iterator iterator () - { - return new IdentityIterator (IdentityIterator.ENTRIES); - } - - public void clear () - { - IdentityHashMap.this.clear (); - } + public int size() + { + return size; + } + + public Iterator iterator() + { + return new IdentityIterator(ENTRIES); + } + + public void clear() + { + IdentityHashMap.this.clear(); + } + + public boolean contains(Object o) + { + if (! (o instanceof Map.Entry)) + return false; + Map.Entry m = (Map.Entry) o; + return m.getValue() == table[hash(m.getKey()) + 1]; + } + + public int hashCode() + { + return IdentityHashMap.this.hashCode(); + } + + public boolean remove(Object o) + { + if (! (o instanceof Map.Entry)) + return false; + Object key = ((Map.Entry) o).getKey(); + int h = hash(key); + if (table[h] == key) + { + size--; + modCount++; + table[h] = tombstone; + table[h + 1] = tombstone; + return true; + } + return false; + } + }; + return entries; + } - public boolean contains (Object o) - { - if (! (o instanceof Map.Entry)) - return false; - Map.Entry m = (Map.Entry) o; - return (IdentityHashMap.this.containsKey (m.getKey ()) - && IdentityHashMap.this.get (m.getKey ()) == m.getValue ()); - } + /** + * Compares two maps for equality. This returns true only if both maps + * have the same reference-identity comparisons. While this returns + * <code>this.entrySet().equals(m.entrySet())</code> as specified by Map, + * this will not work with normal maps, since the entry set compares + * with == instead of .equals. + * + * @param o the object to compare to + * @return true if it is equal + */ + public boolean equals(Object o) + { + // Why did Sun specify this one? The superclass does the right thing. + return super.equals(o); + } - public boolean remove (Object o) - { - if (! (o instanceof Map.Entry)) - return false; - Map.Entry m = (Map.Entry) o; - if (IdentityHashMap.this.containsKey (m.getKey ()) - && IdentityHashMap.this.get (m.getKey ()) == m.getValue ()) - { - int oldsize = size; - IdentityHashMap.this.remove (m.getKey ()); - return oldsize != size; - } - return false; - } - }; + /** + * Return the value in this Map associated with the supplied key, + * or <pre>null</pre> if the key maps to nothing. NOTE: Since the value + * could also be null, you must use containsKey to see if this key + * actually maps to something. Unlike normal maps, this tests for the key + * with <code>entry == key</code> instead of + * <code>entry == null ? key == null : entry.equals(key)</code>. + * + * @param key the key for which to fetch an associated value + * @return what the key maps to, if present + * @see #put(Object, Object) + * @see #containsKey(Object) + */ + public Object get(Object key) + { + int h = hash(key); + return table[h] == key ? table[h + 1] : null; } - public Object get (Object key) + /** + * Returns the hashcode of this map. This guarantees that two + * IdentityHashMaps that compare with equals() will have the same hash code, + * but may break with comparison to normal maps since it uses + * System.identityHashCode() instead of hashCode(). + * + * @return the hash code + */ + public int hashCode() { - int h = getHash (key); - int save = h; - while (true) + int hash = 0; + for (int i = table.length - 2; i >= 0; i -= 2) { - if (table[h] == key) - return table[h + 1]; - if (table[h] == emptyslot) - return null; - h += 2; - if (h >= table.length) - h = 0; - if (h == save) - return null; + Object key = table[i]; + if (key == emptyslot || key == tombstone) + continue; + hash += (System.identityHashCode(key) + ^ System.identityHashCode(table[i + 1])); } + return hash; } - public boolean isEmpty () + /** + * Returns true if there are no key-value mappings currently in this Map + * @return <code>size() == 0</code> + */ + public boolean isEmpty() { return size == 0; } - public Set keySet () + /** + * Returns a "set view" of this Map's keys. The set is backed by the + * Map, so changes in one show up in the other. The set supports + * element removal, but not element addition. + * <p> + * + * <em>The semantics of this set are different from the contract of Set + * in order to make IdentityHashMap work. This means that while you can + * compare these objects between IdentityHashMaps, comparing them with + * regular sets is likely to have undefined behavior.</em> The hashCode + * of the set is the sum of the identity hash codes, instead of the + * regular hashCodes, and equality is determined by reference instead + * of by the equals method. + * <p> + * + * @return a set view of the keys + * @see #values() + * @see #entrySet() + */ + public Set keySet() { - return new AbstractSet () - { - public int size () - { - return size; - } - - public Iterator iterator () - { - return new IdentityIterator (IdentityIterator.KEYS); - } - - public void clear () + if (keys == null) + keys = new AbstractSet() { - IdentityHashMap.this.clear (); - } - - public boolean contains (Object o) - { - return IdentityHashMap.this.containsKey (o); - } - - public boolean remove (Object o) - { - int oldsize = size; - IdentityHashMap.this.remove (o); - return oldsize != size; - } - }; + public int size() + { + return size; + } + + public Iterator iterator() + { + return new IdentityIterator(KEYS); + } + + public void clear() + { + IdentityHashMap.this.clear(); + } + + public boolean contains(Object o) + { + return containsKey(o); + } + + public int hashCode() + { + int hash = 0; + for (int i = table.length - 2; i >= 0; i -= 2) + { + Object key = table[i]; + if (key == emptyslot || key == tombstone) + continue; + hash += System.identityHashCode(key); + } + return hash; + + } + + public boolean remove(Object o) + { + int h = hash(o); + if (table[h] == o) + { + size--; + modCount++; + table[h] = tombstone; + table[h + 1] = tombstone; + return true; + } + return false; + } + }; + return keys; } - public Object put (Object key, Object value) + /** + * Puts the supplied value into the Map, mapped by the supplied key. + * The value may be retrieved by any object which <code>equals()</code> + * this key. NOTE: Since the prior value could also be null, you must + * first use containsKey if you want to see if you are replacing the + * key's mapping. Unlike normal maps, this tests for the key + * with <code>entry == key</code> instead of + * <code>entry == null ? key == null : entry.equals(key)</code>. + * + * @param key the key used to locate the value + * @param value the value to be stored in the HashMap + * @return the prior mapping of the key, or null if there was none + * @see #get(Object) + */ + public Object put(Object key, Object value) { - // Rehash if the load factor is too high. We use a factor of 1.5 - // -- the division by 2 is implicit on both sides. - if (size * 3 > table.length) - { - Object[] old = table; - table = new Object[old.length * 2]; - Arrays.fill (table, emptyslot); - size = 0; - for (int i = 0; i < old.length; i += 2) - { - if (old[i] != tombstone && old[i] != emptyslot) - { - // Just use put. This isn't very efficient, but it is - // ok. - put (old[i], old[i + 1]); - } - } - } - - int h = getHash (key); - int save = h; - int del = -1; - while (true) + // Rehash if the load factor is too high. + if (size > threshold) { - if (table[h] == key) - { - Object r = table[h + 1]; - table[h + 1] = value; - return r; - } - else if (table[h] == tombstone && del == -1) - del = h; - else if (table[h] == emptyslot) - { - if (del == -1) - del = h; - break; - } - h += 2; - if (h >= table.length) - h = 0; - if (h == save) - break; + Object[] old = table; + // This isn't necessarily prime, but it is an odd number of key/value + // slots, which has a higher probability of fewer collisions. + table = new Object[old.length * 2 + 2]; + Arrays.fill(table, emptyslot); + size = 0; + threshold = table.length / 4 * 3; + + for (int i = old.length - 2; i >= 0; i -= 2) + { + Object oldkey = old[i]; + if (oldkey != tombstone && oldkey != emptyslot) + // Just use put. This isn't very efficient, but it is ok. + put(oldkey, old[i + 1]); + } } - if (del != -1) + int h = hash(key); + if (table[h] == key) { - table[del] = key; - table[del + 1] = value; - ++size; - return null; + Object r = table[h + 1]; + table[h + 1] = value; + return r; } - // This is an error. + // At this point, we add a new mapping. + modCount++; + size++; + table[h] = key; + table[h + 1] = value; return null; } - public Object remove (Object key) + /** + * Copies all of the mappings from the specified map to this. If a key + * is already in this map, its value is replaced. + * + * @param m the map to copy + * @throws NullPointerException if m is null + */ + public void putAll(Map m) { - int h = getHash (key); - int save = h; - while (true) + // Why did Sun specify this one? The superclass does the right thing. + super.putAll(m); + } + + /** + * Removes from the HashMap and returns the value which is mapped by the + * supplied key. If the key maps to nothing, then the HashMap remains + * unchanged, and <pre>null</pre> is returned. NOTE: Since the value + * could also be null, you must use containsKey to see if you are + * actually removing a mapping. Unlike normal maps, this tests for the + * key with <code>entry == key</code> instead of + * <code>entry == null ? key == null : entry.equals(key)</code>. + * + * @param key the key used to locate the value to remove + * @return whatever the key mapped to, if present + */ + public Object remove(Object key) + { + int h = hash(key); + if (table[h] == key) { - if (table[h] == key) - { - Object r = table[h + 1]; - table[h] = tombstone; - table[h + 1] = tombstone; - --size; - return r; - } - h += 2; - if (h >= table.length) - h = 0; - if (h == save) - break; + modCount++; + size--; + Object r = table[h + 1]; + table[h] = tombstone; + table[h + 1] = tombstone; + return r; } - return null; } - public int size () + /** + * Returns the number of kay-value mappings currently in this Map + * @return the size + */ + public int size() { return size; } - public Collection values () + /** + * Returns a "collection view" (or "bag view") of this Map's values. + * The collection is backed by the Map, so changes in one show up + * in the other. The collection supports element removal, but not element + * addition. + * <p> + * + * <em>The semantics of this set are different from the contract of + * Collection in order to make IdentityHashMap work. This means that + * while you can compare these objects between IdentityHashMaps, comparing + * them with regular sets is likely to have undefined behavior.</em> + * Likewise, contains and remove go by == instead of equals(). + * <p> + * + * @return a bag view of the values + * @see #keySet() + * @see #entrySet() + */ + public Collection values() { - return new AbstractCollection () - { - public int size () + if (values == null) + values = new AbstractCollection() { - return size; - } + public int size() + { + return size; + } + + public Iterator iterator() + { + return new IdentityIterator(VALUES); + } + + public void clear() + { + IdentityHashMap.this.clear(); + } + + public boolean remove(Object o) + { + for (int i = table.length - 1; i > 0; i -= 2) + if (table[i] == o) + { + modCount++; + table[i - 1] = tombstone; + table[i] = tombstone; + size--; + return true; + } + return false; + } + }; + return values; + } - public Iterator iterator () - { - return new IdentityIterator (IdentityIterator.VALUES); - } + /** + * Helper method which computes the hash code, then traverses the table + * until it finds the key, or the spot where the key would go. + * + * @param key the key to check + * @return the index where the key belongs + * @see #IdentityHashMap(int) + * @see #put(Object, Object) + */ + // Package visible for use by nested classes. + int hash(Object key) + { + // Implementation note: it is feasible for the table to have no + // emptyslots, if it is full with entries and tombstones, so we must + // remember where we started. If we encounter the key or an emptyslot, + // we are done. If we encounter a tombstone, the key may still be in + // the array. If we don't encounter the key, we use the first emptyslot + // or tombstone we encountered as the location where the key would go. + // By requiring at least 2 key/value slots, and rehashing at 75% + // capacity, we guarantee that there will always be either an emptyslot + // or a tombstone somewhere in the table. + int h = 2 * Math.abs(System.identityHashCode(key) % table.length); + int del = -1; + int save = h; - public void clear () + do { - IdentityHashMap.this.clear (); + if (table[h] == key) + return h; + if (table[h] == emptyslot) + break; + if (table[h] == tombstone && del < 0) + del = h; + h -= 2; + if (h < 0) + h = table.length - 2; } - }; + while (h != save); + + return del < 0 ? h : del; } - private class IdentityIterator implements Iterator + /** + * This class allows parameterized iteration over IdentityHashMaps. Based + * on its construction, it returns the key or value of a mapping, or + * creates the appropriate Map.Entry object with the correct fail-fast + * semantics and identity comparisons. + * + * @author Tom Tromey <tromey@redhat.com> + * @author Eric Blake <ebb9@email.byu.edu> + */ + private final class IdentityIterator implements Iterator { - static final int KEYS = 0; - static final int VALUES = 1; - static final int ENTRIES = 2; - - // Type of iterator. - int type; - // Location in the table. - int loc; - // How many items we've seen. - int seen; - - IdentityIterator (int type) + /** + * The type of this Iterator: {@link #KEYS}, {@link #VALUES}, + * or {@link #ENTRIES}. + */ + final int type; + /** The number of modifications to the backing Map that we know about. */ + int knownMod = modCount; + /** The number of elements remaining to be returned by next(). */ + int count = size; + /** Location in the table. */ + int loc = table.length; + + /** + * Construct a new Iterator with the supplied type. + * @param type {@link #KEYS}, {@link #VALUES}, or {@link #ENTRIES} + */ + IdentityIterator(int type) { this.type = type; - loc = 0; - seen = 0; } - public boolean hasNext () + /** + * Returns true if the Iterator has more elements. + * @return true if there are more elements + * @throws ConcurrentModificationException if the Map was modified + */ + public boolean hasNext() { - return seen < size; + if (knownMod != modCount) + throw new ConcurrentModificationException(); + return count > 0; } - public Object next () + /** + * Returns the next element in the Iterator's sequential view. + * @return the next element + * @throws ConcurrentModificationException if the Map was modified + * @throws NoSuchElementException if there is none + */ + public Object next() { - while (true) - { - loc += 2; - if (loc >= table.length) - throw new NoSuchElementException (); - if (table[loc] != tombstone && table[loc] != emptyslot) - { - ++seen; - return table[loc]; - } - } + if (knownMod != modCount) + throw new ConcurrentModificationException(); + if (count == 0) + throw new NoSuchElementException(); + count--; + + Object key; + do + { + loc -= 2; + key = table[loc]; + } + while (key == emptyslot || key == tombstone); + + return type == KEYS ? key : (type == VALUES ? table[loc + 1] + : new IdentityEntry(loc)); } - public void remove () + /** + * Removes from the backing Map the last element which was fetched + * with the <pre>next()</pre> method. + * @throws ConcurrentModificationException if the Map was modified + * @throws IllegalStateException if called when there is no last element + */ + public void remove() { - if (loc >= table.length - || table[loc] == tombstone - || table[loc] == emptyslot) - throw new IllegalStateException (); + if (knownMod != modCount) + throw new ConcurrentModificationException(); + if (loc == table.length || table[loc] == tombstone) + throw new IllegalStateException(); + modCount++; + size--; table[loc] = tombstone; table[loc + 1] = tombstone; - --size; + knownMod++; } - } + } // class IdentityIterator + + /** + * This class provides Map.Entry objects for IdentityHashMaps. The entry + * is fail-fast, and will throw a ConcurrentModificationException if + * the underlying map is modified, or if remove is called on the iterator + * that generated this object. It is identity based, so it violates + * the general contract of Map.Entry, and is probably unsuitable for + * comparison to normal maps; but it works among other IdentityHashMaps. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private final class IdentityEntry implements Map.Entry + { + /** The location of this entry. */ + final int loc; + /** The number of modifications to the backing Map that we know about. */ + final int knownMod = modCount; + + /** + * Constructs the Entry. + * + * @param loc the location of this entry in table + */ + IdentityEntry(int loc) + { + this.loc = loc; + } + + /** + * Compares the specified object with this entry, using identity + * semantics. Note that this can lead to undefined results with + * Entry objects created by normal maps. + * + * @param o the object to compare + * @return true if it is equal + * @throws ConcurrentModificationException if the entry was invalidated + * by modifying the Map or calling Iterator.remove() + */ + public boolean equals(Object o) + { + if (knownMod != modCount || table[loc] == tombstone) + throw new ConcurrentModificationException(); + if (! (o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry) o; + return table[loc] == e.getKey() && table[loc + 1] == e.getValue(); + } + + /** + * Returns the key of this entry. + * + * @return the key + * @throws ConcurrentModificationException if the entry was invalidated + * by modifying the Map or calling Iterator.remove() + */ + public Object getKey() + { + if (knownMod != modCount || table[loc] == tombstone) + throw new ConcurrentModificationException(); + return table[loc]; + } + + /** + * Returns the value of this entry. + * + * @return the value + * @throws ConcurrentModificationException if the entry was invalidated + * by modifying the Map or calling Iterator.remove() + */ + public Object getValue() + { + if (knownMod != modCount || table[loc] == tombstone) + throw new ConcurrentModificationException(); + return table[loc + 1]; + } + + /** + * Returns the hashcode of the entry, using identity semantics. + * Note that this can lead to undefined results with Entry objects + * created by normal maps. + * + * @return the hash code + * @throws ConcurrentModificationException if the entry was invalidated + * by modifying the Map or calling Iterator.remove() + */ + public int hashCode() + { + if (knownMod != modCount || table[loc] == tombstone) + throw new ConcurrentModificationException(); + return (System.identityHashCode(table[loc]) + ^ System.identityHashCode(table[loc + 1])); + } + + /** + * Replaces the value of this mapping, and returns the old value. + * + * @param value the new value + * @return the old value + * @throws ConcurrentModificationException if the entry was invalidated + * by modifying the Map or calling Iterator.remove() + */ + public Object setValue(Object value) + { + if (knownMod != modCount || table[loc] == tombstone) + throw new ConcurrentModificationException(); + Object r = table[loc + 1]; + table[loc + 1] = value; + return r; + } + + /** + * This provides a string representation of the entry. It is of the form + * "key=value", where string concatenation is used on key and value. + * + * @return the string representation + * @throws ConcurrentModificationException if the entry was invalidated + * by modifying the Map or calling Iterator.remove() + */ + public final String toString() + { + if (knownMod != modCount || table[loc] == tombstone) + throw new ConcurrentModificationException(); + return table[loc] + "=" + table[loc + 1]; + } + } // class IdentityEntry - private void readObject (ObjectInputStream s) + /** + * Reads the object from a serial stream. + * + * @param s the stream to read from + * @throws ClassNotFoundException if the underlying stream fails + * @throws IOException if the underlying stream fails + * @serialData expects the size (int), followed by that many key (Object) + * and value (Object) pairs, with the pairs in no particular + * order + */ + private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { - int num = s.readInt (); - for (int i = 0; i < num; ++i) - { - Object key = s.readObject (); - Object value = s.readObject (); - put (key, value); - } + s.defaultReadObject(); + + int num = s.readInt(); + table = new Object[2 * Math.max(num * 2, DEFAULT_CAPACITY)]; + // Read key/value pairs. + while (--num >= 0) + put(s.readObject(), s.readObject()); } - private void writeObject (ObjectOutputStream s) + /** + * Writes the object to a serial stream. + * + * @param s the stream to write to + * @throws IOException if the underlying stream fails + * @serialData outputs the size (int), followed by that many key (Object) + * and value (Object) pairs, with the pairs in no particular + * order + */ + private void writeObject(ObjectOutputStream s) throws IOException { - s.writeInt (size); - Iterator it = entrySet ().iterator (); - while (it.hasNext ()) + s.defaultWriteObject(); + s.writeInt(size); + for (int i = table.length - 2; i >= 0; i -= 2) { - Map.Entry entry = (Map.Entry) it.next (); - s.writeObject (entry.getKey ()); - s.writeObject (entry.getValue ()); + Object key = table[i]; + if (key != tombstone && key != emptyslot) + { + s.writeObject(key); + s.writeObject(table[i + 1]); + } } } - - // Compute the hash value we will use for an object. - private int getHash (Object o) - { - return 2 * Math.abs (System.identityHashCode (o) % (table.length / 2)); - } - - // Number of items in hash table. - private int size; - // The table itself. - private Object[] table; - - // This object is used to mark deleted items. - private static final Object tombstone = new Object (); - // This object is used to mark empty slots. We need this because - // using null is ambiguous. - private static final Object emptyslot = new Object (); } diff --git a/libjava/java/util/LinkedHashMap.java b/libjava/java/util/LinkedHashMap.java index 8950d58970b..8503e375ace 100644 --- a/libjava/java/util/LinkedHashMap.java +++ b/libjava/java/util/LinkedHashMap.java @@ -28,11 +28,6 @@ executable file might be covered by the GNU General Public License. */ package java.util; -import java.io.IOException; -import java.io.Serializable; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; - /** * This class provides a hashtable-backed implementation of the * Map interface, with predictable traversal order. @@ -89,6 +84,7 @@ import java.io.ObjectOutputStream; * @see TreeMap * @see Hashtable * @since 1.4 + * @status updated to 1.4 */ public class LinkedHashMap extends HashMap { @@ -218,8 +214,8 @@ public class LinkedHashMap extends HashMap * Construct a new insertion-ordered LinkedHashMap with a specific * inital capacity and default load factor of 0.75. * - * @param initialCapacity the initial capacity of this HashMap (>=0) - * @throws IllegalArgumentException if (initialCapacity < 0) + * @param initialCapacity the initial capacity of this HashMap (>= 0) + * @throws IllegalArgumentException if (initialCapacity < 0) */ public LinkedHashMap(int initialCapacity) { @@ -231,10 +227,10 @@ public class LinkedHashMap extends HashMap * Construct a new insertion-orderd LinkedHashMap with a specific * inital capacity and load factor. * - * @param initialCapacity the initial capacity (>=0) - * @param loadFactor the load factor (>0, not NaN) - * @throws IllegalArgumentException if (initialCapacity < 0) || - * ! (loadFactor > 0.0) + * @param initialCapacity the initial capacity (>= 0) + * @param loadFactor the load factor (> 0, not NaN) + * @throws IllegalArgumentException if (initialCapacity < 0) || + * ! (loadFactor > 0.0) */ public LinkedHashMap(int initialCapacity, float loadFactor) { @@ -281,7 +277,7 @@ public class LinkedHashMap extends HashMap LinkedHashEntry e = head; while (e != null) { - if (value == null ? e.value == null : value.equals(e.value)) + if (equals(value, e.value)) return true; e = e.succ; } @@ -307,7 +303,7 @@ public class LinkedHashMap extends HashMap HashEntry e = buckets[idx]; while (e != null) { - if (key == null ? e.key == null : key.equals(e.key)) + if (equals(key, e.key)) { if (accessOrder) { @@ -376,13 +372,14 @@ public class LinkedHashMap extends HashMap return false; } - /** Helper method called by <code>put</code>, which creates and adds a + /** + * Helper method called by <code>put</code>, which creates and adds a * new Entry, followed by performing bookkeeping (like removeEldestEntry). * * @param key the key of the new Entry * @param value the value * @param idx the index in buckets where the new Entry belongs - * @param callRemove Whether to call the removeEldestEntry method. + * @param callRemove whether to call the removeEldestEntry method * @see #put(Object, Object) * @see #removeEldestEntry(Map.Entry) */ @@ -397,6 +394,11 @@ public class LinkedHashMap extends HashMap remove(head); } + /** + * Helper method, called by clone() to reset the doubly-linked list. + * @param m the map to add entries from + * @see #clone() + */ void putAllInternal(Map m) { head = null; @@ -466,8 +468,8 @@ public class LinkedHashMap extends HashMap throw new IllegalStateException(); LinkedHashMap.this.remove(last.key); - knownMod++; last = null; + knownMod++; } }; } diff --git a/libjava/java/util/LinkedHashSet.java b/libjava/java/util/LinkedHashSet.java new file mode 100644 index 00000000000..f98c32e4b70 --- /dev/null +++ b/libjava/java/util/LinkedHashSet.java @@ -0,0 +1,149 @@ +/* LinkedHashSet.java -- a set backed by a LinkedHashMap, for linked + list traversal. + Copyright (C) 2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +As a special exception, if you link this library with other files to +produce an executable, this library does not by itself cause the +resulting executable to be covered by the GNU General Public License. +This exception does not however invalidate any other reasons why the +executable file might be covered by the GNU General Public License. */ + + +package java.util; + +import java.io.Serializable; + +/** + * This class provides a hashtable-backed implementation of the + * Set interface, with predictable traversal order. + * <p> + * + * It uses a hash-bucket approach; that is, hash collisions are handled + * by linking the new node off of the pre-existing node (or list of + * nodes). In this manner, techniques such as linear probing (which + * can cause primary clustering) and rehashing (which does not fit very + * well with Java's method of precomputing hash codes) are avoided. In + * addition, this maintains a doubly-linked list which tracks insertion + * order. Note that the insertion order is not modified if an + * <code>add</code> simply reinserts an element in the set. + * <p> + * + * One of the nice features of tracking insertion order is that you can + * copy a set, and regardless of the implementation of the original, + * produce the same results when iterating over the copy. This is possible + * without needing the overhead of <code>TreeSet</code>. + * <p> + * + * Under ideal circumstances (no collisions), LinkedHashSet offers O(1) + * performance on most operations. In the worst case (all elements map + * to the same hash code -- very unlikely), most operations are O(n). + * <p> + * + * LinkedHashSet accepts the null entry. It is not synchronized, so if + * you need multi-threaded access, consider using:<br> + * <code>Set s = Collections.synchronizedSet(new LinkedHashSet(...));</code> + * <p> + * + * The iterators are <i>fail-fast</i>, meaning that any structural + * modification, except for <code>remove()</code> called on the iterator + * itself, cause the iterator to throw a + * {@link ConcurrentModificationException} rather than exhibit + * non-deterministic behavior. + * + * @author Eric Blake <ebb9@email.byu.edu> + * @see Object#hashCode() + * @see Collection + * @see Set + * @see HashSet + * @see TreeSet + * @see Collections#synchronizedSet(Set) + * @since 1.4 + * @status updated to 1.4 + */ +public class LinkedHashSet extends HashSet + implements Set, Cloneable, Serializable +{ + /** + * Compatible with JDK 1.4. + */ + private static final long serialVersionUID = -2851667679971038690L; + + /** + * Construct a new, empty HashSet whose backing HashMap has the default + * capacity (11) and loadFacor (0.75). + */ + public LinkedHashSet() + { + super(); + } + + /** + * Construct a new, empty HashSet whose backing HashMap has the supplied + * capacity and the default load factor (0.75). + * + * @param initialCapacity the initial capacity of the backing HashMap + * @throws IllegalArgumentException if the capacity is negative + */ + public LinkedHashSet(int initialCapacity) + { + super(initialCapacity); + } + + /** + * Construct a new, empty HashSet whose backing HashMap has the supplied + * capacity and load factor. + * + * @param initialCapacity the initial capacity of the backing HashMap + * @param loadFactor the load factor of the backing HashMap + * @throws IllegalArgumentException if either argument is negative, or + * if loadFactor is POSITIVE_INFINITY or NaN + */ + public LinkedHashSet(int initialCapacity, float loadFactor) + { + super(initialCapacity, loadFactor); + } + + /** + * Construct a new HashSet with the same elements as are in the supplied + * collection (eliminating any duplicates, of course). The backing storage + * has twice the size of the collection, or the default size of 11, + * whichever is greater; and the default load factor (0.75). + * + * @param c a collection of initial set elements + * @throws NullPointerException if c is null + */ + public LinkedHashSet(Collection c) + { + super(c); + } + + /** + * Helper method which initializes the backing Map. + * + * @param capacity the initial capacity + * @param load the initial load factor + * @return the backing HashMap + */ + HashMap init(int capacity, float load) + { + return new LinkedHashMap(capacity, load); + } + +} diff --git a/libjava/java/util/LinkedList.java b/libjava/java/util/LinkedList.java index 41ca0936cf2..f2f333c4ccf 100644 --- a/libjava/java/util/LinkedList.java +++ b/libjava/java/util/LinkedList.java @@ -1,5 +1,5 @@ /* LinkedList.java -- Linked list implementation of the List interface - Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc. + Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -32,25 +32,50 @@ import java.io.ObjectInputStream; import java.io.IOException; import java.lang.reflect.Array; -// TO DO: -// ~ Doc comment for the class. -// ~ Doc comments for the non-list methods. -// ~ other general implementation notes. - /** - * Linked list implementation of the List interface. + * Linked list implementation of the List interface. In addition to the + * methods of the List interface, this class provides access to the first + * and last list elements in O(1) time for easy stack, queue, or double-ended + * queue (deque) creation. The list is doubly-linked, with traversal to a + * given index starting from the end closest to the element.<p> + * + * LinkedList is not synchronized, so if you need multi-threaded access, + * consider using:<br> + * <code>List l = Collections.synchronizedList(new LinkedList(...));</code> + * <p> + * + * The iterators are <i>fail-fast</i>, meaning that any structural + * modification, except for <code>remove()</code> called on the iterator + * itself, cause the iterator to throw a + * {@link ConcurrentModificationException} rather than exhibit + * non-deterministic behavior. + * + * @author Original author unknown + * @author Bryce McKinlay + * @author Eric Blake <ebb9@email.byu.edu> + * @see List + * @see ArrayList + * @see Vector + * @see Collections#synchronizedList(List) + * @since 1.2 + * @status missing javadoc, but complete to 1.4 */ public class LinkedList extends AbstractSequentialList implements List, Cloneable, Serializable { - static final long serialVersionUID = 876323262645176354L; + /** + * Compatible with JDK 1.2. + */ + private static final long serialVersionUID = 876323262645176354L; /** - * An Entry containing the head (in the next field) and the tail (in the - * previous field) of the list. The data field is null. If the list is empty, - * both the head and the tail point to ends itself. + * The first element in the list. */ transient Entry first; + + /** + * The last element in the list. + */ transient Entry last; /** @@ -61,18 +86,27 @@ public class LinkedList extends AbstractSequentialList /** * Class to represent an entry in the list. Holds a single element. */ - private static class Entry + private static final class Entry { + /** The element in the list. */ Object data; + + /** The next list entry, null if this is last. */ Entry next; + + /** The previous list entry, null if this is first. */ Entry previous; - + + /** + * Construct an entry. + * @param data the list element + */ Entry(Object data) { this.data = data; } - } - + } // class Entry + /** * Obtain the Entry at a given position in a list. This method of course * takes linear time, but it is intelligent enough to take the shorter of the @@ -82,7 +116,8 @@ public class LinkedList extends AbstractSequentialList * For speed and flexibility, range checking is not done in this method: * Incorrect values will be returned if (n < 0) or (n >= size). * - * @param n the number of the entry to get. + * @param n the number of the entry to get + * @return the entry at position n */ private Entry getEntry(int n) { @@ -90,50 +125,76 @@ public class LinkedList extends AbstractSequentialList if (n < size / 2) { e = first; - // n less than size/2, iterate from start - while (n-- > 0) - { - e = e.next; - } + // n less than size/2, iterate from start + while (n-- > 0) + e = e.next; } else { - e = last; - // n greater than size/2, iterate from end - while (++n < size) - { - e = e.previous; - } + e = last; + // n greater than size/2, iterate from end + while (++n < size) + e = e.previous; } return e; } - - /** Remove an entry from the list. This will adjust size and deal with - * `first' and `last' appropriatly. It does not effect modCount, that is - * the responsibility of the caller. */ + + /** + * Remove an entry from the list. This will adjust size and deal with + * `first' and `last' appropriatly. + * + * @param e the entry to remove + */ private void removeEntry(Entry e) { - if (size == 1) + modCount++; + size--; + if (size == 0) first = last = null; else { - if (e == first) - { - first = e.next; - e.next.previous = null; - } - else if (e == last) - { - last = e.previous; - e.previous.next = null; - } - else - { - e.next.previous = e.previous; - e.previous.next = e.next; - } + if (e == first) + { + first = e.next; + e.next.previous = null; + } + else if (e == last) + { + last = e.previous; + e.previous.next = null; + } + else + { + e.next.previous = e.previous; + e.previous.next = e.next; + } } - size--; + } + + /** + * Checks that the index is in the range of possible elements (inclusive). + * + * @param index the index to check + * @throws IndexOutOfBoundsException if index < 0 || index > size + */ + private void checkBoundsInclusive(int index) + { + if (index < 0 || index > size) + throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + + size); + } + + /** + * Checks that the index is in the range of existing elements (exclusive). + * + * @param index the index to check + * @throws IndexOutOfBoundsException if index < 0 || index >= size + */ + private void checkBoundsExclusive(int index) + { + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + + size); } /** @@ -141,24 +202,26 @@ public class LinkedList extends AbstractSequentialList */ public LinkedList() { - super(); } /** * Create a linked list containing the elements, in order, of a given * collection. * - * @param c the collection to populate this list from. + * @param c the collection to populate this list from + * @throws NullPointerException if c is null */ public LinkedList(Collection c) { - super(); - // Note: addAll could be made slightly faster, but not enough so to justify - // re-implementing it from scratch. It is just a matter of a relatively - // small constant factor. addAll(c); } + /** + * Returns the first element in the list. + * + * @return the first list element + * @throws NoSuchElementException if the list is empty + */ public Object getFirst() { if (size == 0) @@ -166,6 +229,12 @@ public class LinkedList extends AbstractSequentialList return first.data; } + /** + * Returns the last element in the list. + * + * @return the last list element + * @throws NoSuchElementException if the list is empty + */ public Object getLast() { if (size == 0) @@ -173,273 +242,374 @@ public class LinkedList extends AbstractSequentialList return last.data; } + /** + * Remove and return the first element in the list. + * + * @return the former first element in the list + * @throws NoSuchElementException if the list is empty + */ public Object removeFirst() { if (size == 0) throw new NoSuchElementException(); - size--; modCount++; + size--; Object r = first.data; - + if (first.next != null) first.next.previous = null; else last = null; first = first.next; - + return r; } + /** + * Remove and return the last element in the list. + * + * @return the former last element in the list + * @throws NoSuchElementException if the list is empty + */ public Object removeLast() { if (size == 0) throw new NoSuchElementException(); - size--; modCount++; + size--; Object r = last.data; - + if (last.previous != null) last.previous.next = null; else first = null; - + last = last.previous; - + return r; } + /** + * Insert an element at the first of the list. + * + * @param o the element to insert + */ public void addFirst(Object o) { - modCount++; Entry e = new Entry(o); - + + modCount++; if (size == 0) first = last = e; else { - e.next = first; + e.next = first; first.previous = e; - first = e; - } + first = e; + } size++; } + /** + * Insert an element at the last of the list. + * + * @param o the element to insert + */ public void addLast(Object o) { - modCount++; addLastEntry(new Entry(o)); } - + + /** + * Inserts an element at the end of the list. + * + * @param e the entry to add + */ private void addLastEntry(Entry e) { + modCount++; if (size == 0) first = last = e; else { - e.previous = last; + e.previous = last; last.next = e; - last = e; + last = e; } size++; } + /** + * Returns true if the list contains the given object. Comparison is done by + * <code>o == null ? e = null : o.equals(e)</code>. + * + * @param o the element to look for + * @return true if it is found + */ public boolean contains(Object o) { Entry e = first; while (e != null) { - if (e.data == null ? o == null : o.equals(e.data)) - return true; + if (equals(o, e.data)) + return true; e = e.next; } return false; } + /** + * Returns the size of the list. + * + * @return the list size + */ public int size() { return size; } - + + /** + * Adds an element to the end of the list. + * + * @param e the entry to add + * @return true, as it always succeeds + */ public boolean add(Object o) { - modCount++; addLastEntry(new Entry(o)); return true; } - + + /** + * Removes the entry at the lowest index in the list that matches the given + * object, comparing by <code>o == null ? e = null : o.equals(e)</code>. + * + * @param o the object to remove + * @return true if an instance of the object was removed + */ public boolean remove(Object o) { - modCount++; Entry e = first; while (e != null) { - if (e.data == null ? o == null : o.equals(e.data)) - { - removeEntry(e); - return true; - } + if (equals(o, e.data)) + { + removeEntry(e); + return true; + } e = e.next; } return false; } + /** + * Append the elements of the collection in iteration order to the end of + * this list. If this list is modified externally (for example, if this + * list is the collection), behavior is unspecified. + * + * @param c the collection to append + * @return true if the list was modified + * @throws NullPointerException if c is null + */ public boolean addAll(Collection c) { return addAll(size, c); } - + + /** + * Insert the elements of the collection in iteration order at the given + * index of this list. If this list is modified externally (for example, + * if this list is the collection), behavior is unspecified. + * + * @param c the collection to append + * @return true if the list was modified + * @throws NullPointerException if c is null + * @throws IndexOutOfBoundsException if index < 0 || index > size() + */ public boolean addAll(int index, Collection c) { - if (index < 0 || index > size) - throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + - size); - modCount++; + checkBoundsInclusive(index); int csize = c.size(); if (csize == 0) return false; Iterator itr = c.iterator(); - + // Get the entries just before and after index. If index is at the start - // of the list, BEFORE is null. If index is at the end of thelist, AFTER is - // null. If the list is empty, both are null. + // of the list, BEFORE is null. If index is at the end of the list, AFTER + // is null. If the list is empty, both are null. Entry after = null; - Entry before = null; + Entry before = null; if (index != size) { - after = getEntry(index); - before = after.previous; + after = getEntry(index); + before = after.previous; } else before = last; - + // Create the first new entry. We do not yet set the link from `before' - // to the first entry, in order to deal with the case where (c == this). - // [Actually, we don't have to handle this case to fufill the + // to the first entry, in order to deal with the case where (c == this). + // [Actually, we don't have to handle this case to fufill the // contract for addAll(), but Sun's implementation appears to.] Entry e = new Entry(itr.next()); e.previous = before; Entry prev = e; Entry firstNew = e; - + // Create and link all the remaining entries. for (int pos = 1; pos < csize; pos++) { e = new Entry(itr.next()); - e.previous = prev; - prev.next = e; - prev = e; + e.previous = prev; + prev.next = e; + prev = e; } + // Link the new chain of entries into the list. + modCount++; + size += csize; prev.next = after; if (after != null) after.previous = e; else last = e; - + if (before != null) before.next = firstNew; else first = firstNew; - - size += csize; return true; } + /** + * Remove all elements from this list. + */ public void clear() { - modCount++; - first = null; - last = null; - size = 0; + if (size > 0) + { + modCount++; + first = null; + last = null; + size = 0; + } } + /** + * Return the element at index. + * + * @param index the place to look + * @return the element at index + * @throws IndexOutOfBoundsException if index < 0 || index >= size() + */ public Object get(int index) { - if (index < 0 || index >= size) - throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + - size); - Entry e = getEntry(index); - return e.data; + checkBoundsExclusive(index); + return getEntry(index).data; } - + + /** + * Replace the element at the given location in the list. + * + * @param index which index to change + * @param o the new element + * @return the prior element + * @throws IndexOutOfBoundsException if index < 0 || index >= size() + */ public Object set(int index, Object o) { - if (index < 0 || index >= size) - throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + - size); + checkBoundsExclusive(index); Entry e = getEntry(index); Object old = e.data; e.data = o; return old; } + /** + * Inserts an element in the given position in the list. + * + * @param index where to insert the element + * @param o the element to insert + * @throws IndexOutOfBoundsException if index < 0 || index > size() + */ public void add(int index, Object o) { - if (index < 0 || index > size) - throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + - size); - modCount++; - addEntry(index, new Entry(o)); - } - - private void addEntry(int index, Entry e) - { + checkBoundsInclusive(index); + Entry e = new Entry(o); + if (index < size) { - Entry after = getEntry(index); - e.next = after; - e.previous = after.previous; - if (after.previous == null) - first = e; - else - after.previous.next = e; - after.previous = e; - size++; + modCount++; + Entry after = getEntry(index); + e.next = after; + e.previous = after.previous; + if (after.previous == null) + first = e; + else + after.previous.next = e; + after.previous = e; + size++; } else addLastEntry(e); } - + + /** + * Removes the element at the given position from the list. + * + * @param index the location of the element to remove + * @return the removed element + * @throws IndexOutOfBoundsException if index < 0 || index > size() + */ public Object remove(int index) { - if (index < 0 || index >= size) - throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + - size); - modCount++; + checkBoundsExclusive(index); Entry e = getEntry(index); removeEntry(e); return e.data; } - + + /** + * Returns the first index where the element is located in the list, or -1. + * + * @param o the element to look for + * @return its position, or -1 if not found + */ public int indexOf(Object o) { int index = 0; Entry e = first; while (e != null) { - if (e.data == null ? o == null : o.equals(e.data)) - return index; - ++index; + if (equals(o, e.data)) + return index; + index++; e = e.next; } return -1; } - + + /** + * Returns the last index where the element is located in the list, or -1. + * + * @param o the element to look for + * @return its position, or -1 if not found + */ public int lastIndexOf(Object o) { int index = size - 1; Entry e = last; while (e != null) { - if (e.data == null ? o == null : o.equals(e.data)) - return index; - --index; + if (equals(o, e.data)) + return index; + index--; e = e.previous; } - return -1; + return -1; } /** @@ -448,28 +618,27 @@ public class LinkedList extends AbstractSequentialList * methods. * * @param index the index of the element to be returned by the first call to - * next(), or size() to be initially positioned at the end of the list. - * @exception IndexOutOfBoundsException if index < 0 || index > size(). + * next(), or size() to be initially positioned at the end of the list + * @throws IndexOutOfBoundsException if index < 0 || index > size() */ public ListIterator listIterator(int index) { - if (index < 0 || index > size) - throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + - size); + checkBoundsInclusive(index); return new LinkedListItr(index); } /** - * Create a shallow copy of this LinkedList. + * Create a shallow copy of this LinkedList (the elements are not cloned). + * * @return an object of the same class as this object, containing the - * same elements in the same order. + * same elements in the same order */ public Object clone() { LinkedList copy = null; try { - copy = (LinkedList) super.clone(); + copy = (LinkedList) super.clone(); } catch (CloneNotSupportedException ex) { @@ -478,7 +647,12 @@ public class LinkedList extends AbstractSequentialList copy.addAll(this); return copy; } - + + /** + * Returns an array which contains the elements of the list in order. + * + * @return an array containing the list elements + */ public Object[] toArray() { Object[] array = new Object[size]; @@ -490,182 +664,282 @@ public class LinkedList extends AbstractSequentialList } return array; } - - public Object[] toArray(Object[] array) + + /** + * Returns an Array whose component type is the runtime component type of + * the passed-in Array. The returned Array is populated with all of the + * elements in this LinkedList. If the passed-in Array is not large enough + * to store all of the elements in this List, a new Array will be created + * and returned; if the passed-in Array is <i>larger</i> than the size + * of this List, then size() index will be set to null. + * + * @param a the passed-in Array + * @return an array representation of this list + * @throws ArrayStoreException if the runtime type of a does not allow + * an element in this list + * @throws NullPointerException if a is null + */ + public Object[] toArray(Object[] a) { - if (array.length < size) - array = (Object[]) Array.newInstance(array.getClass().getComponentType(), - size); - else if (array.length > size) - array[size] = null; + if (a.length < size) + a = (Object[]) Array.newInstance(a.getClass().getComponentType(), size); + else if (a.length > size) + a[size] = null; Entry e = first; for (int i = 0; i < size; i++) { - array[i] = e.data; + a[i] = e.data; e = e.next; } - return array; + return a; } /** - * Serialize an object to a stream. - * @serialdata the size of the list (int), followed by all the elements - * (Object) in proper order. + * Serializes this object to the given stream. + * + * @param s the stream to write to + * @throws IOException if the underlying stream fails + * @serialData the size of the list (int), followed by all the elements + * (Object) in proper order */ private void writeObject(ObjectOutputStream s) throws IOException { + s.defaultWriteObject(); s.writeInt(size); - Iterator itr = iterator(); - for (int i = 0; i < size; i++) - s.writeObject(itr.next()); + Entry e = first; + while (e != null) + { + s.writeObject(e.data); + e = e.next; + } } /** - * Deserialize an object from a stream. - * @serialdata the size of the list (int), followed by all the elements - * (Object) in proper order. + * Deserializes this object from the given stream. + * + * @param s the stream to read from + * @throws ClassNotFoundException if the underlying stream fails + * @throws IOException if the underlying stream fails + * @serialData the size of the list (int), followed by all the elements + * (Object) in proper order */ private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { - int serialSize = s.readInt(); - for (int i = 0; i < serialSize; i++) + s.defaultReadObject(); + int i = s.readInt(); + while (--i >= 0) addLastEntry(new Entry(s.readObject())); } - - /** A ListIterator over the list. This class keeps track of its + + /** + * A ListIterator over the list. This class keeps track of its * position in the list and the two list entries it is between. + * + * @author Original author unknown + * @author Eric Blake <ebb9@email.byu.edu> */ - class LinkedListItr implements ListIterator + private final class LinkedListItr implements ListIterator { - int knownMod; - Entry next; // entry that will be returned by next(). - Entry previous; // entry that will be returned by previous(). - Entry lastReturned; // entry that will be affected by remove() or set(). - int position; // index of `next'. + /** Number of modifications we know about. */ + private int knownMod = modCount; + + /** Entry that will be returned by next(). */ + private Entry next; + + /** Entry that will be returned by previous(). */ + private Entry previous; + /** Entry that will be affected by remove() or set(). */ + private Entry lastReturned; + + /** Index of `next'. */ + private int position; + + /** + * Initialize the iterator. + * + * @param index the initial index + */ LinkedListItr(int index) { if (index == size) { next = null; - previous = last; - } + previous = last; + } else { next = getEntry(index); - previous = next.previous; - } + previous = next.previous; + } position = index; - knownMod = modCount; } + /** + * Checks for iterator consistency. + * + * @throws ConcurrentModificationException if the list was modified + */ private void checkMod() { if (knownMod != modCount) - throw new ConcurrentModificationException(); + throw new ConcurrentModificationException(); } + /** + * Returns the index of the next element. + * + * @return the next index + * @throws ConcurrentModificationException if the list was modified + */ public int nextIndex() { checkMod(); return position; } + /** + * Returns the index of the previous element. + * + * @return the previous index + * @throws ConcurrentModificationException if the list was modified + */ public int previousIndex() { checkMod(); return position - 1; } + /** + * Returns true if more elements exist via next. + * + * @return true if next will succeed + * @throws ConcurrentModificationException if the list was modified + */ public boolean hasNext() { checkMod(); return (next != null); } + /** + * Returns true if more elements exist via previous. + * + * @return true if previous will succeed + * @throws ConcurrentModificationException if the list was modified + */ public boolean hasPrevious() { checkMod(); return (previous != null); } + /** + * Returns the next element. + * + * @return the next element + * @throws ConcurrentModificationException if the list was modified + * @throws NoSuchElementException if there is no next + */ public Object next() { checkMod(); if (next == null) - throw new NoSuchElementException(); + throw new NoSuchElementException(); position++; lastReturned = previous = next; next = lastReturned.next; return lastReturned.data; } + /** + * Returns the previous element. + * + * @return the previous element + * @throws ConcurrentModificationException if the list was modified + * @throws NoSuchElementException if there is no previous + */ public Object previous() { checkMod(); if (previous == null) - throw new NoSuchElementException(); + throw new NoSuchElementException(); position--; lastReturned = next = previous; previous = lastReturned.previous; return lastReturned.data; } + /** + * Remove the most recently returned element from the list. + * + * @throws ConcurrentModificationException if the list was modified + * @throws IllegalStateException if there was no last element + */ public void remove() { checkMod(); if (lastReturned == null) - throw new IllegalStateException(); + throw new IllegalStateException(); // Adjust the position to before the removed element, if the element // being removed is behind the cursor. if (lastReturned == previous) - position--; + position--; next = lastReturned.next; previous = lastReturned.previous; - modCount++; - knownMod++; removeEntry(lastReturned); - + knownMod++; + lastReturned = null; } + /** + * Adds an element between the previous and next, and advance to the next. + * + * @param o the element to add + * @throws ConcurrentModificationException if the list was modified + */ public void add(Object o) { checkMod(); modCount++; knownMod++; + size++; + position++; Entry e = new Entry(o); e.previous = previous; e.next = next; if (previous != null) - previous.next = e; + previous.next = e; else - first = e; + first = e; if (next != null) - { - next.previous = e; - next = next.next; - } + next.previous = e; else - last = e; + last = e; previous = e; - size++; - position++; lastReturned = null; } + /** + * Changes the contents of the element most recently returned. + * + * @param o the new element + * @throws ConcurrentModificationException if the list was modified + * @throws IllegalStateException if there was no last element + */ public void set(Object o) { checkMod(); if (lastReturned == null) - throw new IllegalStateException(); + throw new IllegalStateException(); lastReturned.data = o; } - } // class LinkedListItr + } // class LinkedListItr } diff --git a/libjava/java/util/List.java b/libjava/java/util/List.java index f2ee49cd260..a000604de49 100644 --- a/libjava/java/util/List.java +++ b/libjava/java/util/List.java @@ -190,7 +190,7 @@ public interface List extends Collection * @see Object#equals(Object) * @see #hashCode() */ - boolean equals(Object o); + /* boolean equals(Object o);*/ /** * Get the element at a given index in this list. @@ -288,7 +288,7 @@ public interface List extends Collection Object remove(int index); /** - * Remove the first occurrence of an object from this list (optional + * Remove the first occurence of an object from this list (optional * operation). That is, remove the first element e such that * <code>o == null ? e == null : o.equals(e)</code>. * diff --git a/libjava/java/util/Stack.java b/libjava/java/util/Stack.java index 8647fa48b08..a7b3f5d4e70 100644 --- a/libjava/java/util/Stack.java +++ b/libjava/java/util/Stack.java @@ -32,24 +32,30 @@ package java.util; * "The Java Language Specification", ISBN 0-201-63451-1 * plus online API docs for JDK 1.2 beta from http://www.javasoft.com. * Status: Believed complete and correct - */ /** * Stack provides a Last In First Out (LIFO) data type, commonly known - * as a Stack. - * - * Stack itself extends Vector and provides the additional methods - * for stack manipulation (push, pop, peek). + * as a Stack. Stack itself extends Vector and provides the additional + * methods for stack manipulation (push, pop, peek). You can also seek for + * the 1-based position of an element on the stack. * * @author Warren Levy <warrenl@cygnus.com> - * @date August 20, 1998. + * @author Eric Blake <ebb9@email.byu.edu> + * @see List + * @see AbstractList + * @see LinkedList + * @since 1.0 + * @status updated to 1.4 */ public class Stack extends Vector { - // Could use Vector methods internally for the following methods + // We could use Vector methods internally for the following methods, // but have used Vector fields directly for efficiency (i.e. this // often reduces out duplicate bounds checking). + /** + * Compatible with JDK 1.0+. + */ private static final long serialVersionUID = 1224463164541339165L; /** @@ -57,16 +63,15 @@ public class Stack extends Vector */ public Stack() { - super(); } /** * Pushes an Object onto the top of the stack. This method is effectively - * the same as addElement(item) + * the same as addElement(item). * * @param item the Object to push onto the stack - * @returns the Object pushed onto the stack - * @see java.util.Vector#addElement(java.util.Object) + * @return the Object pushed onto the stack + * @see Vector#addElement(Object) */ public Object push(Object item) { @@ -80,26 +85,29 @@ public class Stack extends Vector /** * Pops an item from the stack and returns it. The item popped is - * removed from the Stack + * removed from the Stack. * - * @returns the Object popped from the stack + * @return the Object popped from the stack + * @throws EmptyStackException if the stack is empty */ public synchronized Object pop() { if (elementCount == 0) throw new EmptyStackException(); + modCount++; Object obj = elementData[--elementCount]; - // Set topmost element to null to assist the gc in cleanup + // Set topmost element to null to assist the gc in cleanup. elementData[elementCount] = null; return obj; } /** - * Returns the top Object on the stack without removing it + * Returns the top Object on the stack without removing it. * - * @returns the top Object on the stack + * @return the top Object on the stack + * @throws EmptyStackException if the stack is empty */ public synchronized Object peek() { @@ -110,11 +118,11 @@ public class Stack extends Vector } /** - * Tests if the stack is empty + * Tests if the stack is empty. * - * @returns true if the stack contains no items, false otherwise + * @return true if the stack contains no items, false otherwise */ - public boolean empty() + public synchronized boolean empty() { return elementCount == 0; } @@ -122,18 +130,18 @@ public class Stack extends Vector /** * Returns the position of an Object on the stack, with the top * most Object being at position 1, and each Object deeper in the - * stack at depth + 1 + * stack at depth + 1. * * @param o The object to search for - * @returns The 1 based depth of the Object, or -1 if the Object - * is not on the stack. + * @return The 1 based depth of the Object, or -1 if the Object + * is not on the stack */ public synchronized int search(Object o) { - for (int i = elementCount-1; i >=0; --i) - if (elementData[i].equals(o)) + int i = elementCount; + while (--i >= 0) + if (equals(o, elementData[i])) return elementCount - i; - return -1; } } diff --git a/libjava/java/util/TreeMap.java b/libjava/java/util/TreeMap.java index 59d6079e32a..83386d6e54b 100644 --- a/libjava/java/util/TreeMap.java +++ b/libjava/java/util/TreeMap.java @@ -38,80 +38,166 @@ import java.io.IOException; * interface. Elements in the Map will be sorted by either a user-provided * Comparator object, or by the natural ordering of the keys. * - * The algorithms are adopted from Corman, Leiserson, - * and Rivest's <i>Introduction to Algorithms.</i> In other words, - * I cribbed from the same pseudocode as Sun. <em>Any similarity - * between my code and Sun's (if there is any -- I have never looked - * at Sun's) is a result of this fact.</em> - * - * TreeMap guarantees O(log n) insertion and deletion of elements. That - * being said, there is a large enough constant coefficient in front of - * that "log n" (overhead involved in keeping the tree - * balanced), that TreeMap may not be the best choice for small - * collections. + * The algorithms are adopted from Corman, Leiserson, and Rivest's + * <i>Introduction to Algorithms.</i> TreeMap guarantees O(log n) + * insertion and deletion of elements. That being said, there is a large + * enough constant coefficient in front of that "log n" (overhead involved + * in keeping the tree balanced), that TreeMap may not be the best choice + * for small collections. If something is already sorted, you may want to + * just use a LinkedHashMap to maintain the order while providing O(1) access. * * TreeMap is a part of the JDK1.2 Collections API. Null keys are allowed - * only if a Comparator is used which can deal with them. Null values are - * always allowed. + * only if a Comparator is used which can deal with them; natural ordering + * cannot cope with null. Null values are always allowed. Note that the + * ordering must be <i>consistent with equals</i> to correctly implement + * the Map interface. If this condition is violated, the map is still + * well-behaved, but you may have suprising results when comparing it to + * other maps.<p> + * + * This implementation is not synchronized. If you need to share this between + * multiple threads, do something like:<br> + * <code>SortedMap m + * = Collections.synchronizedSortedMap(new TreeMap(...));</code><p> + * + * The iterators are <i>fail-fast</i>, meaning that any structural + * modification, except for <code>remove()</code> called on the iterator + * itself, cause the iterator to throw a + * <code>ConcurrentModificationException</code> rather than exhibit + * non-deterministic behavior. * - * @author Jon Zeppieri - * @author Bryce McKinlay + * @author Jon Zeppieri + * @author Bryce McKinlay + * @author Eric Blake <ebb9@email.byu.edu> + * @see Map + * @see HashMap + * @see Hashtable + * @see LinkedHashMap + * @see Comparable + * @see Comparator + * @see Collection + * @see Collections#synchronizedSortedMap(SortedMap) + * @since 1.2 + * @status updated to 1.4 */ public class TreeMap extends AbstractMap implements SortedMap, Cloneable, Serializable { - private static final int RED = -1, - BLACK = 1; + // Implementation note: + // A red-black tree is a binary search tree with the additional properties + // that all paths to a leaf node visit the same number of black nodes, + // and no red node has red children. To avoid some null-pointer checks, + // we use the special node nil which is always black, has no relatives, + // and has key and value of null (but is not equal to a mapping of null). - /** Sentinal node, used to avoid null checks for corner cases and make the - delete rebalance code simpler. Note that this must not be static, due - to thread-safety concerns. */ - transient Node nil = new Node(null, null); + /** + * Compatible with JDK 1.2. + */ + private static final long serialVersionUID = 919286545866124006L; - /** The root node of this TreeMap */ - transient Node root = nil; + /** + * Color status of a node. Package visible for use by nested classes. + */ + static final int RED = -1, + BLACK = 1; - /** The size of this TreeMap */ - transient int size = 0; + /** + * Sentinal node, used to avoid null checks for corner cases and make the + * delete rebalance code simpler. The rebalance code must never assign + * the parent, left, or right of nil, but may safely reassign the color + * to be black. This object must never be used as a key in a TreeMap, or + * it will break bounds checking of a SubMap. + */ + static final Node nil = new Node(null, null, BLACK); + static + { + // Nil is self-referential, so we must initialize it after creation. + nil.parent = nil; + nil.left = nil; + nil.right = nil; + } - /** Number of modifications */ - transient int modCount = 0; + /** + * The root node of this TreeMap. + */ + private transient Node root = nil; + + /** + * The size of this TreeMap. Package visible for use by nested classes. + */ + transient int size; + + /** + * The cache for {@link #entrySet()}. + */ + private transient Set entries; - /** This TreeMap's comparator, if any. */ - Comparator comparator = null; + /** + * Counts the number of modifications this TreeMap has undergone, used + * by Iterators to know when to throw ConcurrentModificationExceptions. + * Package visible for use by nested classes. + */ + transient int modCount; - static final long serialVersionUID = 919286545866124006L; + /** + * This TreeMap's comparator, or null for natural ordering. + * Package visible for use by nested classes. + * @serial the comparator ordering this tree, or null + */ + final Comparator comparator; - private static class Node extends BasicMapEntry implements Map.Entry + /** + * Class to represent an entry in the tree. Holds a single key-value pair, + * plus pointers to parent and child nodes. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private static final class Node extends BasicMapEntry { + // All fields package visible for use by nested classes. + /** The color of this node. */ int color; - Node left; - Node right; - Node parent; - Node(Object key, Object value) + /** The left child node. */ + Node left = nil; + /** The right child node. */ + Node right = nil; + /** The parent node. */ + Node parent = nil; + + /** + * Simple constructor. + * @param key the key + * @param value the value + */ + Node(Object key, Object value, int color) { super(key, value); - this.color = BLACK; + this.color = color; } } /** - * Instantiate a new TreeMap with no elements, using the keys' - * natural ordering to sort. + * Instantiate a new TreeMap with no elements, using the keys' natural + * ordering to sort. All entries in the map must have a key which implements + * Comparable, and which are <i>mutually comparable</i>, otherwise map + * operations may throw a {@link ClassCastException}. Attempts to use + * a null key will throw a {@link NullPointerException}. * - * @see java.lang.Comparable + * @see Comparable */ public TreeMap() { + this((Comparator) null); } /** - * Instantiate a new TreeMap with no elements, using the provided - * comparator to sort. + * Instantiate a new TreeMap with no elements, using the provided comparator + * to sort. All entries in the map must have keys which are mutually + * comparable by the Comparator, otherwise map operations may throw a + * {@link ClassCastException}. * - * @param oComparator a Comparator object, used to sort - * the keys of this SortedMap + * @param comparator the sort order for the keys of this map, or null + * for the natural order */ public TreeMap(Comparator c) { @@ -119,62 +205,70 @@ public class TreeMap extends AbstractMap } /** - * Instantiate a new TreeMap, initializing it with all of the - * elements in the provided Map. The elements will be sorted - * using the natural ordering of the keys. - * - * @param map a Map, whose keys will be put into - * this TreeMap + * Instantiate a new TreeMap, initializing it with all of the elements in + * the provided Map. The elements will be sorted using the natural + * ordering of the keys. This algorithm runs in n*log(n) time. All entries + * in the map must have keys which implement Comparable and are mutually + * comparable, otherwise map operations may throw a + * {@link ClassCastException}. * - * @throws ClassCastException if the keys in the provided - * Map do not implement - * Comparable - * - * @see java.lang.Comparable + * @param map a Map, whose entries will be put into this TreeMap + * @throws ClassCastException if the keys in the provided Map are not + * comparable + * @throws NullPointerException if map is null + * @see Comparable */ public TreeMap(Map map) { + this((Comparator) null); putAll(map); } - /** - * Instantiate a new TreeMap, initializing it with all of the - * elements in the provided SortedMap. The elements will be sorted - * using the same method as in the provided SortedMap. + /** + * Instantiate a new TreeMap, initializing it with all of the elements in + * the provided SortedMap. The elements will be sorted using the same + * comparator as in the provided SortedMap. This runs in linear time. + * + * @param sm a SortedMap, whose entries will be put into this TreeMap + * @throws NullPointerException if sm is null */ public TreeMap(SortedMap sm) { this(sm.comparator()); - - int sm_size = sm.size(); + int pos = sm.size(); Iterator itr = sm.entrySet().iterator(); - fabricateTree(sm_size); + fabricateTree(pos); Node node = firstNode(); - - for (int i = 0; i < sm_size; i++) + + while (--pos >= 0) { - Map.Entry me = (Map.Entry) itr.next(); - node.key = me.getKey(); - node.value = me.getValue(); - node = successor(node); + Map.Entry me = (Map.Entry) itr.next(); + node.key = me.getKey(); + node.value = me.getValue(); + node = successor(node); } } - public int size() - { - return size; - } - + /** + * Clears the Map so it has no keys. This is O(1). + */ public void clear() { - modCount++; - root = nil; - // nil node could have a residual parent reference, clear it for GC. - nil.parent = null; - size = 0; + if (size > 0) + { + modCount++; + root = nil; + size = 0; + } } + /** + * Returns a shallow clone of this TreeMap. The Map itself is cloned, + * but its contents are not. + * + * @return the clone + */ public Object clone() { TreeMap copy = null; @@ -185,547 +279,684 @@ public class TreeMap extends AbstractMap catch (CloneNotSupportedException x) { } - // Each instance must have a unique sentinal. - copy.nil = new Node(null, null); + copy.entries = null; copy.fabricateTree(size); Node node = firstNode(); Node cnode = copy.firstNode(); - + while (node != nil) { cnode.key = node.key; - cnode.value = node.value; - node = successor(node); - cnode = copy.successor(cnode); + cnode.value = node.value; + node = successor(node); + cnode = copy.successor(cnode); } return copy; } - + + /** + * Return the comparator used to sort this map, or null if it is by + * natural order. + * + * @return the map's comparator + */ public Comparator comparator() { return comparator; } + /** + * Returns true if the map contains a mapping for the given key. + * + * @param key the key to look for + * @return true if the key has a mapping + * @throws ClassCastException if key is not comparable to map elements + * @throws NullPointerException if key is null and the comparator is not + * tolerant of nulls + */ public boolean containsKey(Object key) { return getNode(key) != nil; } + /** + * Returns true if the map contains at least one mapping to the given value. + * This requires linear time. + * + * @param value the value to look for + * @return true if the value appears in a mapping + */ public boolean containsValue(Object value) { Node node = firstNode(); - Object currentVal; - while (node != nil) { - currentVal = node.getValue(); - - if (value == null ? currentVal == null : value.equals (currentVal)) - return true; - - node = successor(node); + if (equals(value, node.value)) + return true; + node = successor(node); } return false; } + /** + * Returns a "set view" of this TreeMap's entries. The set is backed by + * the TreeMap, so changes in one show up in the other. The set supports + * element removal, but not element addition.<p> + * + * Note that the iterators for all three views, from keySet(), entrySet(), + * and values(), traverse the TreeMap in sorted sequence. + * + * @return a set view of the entries + * @see #keySet() + * @see #values() + * @see Map.Entry + */ public Set entrySet() { - // Create an AbstractSet with custom implementations of those methods that - // can be overriden easily and efficiently. - return new AbstractSet() - { - public int size() + if (entries == null) + // Create an AbstractSet with custom implementations of those methods + // that can be overriden easily and efficiently. + entries = new AbstractSet() { - return size; - } - - public Iterator iterator() - { - return new TreeIterator(TreeIterator.ENTRIES); - } - - public void clear() - { - TreeMap.this.clear(); - } + public int size() + { + return size; + } - public boolean contains(Object o) - { - if (!(o instanceof Map.Entry)) - return false; - Map.Entry me = (Map.Entry) o; - Node n = getNode(me.getKey()); - return (n != nil && me.getValue().equals(n.value)); - } - - public boolean remove(Object o) - { - if (!(o instanceof Map.Entry)) - return false; - Map.Entry me = (Map.Entry) o; - Node n = getNode(me.getKey()); - if (n != nil && me.getValue().equals(n.value)) - { - removeNode(n); - return true; - } - return false; + public Iterator iterator() + { + return new TreeIterator(ENTRIES); + } + + public void clear() + { + TreeMap.this.clear(); + } + + public boolean contains(Object o) + { + if (! (o instanceof Map.Entry)) + return false; + Map.Entry me = (Map.Entry) o; + Node n = getNode(me.getKey()); + return n != nil && AbstractSet.equals(me.getValue(), n.value); } - }; + + public boolean remove(Object o) + { + if (! (o instanceof Map.Entry)) + return false; + Map.Entry me = (Map.Entry) o; + Node n = getNode(me.getKey()); + if (n != nil && AbstractSet.equals(me.getValue(), n.value)) + { + removeNode(n); + return true; + } + return false; + } + }; + return entries; } + /** + * Returns the first (lowest) key in the map. + * + * @return the first key + * @throws NoSuchElementException if the map is empty + */ public Object firstKey() { if (root == nil) - throw new NoSuchElementException("empty"); - return firstNode().getKey(); - } - - private Node firstNode() - { - if (root == nil) - return nil; - Node node = root; - while (node.left != nil) - node = node.left; - return node; + throw new NoSuchElementException(); + return firstNode().key; } - public Object lastKey() - { - if (root == nil) - throw new NoSuchElementException("empty"); - return lastNode().getKey(); - } - - private Node lastNode() - { - if (root == nil) - return nil; - Node node = root; - while (node.right != nil) - node = node.right; - return node; - } - + /** + * Return the value in this TreeMap associated with the supplied key, + * or <code>null</code> if the key maps to nothing. NOTE: Since the value + * could also be null, you must use containsKey to see if this key + * actually maps to something. + * + * @param key the key for which to fetch an associated value + * @return what the key maps to, if present + * @throws ClassCastException if key is not comparable to elements in the map + * @throws NullPointerException if key is null but the comparator does not + * tolerate nulls + * @see #put(Object, Object) + * @see #containsKey(Object) + */ public Object get(Object key) { + // Exploit fact that nil.value == null. return getNode(key).value; } - - /** Return the TreeMap.Node associated with KEY, or the nil node if no such - node exists in the tree. */ - private Node getNode(Object key) - { - int comparison; - Node current = root; - while (current != nil) - { - comparison = compare(key, current.key); - if (comparison > 0) - current = current.right; - else if (comparison < 0) - current = current.left; - else - return current; - } - return current; + /** + * Returns a view of this Map including all entries with keys less than + * <code>toKey</code>. The returned map is backed by the original, so changes + * in one appear in the other. The submap will throw an + * {@link IllegalArgumentException} for any attempt to access or add an + * element beyond the specified cutoff. The returned map does not include + * the endpoint; if you want inclusion, pass the successor element. + * + * @param toKey the (exclusive) cutoff point + * @return a view of the map less than the cutoff + * @throws ClassCastException if <code>toKey</code> is not compatible with + * the comparator (or is not Comparable, for natural ordering) + * @throws NullPointerException if toKey is null, but the comparator does not + * tolerate null elements + */ + public SortedMap headMap(Object toKey) + { + return new SubMap(nil, toKey); } + /** + * Returns a "set view" of this TreeMap's keys. The set is backed by the + * TreeMap, so changes in one show up in the other. The set supports + * element removal, but not element addition. + * + * @return a set view of the keys + * @see #values() + * @see #entrySet() + */ public Set keySet() { - // Create an AbstractSet with custom implementations of those methods that - // can be overriden easily and efficiently. - return new AbstractSet() - { - public int size() + if (keys == null) + // Create an AbstractSet with custom implementations of those methods + // that can be overriden easily and efficiently. + keys = new AbstractSet() { - return size; - } - - public Iterator iterator() - { - return new TreeIterator(TreeIterator.KEYS); - } + public int size() + { + return size; + } - public void clear() - { - TreeMap.this.clear(); - } + public Iterator iterator() + { + return new TreeIterator(KEYS); + } - public boolean contains(Object o) - { - return TreeMap.this.containsKey(o); - } - - public boolean remove(Object key) - { - Node n = getNode(key); - if (n == nil) - return false; - TreeMap.this.removeNode(n); - return true; - } - }; + public void clear() + { + TreeMap.this.clear(); + } + + public boolean contains(Object o) + { + return containsKey(o); + } + + public boolean remove(Object key) + { + Node n = getNode(key); + if (n == nil) + return false; + removeNode(n); + return true; + } + }; + return keys; + } + + /** + * Returns the last (highest) key in the map. + * + * @return the last key + * @throws NoSuchElementException if the map is empty + */ + public Object lastKey() + { + if (root == nil) + throw new NoSuchElementException("empty"); + return lastNode().key; } + /** + * Puts the supplied value into the Map, mapped by the supplied key. + * The value may be retrieved by any object which <code>equals()</code> + * this key. NOTE: Since the prior value could also be null, you must + * first use containsKey if you want to see if you are replacing the + * key's mapping. + * + * @param key the key used to locate the value + * @param value the value to be stored in the HashMap + * @return the prior mapping of the key, or null if there was none + * @throws ClassCastException if key is not comparable to current map keys + * @throws NullPointerException if key is null, but the comparator does + * not tolerate nulls + * @see #get(Object) + * @see Object#equals(Object) + */ public Object put(Object key, Object value) { - modCount++; Node current = root; Node parent = nil; int comparison = 0; - + // Find new node's parent. while (current != nil) { - parent = current; - comparison = compare(key, current.key); - if (comparison > 0) - current = current.right; - else if (comparison < 0) - current = current.left; - else - { - // Key already in tree. - Object r = current.value; - current.value = value; - return r; - } + parent = current; + comparison = compare(key, current.key); + if (comparison > 0) + current = current.right; + else if (comparison < 0) + current = current.left; + else // Key already in tree. + return current.setValue(value); } - + // Set up new node. - Node n = new Node(key, value); - n.color = RED; + Node n = new Node(key, value, RED); n.parent = parent; - n.left = nil; - n.right = nil; - + // Insert node in tree. + modCount++; size++; if (parent == nil) { - // Special case: inserting into an empty tree. - root = n; - n.color = BLACK; - return null; + // Special case inserting into an empty tree. + root = n; + return null; } - else if (comparison > 0) + if (comparison > 0) parent.right = n; else - parent.left = n; - + parent.left = n; + // Rebalance after insert. insertFixup(n); - //verifyTree(); return null; } - /** Maintain red-black balance after inserting a new node. */ - private void insertFixup(Node n) - { - // Only need to rebalance when parent is a RED node, and while at least - // 2 levels deep into the tree (ie: node has a grandparent). - while (n != root && n.parent.parent != nil && n.parent.color == RED) - { - if (n.parent == n.parent.parent.left) - { - Node uncle = n.parent.parent.right; - if (uncle != nil && uncle.color == RED) - { - n.parent.color = BLACK; - uncle.color = BLACK; - n.parent.parent.color = RED; - n = n.parent.parent; - } - else // Uncle is BLACK. - { - if (n == n.parent.right) - { - // Make n a left child. - n = n.parent; - rotateLeft(n); - } - - // Recolor and rotate. - n.parent.color = BLACK; - n.parent.parent.color = RED; - rotateRight(n.parent.parent); - } - } - else - { - // Mirror image of above code. - Node uncle = n.parent.parent.left; - if (uncle != nil && uncle.color == RED) - { - n.parent.color = BLACK; - uncle.color = BLACK; - n.parent.parent.color = RED; - n = n.parent.parent; - } - else - { - if (n == n.parent.left) - { - n = n.parent; - rotateRight(n); - } - n.parent.color = BLACK; - n.parent.parent.color = RED; - rotateLeft(n.parent.parent); - } - } - } - root.color = BLACK; - } - + /** + * Copies all elements of the given map into this hashtable. If this table + * already has a mapping for a key, the new mapping replaces the current + * one. + * + * @param m the map to be hashed into this + * @throws ClassCastException if a key in m is not comparable with keys + * in the map + * @throws NullPointerException if a key in m is null, and the comparator + * does not tolerate nulls + */ public void putAll(Map m) { Iterator itr = m.entrySet().iterator(); - int msize = m.size(); - Map.Entry e; - - for (int i = 0; i < msize; i++) + int pos = m.size(); + while (--pos >= 0) { - e = (Map.Entry) itr.next(); - put(e.getKey(), e.getValue()); + Map.Entry e = (Map.Entry) itr.next(); + put(e.getKey(), e.getValue()); } } + /** + * Removes from the TreeMap and returns the value which is mapped by the + * supplied key. If the key maps to nothing, then the TreeMap remains + * unchanged, and <code>null</code> is returned. NOTE: Since the value + * could also be null, you must use containsKey to see if you are + * actually removing a mapping. + * + * @param key the key used to locate the value to remove + * @return whatever the key mapped to, if present + * @throws ClassCastException if key is not comparable to current map keys + * @throws NullPointerException if key is null, but the comparator does + * not tolerate nulls + */ public Object remove(Object key) { Node n = getNode(key); - if (n != nil) - { - removeNode(n); - return n.value; - } - return null; + if (n == nil) + return null; + removeNode(n); + return n.value; } - - // Remove node from tree. This will increment modCount and decrement size. - // Node must exist in the tree. - private void removeNode(Node node) // z + + /** + * Returns the number of key-value mappings currently in this Map. + * + * @return the size + */ + public int size() { - Node splice; // y - Node child; // x - - modCount++; - size--; + return size; + } - // Find splice, the node at the position to actually remove from the tree. - if (node.left == nil || node.right == nil) - { - // Node to be deleted has 0 or 1 children. - splice = node; - if (node.left == nil) - child = node.right; - else - child = node.left; - } - else - { - // Node has 2 children. Splice is node's successor, and will be - // swapped with node since we can't remove node directly. - splice = node.right; - while (splice.left != nil) - splice = splice.left; - child = splice.right; - } + /** + * Returns a view of this Map including all entries with keys greater or + * equal to <code>fromKey</code> and less than <code>toKey</code> (a + * half-open interval). The returned map is backed by the original, so + * changes in one appear in the other. The submap will throw an + * {@link IllegalArgumentException} for any attempt to access or add an + * element beyond the specified cutoffs. The returned map includes the low + * endpoint but not the high; if you want to reverse this behavior on + * either end, pass in the successor element. + * + * @param fromKey the (inclusive) low cutoff point + * @param toKey the (exclusive) high cutoff point + * @return a view of the map between the cutoffs + * @throws ClassCastException if either cutoff is not compatible with + * the comparator (or is not Comparable, for natural ordering) + * @throws NullPointerException if fromKey or toKey is null, but the + * comparator does not tolerate null elements + * @throws IllegalArgumentException if fromKey is greater than toKey + */ + public SortedMap subMap(Object fromKey, Object toKey) + { + return new SubMap(fromKey, toKey); + } - // Unlink splice from the tree. - Node parent = splice.parent; - child.parent = parent; - if (parent != nil) + /** + * Returns a view of this Map including all entries with keys greater or + * equal to <code>fromKey</code>. The returned map is backed by the + * original, so changes in one appear in the other. The submap will throw an + * {@link IllegalArgumentException} for any attempt to access or add an + * element beyond the specified cutoff. The returned map includes the + * endpoint; if you want to exclude it, pass in the successor element. + * + * @param fromKey the (inclusive) low cutoff point + * @return a view of the map above the cutoff + * @throws ClassCastException if <code>fromKey</code> is not compatible with + * the comparator (or is not Comparable, for natural ordering) + * @throws NullPointerException if fromKey is null, but the comparator + * does not tolerate null elements + */ + public SortedMap tailMap(Object fromKey) + { + return new SubMap(fromKey, nil); + } + + /** + * Returns a "collection view" (or "bag view") of this TreeMap's values. + * The collection is backed by the TreeMap, so changes in one show up + * in the other. The collection supports element removal, but not element + * addition. + * + * @return a bag view of the values + * @see #keySet() + * @see #entrySet() + */ + public Collection values() + { + if (values == null) + // We don't bother overriding many of the optional methods, as doing so + // wouldn't provide any significant performance advantage. + values = new AbstractCollection() { - if (splice == parent.left) - parent.left = child; - else - parent.right = child; - } - else - root = child; + public int size() + { + return size; + } - // Keep track of splice's color in case it gets changed in the swap. - int spliceColor = splice.color; + public Iterator iterator() + { + return new TreeIterator(VALUES); + } -/* - if (splice != node) - { - node.key = splice.key; - node.value = splice.value; - } -*/ - if (splice != node) - { - // Swap SPLICE for NODE. Some implementations optimize here by simply - // swapping the values, but we can't do that: if an iterator was - // referencing a node in its "next" field, and that node got swapped, - // things would get confused. - if (node == root) - { - root = splice; - } - else - { - if (node.parent.left == node) - node.parent.left = splice; - else - node.parent.right = splice; - } - splice.parent = node.parent; - splice.left = node.left; - splice.right = node.right; - splice.left.parent = splice; - splice.right.parent = splice; - splice.color = node.color; - } + public void clear() + { + TreeMap.this.clear(); + } + }; + return values; + } - if (spliceColor == BLACK) - deleteFixup (child); - - //verifyTree(); + /** + * Compares two elements by the set comparator, or by natural ordering. + * Package visible for use by nested classes. + * + * @param o1 the first object + * @param o2 the second object + * @throws ClassCastException if o1 and o2 are not mutually comparable, + * or are not Comparable with natural ordering + * @throws NullPointerException if o1 or o2 is null with natural ordering + */ + final int compare(Object o1, Object o2) + { + return (comparator == null + ? ((Comparable) o1).compareTo(o2) + : comparator.compare(o1, o2)); } - /** Maintain red-black balance after deleting a node. */ - private void deleteFixup (Node node) + /** + * Maintain red-black balance after deleting a node. + * + * @param node the child of the node just deleted, possibly nil + * @param parent the parent of the node just deleted, never nil + */ + private void deleteFixup(Node node, Node parent) { - // A black node has been removed, so we need to rebalance to avoid + // if (parent == nil) + // throw new InternalError(); + // If a black node has been removed, we need to rebalance to avoid // violating the "same number of black nodes on any path" rule. If - // node is red, we can simply recolor it black and all is well. + // node is red, we can simply recolor it black and all is well. while (node != root && node.color == BLACK) { - if (node == node.parent.left) - { - // Rebalance left side. - Node sibling = node.parent.right; - if (sibling.color == RED) - { + if (node == parent.left) + { + // Rebalance left side. + Node sibling = parent.right; + // if (sibling == nil) + // throw new InternalError(); + if (sibling.color == RED) + { + // Case 1: Sibling is red. + // Recolor sibling and parent, and rotate parent left. sibling.color = BLACK; - node.parent.color = RED; - rotateLeft(node.parent); - sibling = node.parent.right; - } + parent.color = RED; + rotateLeft(parent); + sibling = parent.right; + } - if (sibling.left.color == BLACK && sibling.right.color == BLACK) + if (sibling.left.color == BLACK && sibling.right.color == BLACK) { - // Case 2: Sibling has no red children. - sibling.color = RED; - // Black height has been decreased, so move up the tree and - // repeat. - node = node.parent; + // Case 2: Sibling has no red children. + // Recolor sibling, and move to parent. + sibling.color = RED; + node = parent; + parent = parent.parent; } - else - { - if (sibling.right.color == BLACK) - { - // Case 3: Sibling has red left child. - sibling.left.color = BLACK; - sibling.color = RED; + else + { + if (sibling.right.color == BLACK) + { + // Case 3: Sibling has red left child. + // Recolor sibling and left child, rotate sibling right. + sibling.left.color = BLACK; + sibling.color = RED; rotateRight(sibling); - sibling = node.parent.right; - } - - // Case 4: Sibling has red right child. - sibling.color = sibling.parent.color; - sibling.parent.color = BLACK; - sibling.right.color = BLACK; - rotateLeft(node.parent); + sibling = parent.right; + } + // Case 4: Sibling has red right child. Recolor sibling, + // right child, and parent, and rotate parent left. + sibling.color = parent.color; + parent.color = BLACK; + sibling.right.color = BLACK; + rotateLeft(parent); node = root; // Finished. - } - } - else - { - // Symmetric "mirror" of left-side case. - Node sibling = node.parent.left; - if (sibling.color == RED) - { + } + } + else + { + // Symmetric "mirror" of left-side case. + Node sibling = parent.left; + // if (sibling == nil) + // throw new InternalError(); + if (sibling.color == RED) + { + // Case 1: Sibling is red. + // Recolor sibling and parent, and rotate parent right. sibling.color = BLACK; - node.parent.color = RED; - rotateRight(node.parent); - sibling = node.parent.left; - } + parent.color = RED; + rotateRight(parent); + sibling = parent.left; + } - if (sibling.left.color == BLACK && sibling.right.color == BLACK) + if (sibling.right.color == BLACK && sibling.left.color == BLACK) { - sibling.color = RED; - node = node.parent; + // Case 2: Sibling has no red children. + // Recolor sibling, and move to parent. + sibling.color = RED; + node = parent; + parent = parent.parent; } - else - { - if (sibling.left.color == BLACK) - { - sibling.right.color = BLACK; - sibling.color = RED; + else + { + if (sibling.left.color == BLACK) + { + // Case 3: Sibling has red right child. + // Recolor sibling and right child, rotate sibling left. + sibling.right.color = BLACK; + sibling.color = RED; rotateLeft(sibling); - sibling = node.parent.left; - } - - sibling.color = sibling.parent.color; - sibling.parent.color = BLACK; - sibling.left.color = BLACK; - rotateRight(node.parent); - node = root; - } - } + sibling = parent.left; + } + // Case 4: Sibling has red left child. Recolor sibling, + // left child, and parent, and rotate parent right. + sibling.color = parent.color; + parent.color = BLACK; + sibling.left.color = BLACK; + rotateRight(parent); + node = root; // Finished. + } + } } node.color = BLACK; } - public SortedMap subMap(Object fromKey, Object toKey) + /** + * Construct a perfectly balanced tree consisting of n "blank" nodes. This + * permits a tree to be generated from pre-sorted input in linear time. + * + * @param count the number of blank nodes, non-negative + */ + private void fabricateTree(final int count) { - if (compare(fromKey, toKey) <= 0) - return new SubMap(fromKey, toKey); - else - throw new IllegalArgumentException("fromKey > toKey"); - } + if (count == 0) + return; - public SortedMap headMap(Object toKey) - { - return new SubMap(nil, toKey); - } + // We color every row of nodes black, except for the overflow nodes. + // I believe that this is the optimal arrangement. We construct the tree + // in place by temporarily linking each node to the next node in the row, + // then updating those links to the children when working on the next row. - public SortedMap tailMap(Object fromKey) - { - return new SubMap(fromKey, nil); - } + // Make the root node. + root = new Node(null, null, BLACK); + size = count; + Node row = root; + int rowsize; - /** Returns a "collection view" (or "bag view") of this TreeMap's values. */ - public Collection values() - { - // We don't bother overriding many of the optional methods, as doing so - // wouldn't provide any significant performance advantage. - return new AbstractCollection() - { - public int size() + // Fill each row that is completely full of nodes. + for (rowsize = 2; rowsize + rowsize < count; rowsize <<= 1) + { + Node parent = row; + Node last = null; + for (int i = 0; i < rowsize; i += 2) + { + Node left = new Node(null, null, BLACK); + Node right = new Node(null, null, BLACK); + left.parent = parent; + left.right = right; + right.parent = parent; + parent.left = left; + Node next = parent.right; + parent.right = right; + parent = next; + if (last != null) + last.right = left; + last = right; + } + row = row.left; + } + + // Now do the partial final row in red. + int overflow = count - rowsize; + Node parent = row; + int i; + for (i = 0; i < overflow; i += 2) + { + Node left = new Node(null, null, RED); + Node right = new Node(null, null, RED); + left.parent = parent; + right.parent = parent; + parent.left = left; + Node next = parent.right; + parent.right = right; + parent = next; + } + // Add a lone left node if necessary. + if (i - overflow == 0) { - return size; + Node left = new Node(null, null, RED); + left.parent = parent; + parent.left = left; + parent = parent.right; + left.parent.right = nil; } - - public Iterator iterator() + // Unlink the remaining nodes of the previous row. + while (parent != nil) { - return new TreeIterator(TreeIterator.VALUES); + Node next = parent.right; + parent.right = nil; + parent = next; } - - public void clear() + } + + /** + * Returns the first sorted node in the map, or nil if empty. Package + * visible for use by nested classes. + * + * @return the first node + */ + final Node firstNode() + { + // Exploit fact that nil.left == nil. + Node node = root; + while (node.left != nil) + node = node.left; + return node; + } + + /** + * Return the TreeMap.Node associated with key, or the nil node if no such + * node exists in the tree. Package visible for use by nested classes. + * + * @param key the key to search for + * @return the node where the key is found, or nil + */ + final Node getNode(Object key) + { + Node current = root; + while (current != nil) { - TreeMap.this.clear(); + int comparison = compare(key, current.key); + if (comparison > 0) + current = current.right; + else if (comparison < 0) + current = current.left; + else + return current; } - }; + return current; } - // Find the "highest" node which is < key. If key is nil, return last node. - // Note that highestLessThan is exclusive (it won't return a key which is - // equal to "key"), while lowestGreaterThan is inclusive, in order to be - // consistent with the semantics of subMap(). - private Node highestLessThan(Object key) + /** + * Find the "highest" node which is < key. If key is nil, return last + * node. Package visible for use by nested classes. + * + * @param key the upper bound, exclusive + * @return the previous node + */ + final Node highestLessThan(Object key) { if (key == nil) return lastNode(); - + Node last = nil; Node current = root; int comparison = 0; @@ -734,24 +965,118 @@ public class TreeMap extends AbstractMap { last = current; comparison = compare(key, current.key); - if (comparison > 0) - current = current.right; - else if (comparison < 0) - current = current.left; - else /* Exact match. */ - return predecessor(last); + if (comparison > 0) + current = current.right; + else if (comparison < 0) + current = current.left; + else // Exact match. + return predecessor(last); } - if (comparison <= 0) - return predecessor(last); - else - return last; + return comparison <= 0 ? predecessor(last) : last; + } + + /** + * Maintain red-black balance after inserting a new node. + * + * @param n the newly inserted node + */ + private void insertFixup(Node n) + { + // Only need to rebalance when parent is a RED node, and while at least + // 2 levels deep into the tree (ie: node has a grandparent). Remember + // that nil.color == BLACK. + while (n.parent.color == RED && n.parent.parent != nil) + { + if (n.parent == n.parent.parent.left) + { + Node uncle = n.parent.parent.right; + // Uncle may be nil, in which case it is BLACK. + if (uncle.color == RED) + { + // Case 1. Uncle is RED: Change colors of parent, uncle, + // and grandparent, and move n to grandparent. + n.parent.color = BLACK; + uncle.color = BLACK; + uncle.parent.color = RED; + n = uncle.parent; + } + else + { + if (n == n.parent.right) + { + // Case 2. Uncle is BLACK and x is right child. + // Move n to parent, and rotate n left. + n = n.parent; + rotateLeft(n); + } + // Case 3. Uncle is BLACK and x is left child. + // Recolor parent, grandparent, and rotate grandparent right. + n.parent.color = BLACK; + n.parent.parent.color = RED; + rotateRight(n.parent.parent); + } + } + else + { + // Mirror image of above code. + Node uncle = n.parent.parent.left; + // Uncle may be nil, in which case it is BLACK. + if (uncle.color == RED) + { + // Case 1. Uncle is RED: Change colors of parent, uncle, + // and grandparent, and move n to grandparent. + n.parent.color = BLACK; + uncle.color = BLACK; + uncle.parent.color = RED; + n = uncle.parent; + } + else + { + if (n == n.parent.left) + { + // Case 2. Uncle is BLACK and x is left child. + // Move n to parent, and rotate n right. + n = n.parent; + rotateRight(n); + } + // Case 3. Uncle is BLACK and x is right child. + // Recolor parent, grandparent, and rotate grandparent left. + n.parent.color = BLACK; + n.parent.parent.color = RED; + rotateLeft(n.parent.parent); + } + } + } + root.color = BLACK; } - // Find the "lowest" node which is >= key. If key is nil, return first node. - private Node lowestGreaterThan(Object key) + /** + * Returns the last sorted node in the map, or nil if empty. + * + * @return the last node + */ + private Node lastNode() + { + // Exploit fact that nil.right == nil. + Node node = root; + while (node.right != nil) + node = node.right; + return node; + } + + /** + * Find the "lowest" node which is >= key. If key is nil, return either + * nil or the first node, depending on the parameter first. + * Package visible for use by nested classes. + * + * @param key the lower bound, inclusive + * @param first true to return the first element instead of nil for nil key + * @return the next node + */ + final Node lowestGreaterThan(Object key, boolean first) { if (key == nil) - return firstNode(); + return first ? firstNode() : nil; Node last = nil; Node current = root; @@ -761,95 +1086,176 @@ public class TreeMap extends AbstractMap { last = current; comparison = compare(key, current.key); - if (comparison > 0) - current = current.right; - else if (comparison < 0) - current = current.left; - else - return current; + if (comparison > 0) + current = current.right; + else if (comparison < 0) + current = current.left; + else + return current; } - if (comparison > 0) - return successor(last); - else - return last; - } + return comparison > 0 ? successor(last) : last; + } - private void writeObject(ObjectOutputStream out) throws IOException + /** + * Return the node preceding the given one, or nil if there isn't one. + * + * @param node the current node, not nil + * @return the prior node in sorted order + */ + private Node predecessor(Node node) { - out.defaultWriteObject(); + if (node.left != nil) + { + node = node.left; + while (node.right != nil) + node = node.right; + return node; + } - Node node = firstNode(); - out.writeInt(size); - - while (node != nil) + Node parent = node.parent; + // Exploit fact that nil.left == nil and node is non-nil. + while (node == parent.left) { - out.writeObject(node.key); - out.writeObject(node.value); - node = successor(node); + node = parent; + parent = node.parent; } + return parent; } - private void readObject(ObjectInputStream in) + /** + * Construct a tree from sorted keys in linear time. Package visible for + * use by TreeSet. + * + * @param s the stream to read from + * @param count the number of keys to read + * @param readValue true to read values, false to insert "" as the value + * @throws ClassNotFoundException if the underlying stream fails + * @throws IOException if the underlying stream fails + * @see #readObject(ObjectInputStream) + * @see TreeSet#readObject(ObjectInputStream) + */ + final void putFromObjStream(ObjectInputStream s, int count, + boolean readValues) throws IOException, ClassNotFoundException { - in.defaultReadObject(); - int size = in.readInt(); - putFromObjStream(in, size, true); - } + fabricateTree(count); + Node node = firstNode(); - private int compare(Object o1, Object o2) - { - if (comparator == null) - return ((Comparable) o1).compareTo(o2); - else - return comparator.compare(o1, o2); + while (--count >= 0) + { + node.key = s.readObject(); + node.value = readValues ? s.readObject() : ""; + node = successor(node); + } } - /* Return the node following Node, or nil if there isn't one. */ - private Node successor(Node node) + /** + * Construct a tree from sorted keys in linear time, with values of "". + * Package visible for use by TreeSet. + * + * @param keys the iterator over the sorted keys + * @param count the number of nodes to insert + * @see TreeSet#TreeSet(SortedSet) + */ + final void putKeysLinear(Iterator keys, int count) { - if (node.right != nil) - { - node = node.right; - while (node.left != nil) - node = node.left; - return node; - } + fabricateTree(count); + Node node = firstNode(); - Node parent = node.parent; - while (parent != nil && node == parent.right) + while (--count >= 0) { - node = parent; - parent = parent.parent; + node.key = keys.next(); + node.value = ""; + node = successor(node); } - return parent; } - /* Return the node preceeding Node, or nil if there isn't one. */ - private Node predecessor(Node node) + /** + * Deserializes this object from the given stream. + * + * @param s the stream to read from + * @throws ClassNotFoundException if the underlying stream fails + * @throws IOException if the underlying stream fails + * @serialData the <i>size</i> (int), followed by key (Object) and value + * (Object) pairs in sorted order + */ + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException { - if (node.left != nil) + s.defaultReadObject(); + int size = s.readInt(); + putFromObjStream(s, size, true); + } + + /** + * Remove node from tree. This will increment modCount and decrement size. + * Node must exist in the tree. Package visible for use by nested classes. + * + * @param node the node to remove + */ + final void removeNode(Node node) + { + Node splice; + Node child; + + modCount++; + size--; + + // Find splice, the node at the position to actually remove from the tree. + if (node.left == nil) { - node = node.left; - while (node.right != nil) - node = node.right; - return node; + // Node to be deleted has 0 or 1 children. + splice = node; + child = node.right; } - - Node parent = node.parent; - while (parent != nil && node == parent.left) + else if (node.right == nil) { - node = parent; - parent = parent.parent; + // Node to be deleted has 1 child. + splice = node; + child = node.left; } - return parent; + else + { + // Node has 2 children. Splice is node's predecessor, and we swap + // its contents into node. + splice = node.left; + while (splice.right != nil) + splice = splice.right; + child = splice.left; + node.key = splice.key; + node.value = splice.value; + } + + // Unlink splice from the tree. + Node parent = splice.parent; + if (child != nil) + child.parent = parent; + if (parent == nil) + { + // Special case for 0 or 1 node remaining. + root = child; + return; + } + if (splice == parent.left) + parent.left = child; + else + parent.right = child; + + if (splice.color == BLACK) + deleteFixup(child, parent); } - /** Rotate node n to the left. */ + /** + * Rotate node n to the left. + * + * @param node the node to rotate + */ private void rotateLeft(Node node) { Node child = node.right; - + // if (node == nil || child == nil) + // throw new InternalError(); + // Establish node.right link. node.right = child.left; if (child.left != nil) @@ -860,331 +1266,146 @@ public class TreeMap extends AbstractMap if (node.parent != nil) { if (node == node.parent.left) - node.parent.left = child; - else - node.parent.right = child; + node.parent.left = child; + else + node.parent.right = child; } else root = child; // Link n and child. child.left = node; - if (node != nil) - node.parent = child; + node.parent = child; } - /** Rotate node n to the right. */ + /** + * Rotate node n to the right. + * + * @param node the node to rotate + */ private void rotateRight(Node node) { Node child = node.left; - + // if (node == nil || child == nil) + // throw new InternalError(); + // Establish node.left link. node.left = child.right; if (child.right != nil) child.right.parent = node; - + // Establish child->parent link. child.parent = node.parent; if (node.parent != nil) { if (node == node.parent.right) - node.parent.right = child; - else - node.parent.left = child; + node.parent.right = child; + else + node.parent.left = child; } else root = child; - + // Link n and child. child.right = node; - if (node != nil) - node.parent = child; - } - - /* Construct a tree from sorted keys in linear time. This is used to - implement TreeSet's SortedSet constructor. */ - void putKeysLinear(Iterator keys, int count) - { - fabricateTree(count); - Node node = firstNode(); - - for (int i = 0; i < count; i++) - { - node.key = keys.next(); - node.value = Boolean.TRUE; - node = successor(node); - } - } - - /* As above, but load keys from an ObjectInputStream. Used by readObject() - methods. If "readValues" is set, entry values will also be read from the - stream. If not, only keys will be read. */ - void putFromObjStream(ObjectInputStream in, int count, boolean readValues) - throws IOException, ClassNotFoundException - { - fabricateTree(count); - Node node = firstNode(); - - for (int i = 0; i < count; i++) - { - node.key = in.readObject(); - if (readValues) - node.value = in.readObject(); - else - node.value = Boolean.TRUE; - node = successor(node); - } - } - - /* Construct a perfectly balanced tree consisting of n "blank" nodes. - This permits a tree to be generated from pre-sorted input in linear - time. */ - private void fabricateTree(int count) - { - if (count == 0) - return; - // Calculate the (maximum) depth of the perfectly balanced tree. - double ddepth = (Math.log (count + 1) / Math.log (2)); - int maxdepth = (int) Math.ceil (ddepth); - - // The number of nodes which can fit in a perfectly-balanced tree of - // height "depth - 1". - int max = (int) Math.pow (2, maxdepth - 1) - 1; - - // Number of nodes which spill over into the deepest row of the tree. - int overflow = (int) count - max; - - size = count; - // Make the root node. - root = new Node(null, null); - root.parent = nil; - root.left = nil; - root.right = nil; - - Node row = root; - for (int depth = 2; depth <= maxdepth; depth++) // each row - { - // Number of nodes at this depth - int rowcap = (int) Math.pow (2, depth - 1); - Node parent = row; - Node last = null; - - // Actual number of nodes to create in this row - int rowsize; - if (depth == maxdepth) - rowsize = overflow; - else - rowsize = rowcap; - - // The bottom most row of nodes is coloured red, as is every second row - // going up, except the root node (row 1). I'm not sure if this is the - // optimal configuration for the tree, but it seems logical enough. - // We just need to honour the black-height and red-parent rules here. - boolean colorRowRed = (depth % 2 == maxdepth % 2); - - int i; - for (i = 1; i <= rowsize; i++) // each node in row - { - Node node = new Node(null, null); - node.parent = parent; - if (i % 2 == 1) - parent.left = node; - else - { - Node nextparent = parent.right; - parent.right = node; - parent = nextparent; - } - - // We use the "right" link to maintain a chain of nodes in - // each row until the parent->child links are established. - if (last != null) - last.right = node; - last = node; - - if (colorRowRed) - node.color = RED; - - if (i == 1) - row = node; - } - - // Set nil child pointers on leaf nodes. - if (depth == maxdepth) - { - // leaf nodes at maxdepth-1. - if (parent != null) - { - if (i % 2 == 0) - { - // Current "parent" has "left" set already. - Node next = parent.right; - parent.right = nil; - parent = next; - } - while (parent != null) - { - parent.left = nil; - Node next = parent.right; - parent.right = nil; - parent = next; - } - } - // leaf nodes at maxdepth. - Node node = row; - Node next; - while (node != null) - { - node.left = nil; - next = node.right; - node.right = nil; - node = next; - } - } - } - } - - private class VerifyResult - { - int count; // Total number of nodes. - int black; // Black height/depth. - int maxdepth; // Maximum depth of branch. + node.parent = child; } - /* Check that red-black properties are consistent for the tree. */ - private void verifyTree() - { - if (root == nil) - { - System.err.println ("Verify: empty tree"); - if (size != 0) - verifyError (this, "no root node but size=" + size); - return; - } - VerifyResult vr = verifySub (root); - if (vr.count != size) - { - verifyError (this, "Tree size not consistent with actual nodes counted. " - + "counted " + vr.count + ", size=" + size); - System.exit(1); - } - System.err.println ("Verify: " + vr.count + " nodes, black height=" + vr.black - + ", maxdepth=" + vr.maxdepth); - } - - /* Recursive call to check that rbtree rules hold. Returns total node count - and black height of the given branch. */ - private VerifyResult verifySub(Node n) + /** + * Return the node following the given one, or nil if there isn't one. + * Package visible for use by nested classes. + * + * @param node the current node, not nil + * @return the next node in sorted order + */ + final Node successor(Node node) { - VerifyResult vr1 = null; - VerifyResult vr2 = null; - - if (n.left == nil && n.right == nil) - { - // leaf node - VerifyResult r = new VerifyResult(); - r.black = (n.color == BLACK ? 1 : 0); - r.count = 1; - r.maxdepth = 1; - return r; - } - - if (n.left != nil) + if (node.right != nil) { - if (n.left.parent != n) - verifyError(n.left, "Node's parent link does not point to " + n); - - if (n.color == RED && n.left.color == RED) - verifyError(n, "Red node has red left child"); - - vr1 = verifySub (n.left); - if (n.right == nil) - { - if (n.color == BLACK) - vr1.black++; - vr1.count++; - vr1.maxdepth++; - return vr1; - } + node = node.right; + while (node.left != nil) + node = node.left; + return node; } - if (n.right != nil) + Node parent = node.parent; + // Exploit fact that nil.right == nil and node is non-nil. + while (node == parent.right) { - if (n.right.parent != n) - verifyError(n.right, "Node's parent link does not point to " + n); - - if (n.color == RED && n.right.color == RED) - verifyError(n, "Red node has red right child"); - - vr2 = verifySub (n.right); - if (n.left == nil) - { - if (n.color == BLACK) - vr2.black++; - vr2.count++; - vr2.maxdepth++; - return vr2; - } + node = parent; + parent = parent.parent; } - - if (vr1.black != vr2.black) - verifyError (n, "Black heights: " + vr1.black + "," + vr2.black + " don't match."); - vr1.count += vr2.count + 1; - vr1.maxdepth = Math.max(vr1.maxdepth, vr2.maxdepth) + 1; - if (n.color == BLACK) - vr1.black++; - return vr1; + return parent; } - - private void verifyError (Object obj, String msg) + + /** + * Serializes this object to the given stream. + * + * @param s the stream to write to + * @throws IOException if the underlying stream fails + * @serialData the <i>size</i> (int), followed by key (Object) and value + * (Object) pairs in sorted order + */ + private void writeObject(ObjectOutputStream s) throws IOException { - System.err.print ("Verify error: "); - try - { - System.err.print (obj); - } - catch (Exception x) + s.defaultWriteObject(); + + Node node = firstNode(); + s.writeInt(size); + while (node != nil) { - System.err.print ("(error printing obj): " + x); + s.writeObject(node.key); + s.writeObject(node.value); + node = successor(node); } - System.err.println(); - System.err.println (msg); - Thread.dumpStack(); - System.exit(1); } /** - * Iterate over HashMap's entries. - * This implementation is parameterized to give a sequential view of - * keys, values, or entries. - */ - class TreeIterator implements Iterator + * Iterate over HashMap's entries. This implementation is parameterized + * to give a sequential view of keys, values, or entries. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private final class TreeIterator implements Iterator { - static final int ENTRIES = 0, - KEYS = 1, - VALUES = 2; - - // the type of this Iterator: KEYS, VALUES, or ENTRIES. - int type; - // the number of modifications to the backing Map that we know about. - int knownMod = TreeMap.this.modCount; - // The last Entry returned by a next() call. - Node last; - // The next entry that should be returned by next(). - Node next; - // The last node visible to this iterator. This is used when iterating - // on a SubMap. - Node max; - - /* Create Iterator with the supplied type: KEYS, VALUES, or ENTRIES */ + /** + * The type of this Iterator: {@link #KEYS}, {@link #VALUES}, + * or {@link #ENTRIES}. + */ + private final int type; + /** The number of modifications to the backing Map that we know about. */ + private int knownMod = modCount; + /** The last Entry returned by a next() call. */ + private Node last; + /** The next entry that should be returned by next(). */ + private Node next; + /** + * The last node visible to this iterator. This is used when iterating + * on a SubMap. + */ + private final Node max; + + /** + * Construct a new TreeIterator with the supplied type. + * @param type {@link #KEYS}, {@link #VALUES}, or {@link #ENTRIES} + */ TreeIterator(int type) { + // FIXME gcj cannot handle this. Bug java/4695 + // this(type, firstNode(), nil); this.type = type; this.next = firstNode(); + this.max = nil; } - - /* Construct an interator for a SubMap. Iteration will begin at node - "first", and stop when "max" is reached. */ + + /** + * Construct a new TreeIterator with the supplied type. Iteration will + * be from "first" (inclusive) to "max" (exclusive). + * + * @param type {@link #KEYS}, {@link #VALUES}, or {@link #ENTRIES} + * @param first where to start iteration, nil for empty iterator + * @param max the cutoff for iteration, nil for all remaining nodes + */ TreeIterator(int type, Node first, Node max) { this.type = type; @@ -1192,263 +1413,351 @@ public class TreeMap extends AbstractMap this.max = max; } + /** + * Returns true if the Iterator has more elements. + * @return true if there are more elements + * @throws ConcurrentModificationException if the TreeMap was modified + */ public boolean hasNext() { - if (knownMod != TreeMap.this.modCount) - throw new ConcurrentModificationException(); - return (next != nil); + if (knownMod != modCount) + throw new ConcurrentModificationException(); + return next != max; } + /** + * Returns the next element in the Iterator's sequential view. + * @return the next element + * @throws ConcurrentModificationException if the TreeMap was modified + * @throws NoSuchElementException if there is none + */ public Object next() { - if (next == nil) - throw new NoSuchElementException(); - if (knownMod != TreeMap.this.modCount) - throw new ConcurrentModificationException(); - Node n = next; - - // Check limit in case we are iterating through a submap. - if (n != max) - next = successor(n); - else - next = nil; - - last = n; - + if (knownMod != modCount) + throw new ConcurrentModificationException(); + if (next == max) + throw new NoSuchElementException(); + last = next; + next = successor(last); + if (type == VALUES) - return n.value; + return last.value; else if (type == KEYS) - return n.key; - return n; + return last.key; + return last; } + /** + * Removes from the backing TreeMap the last element which was fetched + * with the <code>next()</code> method. + * @throws ConcurrentModificationException if the TreeMap was modified + * @throws IllegalStateException if called when there is no last element + */ public void remove() { + if (knownMod != modCount) + throw new ConcurrentModificationException(); if (last == null) - throw new IllegalStateException(); - if (knownMod != TreeMap.this.modCount) - throw new ConcurrentModificationException(); -/* - Object key = null; - if (next != nil) - key = next.key; -*/ - TreeMap.this.removeNode(last); - knownMod++; -/* - if (key != null) - next = getNode(key); -*/ + throw new IllegalStateException(); + + removeNode(last); last = null; + knownMod++; } - } + } // class TreeIterator - class SubMap extends AbstractMap implements SortedMap + /** + * Implementation of {@link #subMap(Object, Object)} and other map + * ranges. This class provides a view of a portion of the original backing + * map, and throws {@link IllegalArgumentException} for attempts to + * access beyond that range. + * + * @author Eric Blake <ebb9@email.byu.edu> + */ + private final class SubMap extends AbstractMap implements SortedMap { - Object minKey; - Object maxKey; - - /* Create a SubMap representing the elements between minKey and maxKey - (inclusive). If minKey is nil, SubMap has no lower bound (headMap). - If maxKey is nil, the SubMap has no upper bound (tailMap). */ + /** + * The lower range of this view, inclusive, or nil for unbounded. + * Package visible for use by nested classes. + */ + final Object minKey; + + /** + * The upper range of this view, exclusive, or nil for unbounded. + * Package visible for use by nested classes. + */ + final Object maxKey; + + /** + * The cache for {@link #entrySet()}. + */ + private Set entries; + + /** + * Create a SubMap representing the elements between minKey (inclusive) + * and maxKey (exclusive). If minKey is nil, SubMap has no lower bound + * (headMap). If maxKey is nil, the SubMap has no upper bound (tailMap). + * + * @param minKey the lower bound + * @param maxKey the upper bound + * @throws IllegalArgumentException if minKey > maxKey + */ SubMap(Object minKey, Object maxKey) { + if (minKey != nil && maxKey != nil && compare(minKey, maxKey) > 0) + throw new IllegalArgumentException("fromKey > toKey"); this.minKey = minKey; this.maxKey = maxKey; } + /** + * Check if "key" is in within the range bounds for this SubMap. The + * lower ("from") SubMap range is inclusive, and the upper ("to") bound + * is exclusive. Package visible for use by nested classes. + * + * @param key the key to check + * @return true if the key is in range + */ + final boolean keyInRange(Object key) + { + return ((minKey == nil || compare(key, minKey) >= 0) + && (maxKey == nil || compare(key, maxKey) < 0)); + } + public void clear() { - Node current; - Node next = lowestGreaterThan(minKey); - Node max = highestLessThan(maxKey); - - if (compare(next.key, max.key) > 0) - // Nothing to delete. - return; - - do + Node next = lowestGreaterThan(minKey, true); + Node max = lowestGreaterThan(maxKey, false); + while (next != max) { - current = next; - next = successor(current); - remove(current); - } - while (current != max); + Node current = next; + next = successor(current); + removeNode(current); + } } - - /* Check if "key" is in within the range bounds for this SubMap. - The lower ("from") SubMap range is inclusive, and the upper (to) bound - is exclusive. */ - private boolean keyInRange(Object key) + + public Comparator comparator() { - return ((minKey == nil || compare(key, minKey) >= 0) - && (maxKey == nil || compare(key, maxKey) < 0)); + return comparator; } public boolean containsKey(Object key) { - return (keyInRange(key) && TreeMap.this.containsKey(key)); + return keyInRange(key) && TreeMap.this.containsKey(key); } public boolean containsValue(Object value) { - Node node = lowestGreaterThan(minKey); - Node max = highestLessThan(maxKey); - Object currentVal; - - if (node == nil || max == nil || compare(node.key, max.key) > 0) - // Nothing to search. - return false; - - while (true) - { - currentVal = node.getValue(); - if (value == null ? currentVal == null : value.equals (currentVal)) - return true; - if (node == max) - return false; - node = successor(node); - } + Node node = lowestGreaterThan(minKey, true); + Node max = lowestGreaterThan(maxKey, false); + while (node != max) + { + if (equals(value, node.getValue())) + return true; + node = successor(node); + } + return false; + } + + public Set entrySet() + { + if (entries == null) + // Create an AbstractSet with custom implementations of those methods + // that can be overriden easily and efficiently. + entries = new AbstractSet() + { + public int size() + { + return SubMap.this.size(); + } + + public Iterator iterator() + { + Node first = lowestGreaterThan(minKey, true); + Node max = lowestGreaterThan(maxKey, false); + return new TreeIterator(ENTRIES, first, max); + } + + public void clear() + { + SubMap.this.clear(); + } + + public boolean contains(Object o) + { + if (! (o instanceof Map.Entry)) + return false; + Map.Entry me = (Map.Entry) o; + Object key = me.getKey(); + if (! keyInRange(key)) + return false; + Node n = getNode(key); + return n != nil && AbstractSet.equals(me.getValue(), n.value); + } + + public boolean remove(Object o) + { + if (! (o instanceof Map.Entry)) + return false; + Map.Entry me = (Map.Entry) o; + Object key = me.getKey(); + if (! keyInRange(key)) + return false; + Node n = getNode(key); + if (n != nil && AbstractSet.equals(me.getValue(), n.value)) + { + removeNode(n); + return true; + } + return false; + } + }; + return entries; } - public Object get(Object key) + public Object firstKey() { - if (keyInRange(key)) - return TreeMap.this.get(key); - return null; + Node node = lowestGreaterThan(minKey, true); + if (node == nil || ! keyInRange(node.key)) + throw new NoSuchElementException(); + return node.key; } - public Object put(Object key, Object value) + public Object get(Object key) { if (keyInRange(key)) - return TreeMap.this.put(key, value); - else - throw new IllegalArgumentException("Key outside range"); + return TreeMap.this.get(key); + return null; } - public Object remove(Object key) + public SortedMap headMap(Object toKey) { - if (keyInRange(key)) - return TreeMap.this.remove(key); - else - return null; + if (! keyInRange(toKey)) + throw new IllegalArgumentException("key outside range"); + return new SubMap(minKey, toKey); } - public int size() + public Set keySet() { - Node node = lowestGreaterThan(minKey); - Node max = highestLessThan(maxKey); + if (this.keys == null) + // Create an AbstractSet with custom implementations of those methods + // that can be overriden easily and efficiently. + this.keys = new AbstractSet() + { + public int size() + { + return SubMap.this.size(); + } - if (node == nil || max == nil || compare(node.key, max.key) > 0) - return 0; // Empty. + public Iterator iterator() + { + Node first = lowestGreaterThan(minKey, true); + Node max = lowestGreaterThan(maxKey, false); + return new TreeIterator(KEYS, first, max); + } - int count = 1; - while (node != max) - { - count++; - node = successor(node); - } + public void clear() + { + SubMap.this.clear(); + } - return count; + public boolean contains(Object o) + { + if (! keyInRange(o)) + return false; + return getNode(o) != nil; + } + + public boolean remove(Object o) + { + if (! keyInRange(o)) + return false; + Node n = getNode(o); + if (n != nil) + { + removeNode(n); + return true; + } + return false; + } + }; + return this.keys; } - public Set entrySet() + public Object lastKey() { - // Create an AbstractSet with custom implementations of those methods that - // can be overriden easily and efficiently. - return new AbstractSet() - { - public int size() - { - return SubMap.this.size(); - } - - public Iterator iterator() - { - Node first = lowestGreaterThan(minKey); - Node max = highestLessThan(maxKey); - return new TreeIterator(TreeIterator.ENTRIES, first, max); - } - - public void clear() - { - this.clear(); - } - - public boolean contains(Object o) - { - if (!(o instanceof Map.Entry)) - return false; - Map.Entry me = (Map.Entry) o; - Object key = me.getKey(); - if (!keyInRange(key)) - return false; - Node n = getNode(key); - return (n != nil && me.getValue().equals(n.value)); - } - - public boolean remove(Object o) - { - if (!(o instanceof Map.Entry)) - return false; - Map.Entry me = (Map.Entry) o; - Object key = me.getKey(); - if (!keyInRange(key)) - return false; - Node n = getNode(key); - if (n != nil && me.getValue().equals(n.value)) - { - removeNode(n); - return true; - } - return false; - } - }; + Node node = highestLessThan(maxKey); + if (node == nil || ! keyInRange(node.key)) + throw new NoSuchElementException(); + return node.key; } - public Comparator comparator() + public Object put(Object key, Object value) { - return comparator; + if (! keyInRange(key)) + throw new IllegalArgumentException("Key outside range"); + return TreeMap.this.put(key, value); } - public Object firstKey() + public Object remove(Object key) { - Node node = lowestGreaterThan(minKey); - if (node == nil || !keyInRange(node.key)) - throw new NoSuchElementException ("empty"); - return node.key; + if (keyInRange(key)) + return TreeMap.this.remove(key); + return null; } - public Object lastKey() + public int size() { - Node node = highestLessThan(maxKey); - if (node == nil || !keyInRange(node.key)) - throw new NoSuchElementException ("empty"); - return node.key; + Node node = lowestGreaterThan(minKey, true); + Node max = lowestGreaterThan(maxKey, false); + int count = 0; + while (node != max) + { + count++; + node = successor(node); + } + return count; } public SortedMap subMap(Object fromKey, Object toKey) { - if (!keyInRange(fromKey) || !keyInRange(toKey)) + if (! keyInRange(fromKey) || ! keyInRange(toKey)) throw new IllegalArgumentException("key outside range"); - - return TreeMap.this.subMap(fromKey, toKey); + return new SubMap(fromKey, toKey); } - public SortedMap headMap(Object toKey) + public SortedMap tailMap(Object fromKey) { - if (!keyInRange(toKey)) + if (! keyInRange(fromKey)) throw new IllegalArgumentException("key outside range"); - - return TreeMap.this.subMap(minKey, toKey); + return new SubMap(fromKey, maxKey); } - public SortedMap tailMap(Object fromKey) + public Collection values() { - if (!keyInRange(fromKey)) - throw new IllegalArgumentException("key outside range"); + if (this.values == null) + // Create an AbstractCollection with custom implementations of those + // methods that can be overriden easily and efficiently. + this.values = new AbstractCollection() + { + public int size() + { + return SubMap.this.size(); + } - return TreeMap.this.subMap(fromKey, maxKey); + public Iterator iterator() + { + Node first = lowestGreaterThan(minKey, true); + Node max = lowestGreaterThan(maxKey, false); + return new TreeIterator(VALUES, first, max); + } + + public void clear() + { + SubMap.this.clear(); + } + }; + return this.keys; } - } -} + } // class SubMap +} // class TreeMap diff --git a/libjava/java/util/TreeSet.java b/libjava/java/util/TreeSet.java index ba852131a13..3d2ef3d24d1 100644 --- a/libjava/java/util/TreeSet.java +++ b/libjava/java/util/TreeSet.java @@ -1,4 +1,4 @@ -/* TreeSet.java -- a class providing a TreeMap-backet SortedSet +/* TreeSet.java -- a class providing a TreeMap-backed SortedSet Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -33,54 +33,91 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; /** - * This class provides a TreeMap-backed implementation of the - * SortedSet interface. + * This class provides a TreeMap-backed implementation of the SortedSet + * interface. The elements will be sorted according to their <i>natural + * order</i>, or according to the provided <code>Comparator</code>.<p> * - * Each element in the Set is a key in the backing TreeMap; each key - * maps to a static token, denoting that the key does, in fact, exist. + * Most operations are O(log n), but there is so much overhead that this + * makes small sets expensive. Note that the ordering must be <i>consistent + * with equals</i> to correctly implement the Set interface. If this + * condition is violated, the set is still well-behaved, but you may have + * suprising results when comparing it to other sets.<p> * - * Most operations are O(log n). + * This implementation is not synchronized. If you need to share this between + * multiple threads, do something like:<br> + * <code>SortedSet s + * = Collections.synchronizedSortedSet(new TreeSet(...));</code><p> * - * TreeSet is a part of the JDK1.2 Collections API. + * The iterators are <i>fail-fast</i>, meaning that any structural + * modification, except for <code>remove()</code> called on the iterator + * itself, cause the iterator to throw a + * <code>ConcurrentModificationException</code> rather than exhibit + * non-deterministic behavior. * - * @author Jon Zeppieri + * @author Jon Zeppieri + * @author Bryce McKinlay + * @author Eric Blake <ebb9@email.byu.edu> + * @see Collection + * @see Set + * @see HashSet + * @see LinkedHashSet + * @see Comparable + * @see Comparator + * @see Collections#synchronizedSortedSet(SortedSet) + * @see TreeMap + * @since 1.2 + * @status updated to 1.4 */ - public class TreeSet extends AbstractSet implements SortedSet, Cloneable, Serializable { - /** The TreeMap which backs this Set */ - transient SortedMap map; + /** + * Compatible with JDK 1.2. + */ + private static final long serialVersionUID = -2479143000061671589L; - static final long serialVersionUID = -2479143000061671589L; + /** + * The SortedMap which backs this Set. + */ + // Not final because of readObject. This will always be one of TreeMap or + // TreeMap.SubMap, which both extend AbstractMap. + private transient SortedMap map; /** - * Construct a new TreeSet whose backing TreeMap using the "natural" - * ordering of keys. + * Construct a new TreeSet whose backing TreeMap using the "natural" + * ordering of keys. Elements that are not mutually comparable will cause + * ClassCastExceptions down the road. + * + * @see Comparable */ public TreeSet() { map = new TreeMap(); } - /** - * Construct a new TreeSet whose backing TreeMap uses the supplied - * Comparator. + /** + * Construct a new TreeSet whose backing TreeMap uses the supplied + * Comparator. Elements that are not mutually comparable will cause + * ClassCastExceptions down the road. * - * @param oComparator the Comparator this Set will use + * @param comparator the Comparator this Set will use */ public TreeSet(Comparator comparator) { map = new TreeMap(comparator); } - /** + /** * Construct a new TreeSet whose backing TreeMap uses the "natural" * orering of the keys and which contains all of the elements in the - * supplied Collection. + * supplied Collection. This runs in n*log(n) time. * - * @param oCollection the new Set will be initialized with all - * of the elements in this Collection + * @param collection the new Set will be initialized with all + * of the elements in this Collection + * @throws ClassCastException if the elements of the collection are not + * comparable + * @throws NullPointerException if the collection is null + * @see Comparable */ public TreeSet(Collection collection) { @@ -93,54 +130,57 @@ public class TreeSet extends AbstractSet * SortedSet and containing all of the elements in the supplied SortedSet. * This constructor runs in linear time. * - * @param sortedSet the new TreeSet will use this SortedSet's - * comparator and will initialize itself - * with all of the elements in this SortedSet + * @param sortedSet the new TreeSet will use this SortedSet's comparator + * and will initialize itself with all its elements + * @throws NullPointerException if sortedSet is null */ public TreeSet(SortedSet sortedSet) { - TreeMap map = new TreeMap(sortedSet.comparator()); - int i = 0; + map = new TreeMap(sortedSet.comparator()); Iterator itr = sortedSet.iterator(); - map.putKeysLinear(itr, sortedSet.size()); - this.map = map; + ((TreeMap) map).putKeysLinear(itr, sortedSet.size()); } - - /* This private constructor is used to implement the subSet() calls around - a backing TreeMap.SubMap. */ - TreeSet(SortedMap backingMap) + + /** + * This private constructor is used to implement the subSet() calls around + * a backing TreeMap.SubMap. + * + * @param backingMap the submap + */ + private TreeSet(SortedMap backingMap) { map = backingMap; } - /** + /** * Adds the spplied Object to the Set if it is not already in the Set; - * returns true if the element is added, false otherwise + * returns true if the element is added, false otherwise. * - * @param obj the Object to be added to this Set + * @param obj the Object to be added to this Set + * @throws ClassCastException if the element cannot be compared with objects + * already in the set */ public boolean add(Object obj) { - return (map.put(obj, Boolean.TRUE) == null); + return map.put(obj, "") == null; } /** * Adds all of the elements in the supplied Collection to this TreeSet. * - * @param c All of the elements in this Collection - * will be added to the Set. - * - * @return true if the Set is altered, false otherwise + * @param c The collection to add + * @return true if the Set is altered, false otherwise + * @throws NullPointerException if c is null + * @throws ClassCastException if an element in c cannot be compared with + * objects already in the set */ public boolean addAll(Collection c) { boolean result = false; - int size = c.size(); + int pos = c.size(); Iterator itr = c.iterator(); - - for (int i = 0; i < size; i++) - result |= (map.put(itr.next(), Boolean.TRUE) == null); - + while (--pos >= 0) + result |= (map.put(itr.next(), "") == null); return result; } @@ -152,137 +192,214 @@ public class TreeSet extends AbstractSet map.clear(); } - /** Returns a shallow copy of this Set. */ + /** + * Returns a shallow copy of this Set. The elements are not cloned. + * + * @return the cloned set + */ public Object clone() { TreeSet copy = null; try { copy = (TreeSet) super.clone(); + // Map may be either TreeMap or TreeMap.SubMap, hence the ugly casts. + copy.map = (SortedMap) ((AbstractMap) map).clone(); } catch (CloneNotSupportedException x) - { + { + // Impossible result. } - copy.map = (SortedMap) ((TreeMap) map).clone(); return copy; } - /** Returns this Set's comparator */ + /** + * Returns this Set's comparator. + * + * @return the comparator, or null if the set uses natural ordering + */ public Comparator comparator() { return map.comparator(); } - /** - * Returns true if this Set contains the supplied Object, - * false otherwise + /** + * Returns true if this Set contains the supplied Object, false otherwise. * - * @param oObject the Object whose existence in the Set is - * being tested + * @param obj the Object to check for + * @return true if it is in the set + * @throws ClassCastException if obj cannot be compared with objects + * already in the set */ public boolean contains(Object obj) { return map.containsKey(obj); } - /** Returns true if this Set has size 0, false otherwise */ - public boolean isEmpty() + /** + * Returns the first (by order) element in this Set. + * + * @return the first element + * @throws NoSuchElementException if the set is empty + */ + public Object first() { - return map.isEmpty(); + return map.firstKey(); } - /** Returns the number of elements in this Set */ - public int size() + /** + * Returns a view of this Set including all elements less than + * <code>to</code>. The returned set is backed by the original, so changes + * in one appear in the other. The subset will throw an + * {@link IllegalArgumentException} for any attempt to access or add an + * element beyond the specified cutoff. The returned set does not include + * the endpoint; if you want inclusion, pass the successor element. + * + * @param to the (exclusive) cutoff point + * @return a view of the set less than the cutoff + * @throws ClassCastException if <code>to</code> is not compatible with + * the comparator (or is not Comparable, for natural ordering) + * @throws NullPointerException if to is null, but the comparator does not + * tolerate null elements + */ + public SortedSet headSet(Object to) { - return map.size(); + return new TreeSet(map.headMap(to)); } - /** - * If the supplied Object is in this Set, it is removed, and true is - * returned; otherwise, false is returned. + /** + * Returns true if this Set has size 0, false otherwise. * - * @param obj the Object we are attempting to remove - * from this Set + * @return true if the set is empty */ - public boolean remove(Object obj) + public boolean isEmpty() { - return (map.remove(obj) != null); + return map.isEmpty(); } - /** Returns the first (by order) element in this Set */ - public Object first() + /** + * Returns in Iterator over the elements in this TreeSet, which traverses + * in ascending order. + * + * @return an iterator + */ + public Iterator iterator() { - return map.firstKey(); + return map.keySet().iterator(); } - /** Returns the last (by order) element in this Set */ + /** + * Returns the last (by order) element in this Set. + * + * @return the last element + * @throws NoSuchElementException if the set is empty + */ public Object last() { return map.lastKey(); } /** - * Returns a view of this Set including all elements in the interval - * [oFromElement, oToElement). + * If the supplied Object is in this Set, it is removed, and true is + * returned; otherwise, false is returned. * - * @param from the resultant view will contain all - * elements greater than or equal to this element - * @param to the resultant view will contain all - * elements less than this element + * @param obj the Object to remove from this Set + * @return true if the set was modified + * @throws ClassCastException if obj cannot be compared to set elements */ - public SortedSet subSet(Object from, Object to) + public boolean remove(Object obj) { - return new TreeSet(map.subMap(from, to)); + return map.remove(obj) != null; } /** - * Returns a view of this Set including all elements less than oToElement + * Returns the number of elements in this Set * - * @param toElement the resultant view will contain all - * elements less than this element + * @return the set size */ - public SortedSet headSet(Object to) + public int size() { - return new TreeSet(map.headMap(to)); + return map.size(); } /** - * Returns a view of this Set including all elements greater than or - * equal to oFromElement. + * Returns a view of this Set including all elements greater or equal to + * <code>from</code> and less than <code>to</code> (a half-open interval). + * The returned set is backed by the original, so changes in one appear in + * the other. The subset will throw an {@link IllegalArgumentException} + * for any attempt to access or add an element beyond the specified cutoffs. + * The returned set includes the low endpoint but not the high; if you want + * to reverse this behavior on either end, pass in the successor element. * - * @param from the resultant view will contain all - * elements greater than or equal to this element + * @param from the (inclusive) low cutoff point + * @param to the (exclusive) high cutoff point + * @return a view of the set between the cutoffs + * @throws ClassCastException if either cutoff is not compatible with + * the comparator (or is not Comparable, for natural ordering) + * @throws NullPointerException if from or to is null, but the comparator + * does not tolerate null elements + * @throws IllegalArgumentException if from is greater than to */ - public SortedSet tailSet(Object from) + public SortedSet subSet(Object from, Object to) { - return new TreeSet(map.tailMap(from)); + return new TreeSet(map.subMap(from, to)); } - /** Returns in Iterator over the elements in this TreeSet */ - public Iterator iterator() + /** + * Returns a view of this Set including all elements greater or equal to + * <code>from</code>. The returned set is backed by the original, so + * changes in one appear in the other. The subset will throw an + * {@link IllegalArgumentException} for any attempt to access or add an + * element beyond the specified cutoff. The returned set includes the + * endpoint; if you want to exclude it, pass in the successor element. + * + * @param from the (inclusive) low cutoff point + * @return a view of the set above the cutoff + * @throws ClassCastException if <code>from</code> is not compatible with + * the comparator (or is not Comparable, for natural ordering) + * @throws NullPointerException if from is null, but the comparator + * does not tolerate null elements + */ + public SortedSet tailSet(Object from) { - return map.keySet().iterator(); + return new TreeSet(map.tailMap(from)); } - private void writeObject(ObjectOutputStream out) throws IOException + /** + * Serializes this object to the given stream. + * + * @param s the stream to write to + * @throws IOException if the underlying stream fails + * @serialData the <i>comparator</i> (Object), followed by the set size + * (int), the the elements in sorted order (Object) + */ + private void writeObject(ObjectOutputStream s) throws IOException { + s.defaultWriteObject(); Iterator itr = map.keySet().iterator(); - int size = map.size(); - - out.writeObject(map.comparator()); - out.writeInt(size); - - for (int i = 0; i < size; i++) - out.writeObject(itr.next()); + int pos = map.size(); + s.writeObject(map.comparator()); + s.writeInt(pos); + while (--pos >= 0) + s.writeObject(itr.next()); } - private void readObject(ObjectInputStream in) + /** + * Deserializes this object from the given stream. + * + * @param s the stream to read from + * @throws ClassNotFoundException if the underlying stream fails + * @throws IOException if the underlying stream fails + * @serialData the <i>comparator</i> (Object), followed by the set size + * (int), the the elements in sorted order (Object) + */ + private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { - Comparator comparator = (Comparator) in.readObject(); - int size = in.readInt(); - TreeMap map = new TreeMap(comparator); - map.putFromObjStream(in, size, false); - this.map = map; + s.defaultReadObject(); + Comparator comparator = (Comparator) s.readObject(); + int size = s.readInt(); + map = new TreeMap(comparator); + ((TreeMap) map).putFromObjStream(s, size, false); } } diff --git a/libjava/java/util/Vector.java b/libjava/java/util/Vector.java index cef84e51e72..24d80f8ca8f 100644 --- a/libjava/java/util/Vector.java +++ b/libjava/java/util/Vector.java @@ -1,5 +1,5 @@ /* Vector.java -- Class that provides growable arrays. - Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc. + Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -30,51 +30,73 @@ import java.lang.reflect.Array; import java.io.Serializable; /** - * the <b>Vector</b> classes implements growable arrays of Objects. + * The <code>Vector</code> classes implements growable arrays of Objects. * You can access elements in a Vector with an index, just as you * can in a built in array, but Vectors can grow and shrink to accommodate - * more or fewer objects. + * more or fewer objects.<p> * * Vectors try to mantain efficiency in growing by having a - * <b>capacityIncrement</b> that can be specified at instantiation. + * <code>capacityIncrement</code> that can be specified at instantiation. * When a Vector can no longer hold a new Object, it grows by the amount - * in <b>capacityIncrement</b>. + * in <code>capacityIncrement</code>. If this value is 0, the vector doubles in + * size.<p> * - * Vector implements the JDK 1.2 List interface, and is therefor a fully - * compliant Collection object. + * Vector implements the JDK 1.2 List interface, and is therefore a fully + * compliant Collection object. The iterators are fail-fast - if external + * code structurally modifies the vector, any operation on the iterator will + * then throw a {@link ConcurrentModificationException}. The Vector class is + * fully synchronized, but the iterators are not. So, when iterating over a + * vector, be sure to synchronize on the vector itself. If you don't want the + * expense of synchronization, use ArrayList instead. On the other hand, the + * Enumeration of elements() is not thread-safe, nor is it fail-fast; so it + * can lead to undefined behavior even in a single thread if you modify the + * vector during iteration.<p> + * + * Note: Some methods, especially those specified by List, specify throwing + * {@link IndexOutOfBoundsException}, but it is easier to implement by + * throwing the subclass {@link ArrayIndexOutOfBoundsException}. Others + * directly specify this subclass. * - * @specnote The JCL claims that various methods in this class throw - * IndexOutOfBoundsException, which would be consistent with other collections - * classes. ArrayIndexOutOfBoundsException is actually thrown, per the online - * docs, even for List method implementations. - * * @author Scott G. Miller + * @author Bryce McKinlay + * @author Eric Blake <ebb9@email.byu.edu> + * @see Collection + * @see List + * @see ArrayList + * @see LinkedList + * @since 1.0 + * @status updated to 1.4 */ -public class Vector extends AbstractList - implements List, Cloneable, Serializable +public class Vector extends AbstractList + implements List, RandomAccess, Cloneable, Serializable { /** - * The amount the Vector's internal array should be increased in size when - * a new element is added that exceeds the current size of the array, - * or when {@link #ensureCapacity} is called. - * @serial + * Compatible with JDK 1.0+. */ - protected int capacityIncrement = 0; + private static final long serialVersionUID = -2767605614048989439L; + + /** + * The internal array used to hold members of a Vector. The elements are + * in positions 0 through elementCount - 1, and all remaining slots are null. + * @serial the elements + */ + protected Object[] elementData; /** * The number of elements currently in the vector, also returned by * {@link #size}. - * @serial + * @serial the size */ - protected int elementCount = 0; + protected int elementCount; /** - * The internal array used to hold members of a Vector - * @serial + * The amount the Vector's internal array should be increased in size when + * a new element is added that exceeds the current size of the array, + * or when {@link #ensureCapacity} is called. If <= 0, the vector just + * doubles in size. + * @serial the amount to grow the vector by */ - protected Object[] elementData; - - private static final long serialVersionUID = -2767605614048989439L; + protected int capacityIncrement; /** * Constructs an empty vector with an initial size of 10, and @@ -82,36 +104,31 @@ public class Vector extends AbstractList */ public Vector() { - this(10); + this(10, 0); } /** * Constructs a vector containing the contents of Collection, in the - * order given by the collection + * order given by the collection. * - * @param c A collection of elements to be added to the newly constructed - * vector + * @param c collection of elements to add to the new vector + * @throws NullPointerException if c is null + * @since 1.2 */ public Vector(Collection c) { - int csize = c.size(); - elementData = new Object[csize]; - elementCount = csize; - Iterator itr = c.iterator(); - for (int i = 0; i < csize; i++) - { - elementData[i] = itr.next(); - } + elementCount = c.size(); + elementData = c.toArray(new Object[elementCount]); } /** - * Constructs a Vector with the initial capacity and capacity - * increment specified + * Constructs a Vector with the initial capacity and capacity + * increment specified. * - * @param initialCapacity The initial size of the Vector's internal - * array - * @param capacityIncrement The amount the internal array should be - * increased if necessary + * @param initialCapacity the initial size of the Vector's internal array + * @param capacityIncrement the amount the internal array should be + * increased by when necessary, 0 to double the size + * @throws IllegalArgumentException if initialCapacity < 0 */ public Vector(int initialCapacity, int capacityIncrement) { @@ -122,37 +139,37 @@ public class Vector extends AbstractList } /** - * Constructs a Vector with the initial capacity specified + * Constructs a Vector with the initial capacity specified, and a capacity + * increment of 0 (double in size). * - * @param initialCapacity The initial size of the Vector's internal array + * @param initialCapacity the initial size of the Vector's internal array + * @throws IllegalArgumentException if initialCapacity < 0 */ public Vector(int initialCapacity) { - if (initialCapacity < 0) - throw new IllegalArgumentException(); - elementData = new Object[initialCapacity]; + this(initialCapacity, 0); } /** - * Copies the contents of a provided array into the Vector. If the - * array is too large to fit in the Vector, an ArrayIndexOutOfBoundsException - * is thrown. Old elements in the Vector are overwritten by the new - * elements + * Copies the contents of a provided array into the Vector. If the + * array is too large to fit in the Vector, an IndexOutOfBoundsException + * is thrown without modifying the array. Old elements in the Vector are + * overwritten by the new elements. * - * @param anArray An array from which elements will be copied into the Vector - * - * @throws ArrayIndexOutOfBoundsException the array being copied - * is larger than the Vectors internal data array + * @param a target array for the copy + * @throws IndexOutOfBoundsException the array is not large enough + * @throws NullPointerException the array is null + * @see #toArray(Object[]) */ - public synchronized void copyInto(Object[] anArray) + public synchronized void copyInto(Object[] a) { - System.arraycopy(elementData, 0, anArray, 0, elementCount); + System.arraycopy(elementData, 0, a, 0, elementCount); } /** * Trims the Vector down to size. If the internal data array is larger * than the number of Objects its holding, a new array is constructed - * that precisely holds the elements. + * that precisely holds the elements. Otherwise this does nothing. */ public synchronized void trimToSize() { @@ -166,68 +183,70 @@ public class Vector extends AbstractList } /** - * Ensures that <b>minCapacity</b> elements can fit within this Vector. - * If it cannot hold this many elements, the internal data array is expanded - * in the following manner. If the current size plus the capacityIncrement - * is sufficient, the internal array is expanded by capacityIncrement. - * If capacityIncrement is non-positive, the size is doubled. If - * neither is sufficient, the internal array is expanded to size minCapacity + * Ensures that <code>minCapacity</code> elements can fit within this Vector. + * If <code>elementData</code> is too small, it is expanded as follows: + * If the <code>elementCount + capacityIncrement</code> is adequate, that + * is the new size. If <code>capacityIncrement</code> is non-zero, the + * candidate size is double the current. If that is not enough, the new + * size is <code>minCapacity</code>. * - * @param minCapacity The minimum capacity the internal array should be - * able to handle after executing this method + * @param minCapacity the desired minimum capacity, negative values ignored */ public synchronized void ensureCapacity(int minCapacity) { if (elementData.length >= minCapacity) return; - int newCapacity; + int newCapacity; if (capacityIncrement <= 0) newCapacity = elementData.length * 2; else newCapacity = elementData.length + capacityIncrement; - + Object[] newArray = new Object[Math.max(newCapacity, minCapacity)]; - System.arraycopy(elementData, 0, newArray, 0, elementData.length); + System.arraycopy(elementData, 0, newArray, 0, elementCount); elementData = newArray; } /** - * Explicitly sets the size of the internal data array, copying the - * old values to the new internal array. If the new array is smaller - * than the old one, old values that don't fit are lost. If the new size - * is larger than the old one, the vector is padded with null entries. + * Explicitly sets the size of the vector (but not necessarily the size of + * the internal data array). If the new size is smaller than the old one, + * old values that don't fit are lost. If the new size is larger than the + * old one, the vector is padded with null entries. * * @param newSize The new size of the internal array + * @throws ArrayIndexOutOfBoundsException if the new size is negative */ public synchronized void setSize(int newSize) { + // Don't bother checking for the case where size() == the capacity of the + // vector since that is a much less likely case; it's more efficient to + // not do the check and lose a bit of performance in that infrequent case modCount++; - Object[] newArray = new Object[newSize]; - System.arraycopy(elementData, 0, newArray, 0, - Math.min(newSize, elementCount)); + ensureCapacity(newSize); + if (newSize < elementCount) + Arrays.fill(elementData, newSize, elementCount, null); elementCount = newSize; - elementData = newArray; } /** * Returns the size of the internal data array (not the amount of elements - * contained in the Vector) + * contained in the Vector). * - * @returns capacity of the internal data array + * @return capacity of the internal data array */ - public int capacity() + public synchronized int capacity() { return elementData.length; } /** - * Returns the number of elements stored in this Vector + * Returns the number of elements stored in this Vector. * - * @returns the number of elements in this Vector + * @return the number of elements in this Vector */ - public int size() + public synchronized int size() { return elementCount; } @@ -235,85 +254,89 @@ public class Vector extends AbstractList /** * Returns true if this Vector is empty, false otherwise * - * @returns true if the Vector is empty, false otherwise + * @return true if the Vector is empty, false otherwise */ - public boolean isEmpty() + public synchronized boolean isEmpty() { return elementCount == 0; } /** - * Searches the vector starting at <b>index</b> for object <b>elem</b> - * and returns the index of the first occurrence of this Object. If - * the object is not found, -1 is returned + * Returns an Enumeration of the elements of this Vector. The enumeration + * visits the elements in increasing index order, but is NOT thread-safe. * - * @param e The Object to search for - * @param index Start searching at this index - * @returns The index of the first occurrence of <b>elem</b>, or -1 - * if it is not found + * @return an Enumeration + * @see #iterator() */ - public synchronized int indexOf(Object e, int index) + // No need to synchronize as the Enumeration is not thread-safe! + public Enumeration elements() { - for (int i = index; i < elementCount; i++) + return new Enumeration() + { + private int i = 0; + + public boolean hasMoreElements() { - if (e == null ? elementData[i] == null : e.equals(elementData[i])) - return i; + return i < elementCount; } - return -1; + + public Object nextElement() + { + if (i >= elementCount) + throw new NoSuchElementException(); + return elementData[i++]; + } + }; } /** - * Returns the first occurrence of <b>elem</b> in the Vector, or -1 if - * <b>elem</b> is not found. + * Returns true when <code>elem</code> is contained in this Vector. * - * @param elem The object to search for - * @returns The index of the first occurrence of <b>elem</b> or -1 if - * not found + * @param elem the element to check + * @return true if the object is contained in this Vector, false otherwise */ - public int indexOf(Object elem) + public boolean contains(Object elem) { - return indexOf(elem, 0); + return indexOf(elem, 0) >= 0; } /** - * Returns true if <b>elem</b> is contained in this Vector, false otherwise. + * Returns the first occurrence of <code>elem</code> in the Vector, or -1 if + * <code>elem</code> is not found. * - * @param elem The element to check - * @returns true if the object is contained in this Vector, false otherwise + * @param elem the object to search for + * @return the index of the first occurrence, or -1 if not found */ - public boolean contains(Object elem) + public int indexOf(Object elem) { - return indexOf(elem, 0) != -1; + return indexOf(elem, 0); } /** - * Returns the index of the first occurrence of <b>elem</b>, when searching - * backwards from <b>index</b>. If the object does not occur in this Vector, - * -1 is returned. + * Searches the vector starting at <code>index</code> for object + * <code>elem</code> and returns the index of the first occurrence of this + * Object. If the object is not found, or index is larger than the size + * of the vector, -1 is returned. * - * @param eThe object to search for - * @param index The index to start searching in reverse from - * @returns The index of the Object if found, -1 otherwise + * @param e the Object to search for + * @param index start searching at this index + * @return the index of the next occurrence, or -1 if it is not found + * @throws IndexOutOfBoundsException if index < 0 */ - public synchronized int lastIndexOf(Object e, int index) + public synchronized int indexOf(Object e, int index) { - if (index >= elementCount) - throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount); - - for (int i = index; i >= 0; i--) - { - if (e == null ? elementData[i] == null : e.equals(elementData[i])) - return i; - } + for (int i = index; i < elementCount; i++) + if (equals(e, elementData[i])) + return i; return -1; } /** - * Returns the last index of <b>elem</b> within this Vector, or -1 - * if the object is not within the Vector + * Returns the last index of <code>elem</code> within this Vector, or -1 + * if the object is not within the Vector. * - * @param elem The object to search for - * @returns the last index of the object, or -1 if not found + * @param elem the object to search for + * @return the last index of the object, or -1 if not found */ public int lastIndexOf(Object elem) { @@ -321,29 +344,42 @@ public class Vector extends AbstractList } /** - * Returns the Object stored at <b>index</b>. If index is out of range - * an ArrayIndexOutOfBoundsException is thrown. + * Returns the index of the first occurrence of <code>elem</code>, when + * searching backwards from <code>index</code>. If the object does not + * occur in this Vector, or index is less than 0, -1 is returned. + * + * @param e the object to search for + * @param index the index to start searching in reverse from + * @return the index of the Object if found, -1 otherwise + * @throws IndexOutOfBoundsException if index >= size() + */ + public synchronized int lastIndexOf(Object e, int index) + { + checkBoundExclusive(index); + for (int i = index; i >= 0; i--) + if (equals(e, elementData[i])) + return i; + return -1; + } + + /** + * Returns the Object stored at <code>index</code>. * * @param index the index of the Object to retrieve - * @returns The object at <b>index</b> - * @throws ArrayIndexOutOfBoundsException <b>index</b> is - * larger than the Vector + * @return the object at <code>index</code> + * @throws ArrayIndexOutOfBoundsException index < 0 || index >= size() + * @see #get(int) */ public synchronized Object elementAt(int index) { - //Within the bounds of this Vector does not necessarily mean within - //the bounds of the internal array - if (index >= elementCount) - throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount); - + checkBoundExclusive(index); return elementData[index]; } /** - * Returns the first element in the Vector. If there is no first Object - * (The vector is empty), a NoSuchElementException is thrown. + * Returns the first element (index 0) in the Vector. * - * @returns The first Object in the Vector + * @return the first Object in the Vector * @throws NoSuchElementException the Vector is empty */ public synchronized Object firstElement() @@ -351,14 +387,13 @@ public class Vector extends AbstractList if (elementCount == 0) throw new NoSuchElementException(); - return elementAt(0); + return elementData[0]; } /** - * Returns the last element in the Vector. If the Vector has no last element - * (The vector is empty), a NoSuchElementException is thrown. + * Returns the last element in the Vector. * - * @returns The last Object in the Vector + * @return the last Object in the Vector * @throws NoSuchElementException the Vector is empty */ public synchronized Object lastElement() @@ -366,95 +401,61 @@ public class Vector extends AbstractList if (elementCount == 0) throw new NoSuchElementException(); - return elementAt(elementCount - 1); - } - - /** - * Places <b>obj</b> at <b>index</b> within the Vector. If <b>index</b> - * refers to an index outside the Vector, an ArrayIndexOutOfBoundsException - * is thrown. - * - * @param obj The object to store - * @param index The position in the Vector to store the object - * @throws ArrayIndexOutOfBoundsException the index is out of range - */ - public synchronized void setElementAt(Object obj, int index) - { - if (index >= elementCount) - throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount); - - elementData[index] = obj; + return elementData[elementCount - 1]; } /** - * Puts <b>element</b> into the Vector at position <b>index</b> and returns - * the Object that previously occupied that position. + * Changes the element at <code>index</code> to be <code>obj</code> * - * @param index The index within the Vector to place the Object - * @param element The Object to store in the Vector - * @returns The previous object at the specified index + * @param obj the object to store + * @param index the position in the Vector to store the object * @throws ArrayIndexOutOfBoundsException the index is out of range - * + * @see #set(int, Object) */ - public synchronized Object set(int index, Object element) + public void setElementAt(Object obj, int index) { - if (index >= elementCount) - throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount); - - Object temp = elementData[index]; - elementData[index] = element; - return temp; + set(index, obj); } /** - * Removes the element at <b>index</b>, and shifts all elements at - * positions greater than index to their index - 1. + * Removes the element at <code>index</code>, and shifts all elements at + * positions greater than index to their index - 1. * - * @param index The index of the element to remove + * @param index the index of the element to remove + * @throws ArrayIndexOutOfBoundsException index < 0 || index >= size(); + * @see #remove(int) */ - public synchronized void removeElementAt(int index) + public void removeElementAt(int index) { - if (index >= elementCount) - throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount); - - modCount++; - elementCount--; - if (index < elementCount) - System.arraycopy(elementData, index + 1, elementData, index, - elementCount - index); - //Delete the last element (which has been copied back one index) - //so it can be garbage collected; - elementData[elementCount] = null; + remove(index); } /** - * Inserts a new element into the Vector at <b>index</b>. Any elements + * Inserts a new element into the Vector at <code>index</code>. Any elements * at or greater than index are shifted up one position. * - * @param obj The object to insert - * @param index The index at which the object is inserted + * @param obj the object to insert + * @param index the index at which the object is inserted + * @throws ArrayIndexOutOfBoundsException index < 0 || index > size() + * @see #add(int, Object) */ - public void insertElementAt(Object obj, int index) + public synchronized void insertElementAt(Object obj, int index) { - if (index > elementCount) - throw new ArrayIndexOutOfBoundsException(index + " > " + elementCount); - + checkBoundInclusive(index); if (elementCount == elementData.length) ensureCapacity(elementCount + 1); - ++modCount; - ++elementCount; + modCount++; System.arraycopy(elementData, index, elementData, index + 1, - elementCount - 1 - index); + elementCount - index); + elementCount++; elementData[index] = obj; } /** - * Adds an element to the Vector at the end of the Vector. If the vector - * cannot hold the element with its present capacity, its size is increased - * based on the same rules followed if ensureCapacity was called with the - * argument currentSize+1. + * Adds an element to the Vector at the end of the Vector. The vector + * is increased by ensureCapacity(size() + 1) if needed. * - * @param obj The object to add to the Vector + * @param obj the object to add to the Vector */ public synchronized void addElement(Object obj) { @@ -465,20 +466,21 @@ public class Vector extends AbstractList } /** - * Removes the first occurrence of the given object from the Vector. - * If such a remove was performed (the object was found), true is returned. - * If there was no such object, false is returned. + * Removes the first (the lowestindex) occurance of the given object from + * the Vector. If such a remove was performed (the object was found), true + * is returned. If there was no such object, false is returned. * - * @param obj The object to remove from the Vector - * @returns true if the Object was in the Vector, false otherwise + * @param obj the object to remove from the Vector + * @return true if the Object was in the Vector, false otherwise + * @see #remove(Object) */ public synchronized boolean removeElement(Object obj) { - int idx = indexOf(obj); - if (idx != -1) + int idx = indexOf(obj, 0); + if (idx >= 0) { - removeElementAt(idx); - return true; + remove(idx); + return true; } return false; } @@ -486,46 +488,49 @@ public class Vector extends AbstractList /** * Removes all elements from the Vector. Note that this does not * resize the internal data array. + * + * @see #clear() */ public synchronized void removeAllElements() { - modCount++; if (elementCount == 0) return; - for (int i = elementCount - 1; i >= 0; --i) - { - elementData[i] = null; - } + modCount++; + Arrays.fill(elementData, 0, elementCount, null); elementCount = 0; } /** - * Creates a new Vector with the same contents as this one. + * Creates a new Vector with the same contents as this one. The clone is + * shallow; elements are not cloned. + * + * @return the clone of this vector */ public synchronized Object clone() { try { - Vector clone = (Vector) super.clone(); - clone.elementData = (Object[]) elementData.clone(); - return clone; + Vector clone = (Vector) super.clone(); + clone.elementData = (Object[]) elementData.clone(); + return clone; } catch (CloneNotSupportedException ex) { - throw new InternalError(ex.toString()); + // Impossible to get here. + throw new InternalError(ex.toString()); } } /** * Returns an Object array with the contents of this Vector, in the order - * they are stored within this Vector. Note that the Object array returned - * is not the internal data array, and that it holds only the elements - * within the Vector. This is similar to creating a new Object[] with the + * they are stored within this Vector. Note that the Object array returned + * is not the internal data array, and that it holds only the elements + * within the Vector. This is similar to creating a new Object[] with the * size of this Vector, then calling Vector.copyInto(yourArray). * - * @returns An Object[] containing the contents of this Vector in order - * + * @return an Object[] containing the contents of this Vector in order + * @since 1.2 */ public synchronized Object[] toArray() { @@ -535,76 +540,97 @@ public class Vector extends AbstractList } /** - * Returns an array containing the contents of this Vector. + * Returns an array containing the contents of this Vector. * If the provided array is large enough, the contents are copied - * into that array, and a null is placed in the position size(). + * into that array, and a null is placed in the position size(). * In this manner, you can obtain the size of a Vector by the position - * of the null element. If the type of the provided array cannot - * hold the elements, an ArrayStoreException is thrown. - * - * If the provided array is not large enough, - * a new one is created with the contents of the Vector, and no null - * element. The new array is of the same runtime type as the provided - * array. + * of the null element, if you know the vector does not itself contain + * null entries. If the array is not large enough, reflection is used + * to create a bigger one of the same runtime type. * - * - * @param array An array to copy the Vector into if large enough - * @returns An array with the contents of this Vector in order + * @param a an array to copy the Vector into if large enough + * @return an array with the contents of this Vector in order * @throws ArrayStoreException the runtime type of the provided array - * cannot hold the elements of the Vector + * cannot hold the elements of the Vector + * @throws NullPointerException if <code>a</code> is null + * @since 1.2 */ - public synchronized Object[] toArray(Object[] array) + public synchronized Object[] toArray(Object[] a) { - if (array.length < elementCount) - array = (Object[]) Array.newInstance(array.getClass().getComponentType(), - elementCount); - else if (array.length > elementCount) - array[elementCount] = null; - System.arraycopy(elementData, 0, array, 0, elementCount); - return array; + if (a.length < elementCount) + a = (Object[]) Array.newInstance(a.getClass().getComponentType(), + elementCount); + else if (a.length > elementCount) + a[elementCount] = null; + System.arraycopy(elementData, 0, a, 0, elementCount); + return a; } /** - * Returns the element at position <b>index</b> + * Returns the element at position <code>index</code>. * * @param index the position from which an element will be retrieved - * @throws ArrayIndexOutOfBoundsException the index is not within the - * range of the Vector + * @return the element at that position + * @throws ArrayIndexOutOfBoundsException index < 0 || index >= size() + * @since 1.2 */ - public synchronized Object get(int index) + public Object get(int index) { return elementAt(index); } /** - * Removes the given Object from the Vector. If it exists, true - * is returned, if not, false is returned. + * Puts <code>element</code> into the Vector at position <code>index</code> + * and returns the Object that previously occupied that position. * - * @param o The object to remove from the Vector - * @returns true if the Object existed in the Vector, false otherwise + * @param index the index within the Vector to place the Object + * @param element the Object to store in the Vector + * @return the previous object at the specified index + * @throws ArrayIndexOutOfBoundsException index < 0 || index >= size() + * @since 1.2 */ - public boolean remove(Object o) + public synchronized Object set(int index, Object element) { - return removeElement(o); + checkBoundExclusive(index); + Object temp = elementData[index]; + elementData[index] = element; + return temp; } /** * Adds an object to the Vector. * - * @param o The element to add to the Vector + * @param o the element to add to the Vector + * @return true, as specified by List + * @since 1.2 */ - public synchronized boolean add(Object o) + public boolean add(Object o) { addElement(o); return true; } /** + * Removes the given Object from the Vector. If it exists, true + * is returned, if not, false is returned. + * + * @param o the object to remove from the Vector + * @return true if the Object existed in the Vector, false otherwise + * @since 1.2 + */ + public boolean remove(Object o) + { + return removeElement(o); + } + + /** * Adds an object at the specified index. Elements at or above * index are shifted up one position. * - * @param index The index at which to add the element - * @param element The element to add to the Vector + * @param index the index at which to add the element + * @param element the element to add to the Vector + * @throws ArrayIndexOutOfBoundsException index < 0 || index > size() + * @since 1.2 */ public void add(int index, Object element) { @@ -614,153 +640,253 @@ public class Vector extends AbstractList /** * Removes the element at the specified index, and returns it. * - * @param index The position from which to remove the element - * @returns The object removed - * @throws ArrayIndexOutOfBoundsException the index was out of the range - * of the Vector + * @param index the position from which to remove the element + * @return the object removed + * @throws ArrayIndexOutOfBoundsException index < 0 || index >= size() + * @since 1.2 */ public synchronized Object remove(int index) { - if (index >= elementCount) - throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount); - + checkBoundExclusive(index); Object temp = elementData[index]; - removeElementAt(index); + modCount++; + elementCount--; + if (index < elementCount) + System.arraycopy(elementData, index + 1, elementData, index, + elementCount - index); + elementData[elementCount] = null; return temp; } /** - * Clears all elements in the Vector and sets its size to 0 + * Clears all elements in the Vector and sets its size to 0. */ public void clear() { removeAllElements(); } + /** + * Returns true if this Vector contains all the elements in c. + * + * @param c the collection to compare to + * @return true if this vector contains all elements of c + * @throws NullPointerException if c is null + * @since 1.2 + */ public synchronized boolean containsAll(Collection c) { - Iterator itr = c.iterator(); - int size = c.size(); - for (int pos = 0; pos < size; pos++) - { - if (!contains(itr.next())) - return false; - } - return true; + // Here just for the sychronization. + return super.containsAll(c); } + /** + * Appends all elements of the given collection to the end of this Vector. + * Behavior is undefined if the collection is modified during this operation + * (for example, if this == c). + * + * @param c the collection to append + * @return true if this vector changed, in other words c was not empty + * @throws NullPointerException if c is null + * @since 1.2 + */ public synchronized boolean addAll(Collection c) { return addAll(elementCount, c); } - + + /** + * Remove from this vector all elements contained in the given collection. + * + * @param c the collection to filter out + * @return true if this vector changed + * @throws NullPointerException if c is null + * @since 1.2 + */ public synchronized boolean removeAll(Collection c) { - return super.removeAll(c); + int i; + int j; + for (i = 0; i < elementCount; i++) + if (c.contains(elementData[i])) + break; + if (i == elementCount) + return false; + + modCount++; + for (j = i++; i < elementCount; i++) + if (! c.contains(elementData[i])) + elementData[j++] = elementData[i]; + elementCount -= i - j; + return true; } - + + /** + * Retain in this vector only the elements contained in the given collection. + * + * @param c the collection to filter by + * @return true if this vector changed + * @throws NullPointerException if c is null + * @since 1.2 + */ public synchronized boolean retainAll(Collection c) { - return super.retainAll(c); + int i; + int j; + for (i = 0; i < elementCount; i++) + if (! c.contains(elementData[i])) + break; + if (i == elementCount) + return false; + + modCount++; + for (j = i++; i < elementCount; i++) + if (c.contains(elementData[i])) + elementData[j++] = elementData[i]; + elementCount -= i - j; + return true; } + /** + * Inserts all elements of the given collection at the given index of + * this Vector. Behavior is undefined if the collection is modified during + * this operation (for example, if this == c). + * + * @param c the collection to append + * @return true if this vector changed, in other words c was not empty + * @throws NullPointerException if c is null + * @throws ArrayIndexOutOfBoundsException index < 0 || index > size() + * @since 1.2 + */ public synchronized boolean addAll(int index, Collection c) { - if (index < 0 || index > elementCount) - throw new ArrayIndexOutOfBoundsException(index); - modCount++; + checkBoundInclusive(index); Iterator itr = c.iterator(); int csize = c.size(); + modCount++; ensureCapacity(elementCount + csize); int end = index + csize; if (elementCount > 0 && index != elementCount) System.arraycopy(elementData, index, elementData, end, csize); elementCount += csize; - for (; index < end; index++) - { - elementData[index] = itr.next(); - } - return (csize > 0); + for ( ; index < end; index++) + elementData[index] = itr.next(); + return (csize > 0); } - public synchronized boolean equals(Object c) + /** + * Compares this to the given object. + * + * @param o the object to compare to + * @return true if the two are equal + * @since 1.2 + */ + public synchronized boolean equals(Object o) { - return super.equals(c); + // Here just for the sychronization. + return super.equals(o); } + /** + * Computes the hashcode of this object. + * + * @return the hashcode + * @since 1.2 + */ public synchronized int hashCode() { + // Here just for the sychronization. return super.hashCode(); } /** - * Returns a string representation of this Vector in the form - * [element0, element1, ... elementN] + * Returns a string representation of this Vector in the form + * "[element0, element1, ... elementN]". * - * @returns the String representation of this Vector + * @return the String representation of this Vector */ public synchronized String toString() { - String r = "["; - for (int i = 0; i < elementCount; i++) - { - r += elementData[i]; - if (i < elementCount - 1) - r += ", "; - } - r += "]"; - return r; + // Here just for the sychronization. + return super.toString(); } /** - * Returns an Enumeration of the elements of this List. - * The Enumeration returned is compatible behavior-wise with - * the 1.1 elements() method, in that it does not check for - * concurrent modification. + * Obtain a List view of a subsection of this list, from fromIndex + * (inclusive) to toIndex (exclusive). If the two indices are equal, the + * sublist is empty. The returned list is modifiable, and changes in one + * reflect in the other. If this list is structurally modified in + * any way other than through the returned list, the result of any subsequent + * operations on the returned list is undefined. + * <p> * - * @returns an Enumeration + * @param fromIndex the index that the returned list should start from + * (inclusive) + * @param toIndex the index that the returned list should go to (exclusive) + * @return a List backed by a subsection of this vector + * @throws IndexOutOfBoundsException if fromIndex < 0 + * || toIndex > size() + * @throws IllegalArgumentException if fromIndex > toIndex + * @see ConcurrentModificationException + * @since 1.2 */ - public synchronized Enumeration elements() - { - return new Enumeration() - { - int i = 0; - public boolean hasMoreElements() - { - return (i < elementCount); - } - public Object nextElement() - { - if (i >= elementCount) - throw new NoSuchElementException(); - return (elementAt(i++)); - } - }; - } - - public List subList(int fromIndex, int toIndex) + public synchronized List subList(int fromIndex, int toIndex) { List sub = super.subList(fromIndex, toIndex); - return Collections.synchronizedList(sub); + // We must specify the correct object to synchronize upon, hence the + // use of a non-public API + return new Collections.SynchronizedList(this, sub); } - - /** @specnote This is not specified as synchronized in the JCL, but it seems - * to me that is should be. If it isn't, a clear() operation on a sublist - * will not be synchronized w.r.t. the Vector object. - */ - protected synchronized void removeRange(int fromIndex, int toIndex) + + /** + * Removes a range of elements from this list. + * + * @param fromIndex the index to start deleting from (inclusive) + * @param toIndex the index to delete up to (exclusive) + */ + // This does not need to be synchronized, because it is only called through + // clear() of a sublist, and clear() had already synchronized. + protected void removeRange(int fromIndex, int toIndex) { - modCount++; if (fromIndex != toIndex) { - System.arraycopy(elementData, toIndex, elementData, fromIndex, - elementCount - toIndex); - // Clear unused elements so objects can be collected. - int save = elementCount; - elementCount -= (toIndex - fromIndex); - for (int i = elementCount; i < save; ++i) - elementData[i] = null; + modCount++; + System.arraycopy(elementData, toIndex, elementData, fromIndex, + elementCount - toIndex); + int save = elementCount; + elementCount -= toIndex - fromIndex; + Arrays.fill(elementData, elementCount, save, null); } } + + /** + * Checks that the index is in the range of possible elements (inclusive). + * + * @param index the index to check + * @throws ArrayIndexOutOfBoundsException if index > size + */ + private void checkBoundInclusive(int index) + { + // Implementation note: we do not check for negative ranges here, since + // use of a negative index will cause an ArrayIndexOutOfBoundsException + // with no effort on our part. + if (index > elementCount) + throw new ArrayIndexOutOfBoundsException(index + " > " + elementCount); + } + + /** + * Checks that the index is in the range of existing elements (exclusive). + * + * @param index the index to check + * @throws ArrayIndexOutOfBoundsException if index >= size + */ + private void checkBoundExclusive(int index) + { + // Implementation note: we do not check for negative ranges here, since + // use of a negative index will cause an ArrayIndexOutOfBoundsException + // with no effort on our part. + if (index >= elementCount) + throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount); + } } diff --git a/libjava/java/util/WeakHashMap.java b/libjava/java/util/WeakHashMap.java index 6f39d468108..6366e9822c2 100644 --- a/libjava/java/util/WeakHashMap.java +++ b/libjava/java/util/WeakHashMap.java @@ -1,5 +1,6 @@ -/* java.util.WeakHashMap - Copyright (C) 1999, 2000 Free Software Foundation, Inc. +/* java.util.WeakHashMap -- a hashtable that keeps only weak references + to its keys, allowing the virtual machine to reclaim them + Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -26,6 +27,7 @@ executable file might be covered by the GNU General Public License. */ package java.util; + import java.lang.ref.WeakReference; import java.lang.ref.ReferenceQueue; @@ -50,53 +52,80 @@ import java.lang.ref.ReferenceQueue; * has similar phenomenons: The size may spontaneously shrink, or an * entry, that was in the set before, suddenly disappears. <br> * - * A weak hash map is not meant for caches; use a normal map, with - * soft references as values instead. <br> + * A weak hash map is not meant for caches; use a normal map, with + * soft references as values instead, or try {@link LinkedHashMap}. <br> * - * The weak hash map supports null values and null keys. Null keys - * are never deleted from the map (except explictly of course). + * The weak hash map supports null values and null keys. The null key + * is never deleted from the map (except explictly of course). * The performance of the methods are similar to that of a hash map. <br> * - * The value object are strongly referenced by this table. So if a + * The value objects are strongly referenced by this table. So if a * value object maintains a strong reference to the key (either direct * or indirect) the key will never be removed from this map. According - * to Sun, this problem may be fixed in a future release. It is not + * to Sun, this problem may be fixed in a future release. It is not * possible to do it with the jdk 1.2 reference model, though. * - * @since jdk1.2 - * @author Jochen Hoenicke + * @author Jochen Hoenicke + * @author Eric Blake <ebb9@email.byu.edu> * @see HashMap - * @see WeakReference */ + * @see WeakReference + * @see LinkedHashMap + * @since 1.2 + * @status updated to 1.4 + */ public class WeakHashMap extends AbstractMap implements Map { - /** - * The default capacity for an instance of HashMap. + /** + * The default capacity for an instance of HashMap. * Sun's documentation mildly suggests that this (11) is the correct - * value. + * value. */ private static final int DEFAULT_CAPACITY = 11; - /** - * The default load factor of a HashMap + /** + * The default load factor of a HashMap. */ private static final float DEFAULT_LOAD_FACTOR = 0.75F; /** * This is used instead of the key value <i>null</i>. It is needed - * to distinguish between an null key and a removed key. + * to distinguish between an null key and a removed key. */ - private static final Object NULL_KEY = new Object(); + // Package visible for use by nested classes. + static final Object NULL_KEY = new Object() + { + /** + * Sets the hashCode to 0, since that's what null would map to. + * @return the hash code 0 + */ + public int hashCode() + { + return 0; + } + + /** + * Compares this key to the given object. Normally, an object should + * NEVER compare equal to null, but since we don't publicize NULL_VALUE, + * it saves bytecode to do so here. + * @return true iff o is this or null + */ + public boolean equals(Object o) + { + return null == o || this == o; + } + }; /** * The reference queue where our buckets (which are WeakReferences) are * registered to. */ - private ReferenceQueue queue; + private final ReferenceQueue queue; /** * The number of entries in this hash map. */ - private int size; + // Package visible for use by nested classes. + int size; /** * The load factor of this WeakHashMap. This is the maximum ratio of @@ -108,7 +137,7 @@ public class WeakHashMap extends AbstractMap implements Map /** * The rounded product of the capacity (i.e. number of buckets) and * the load factor. When the number of elements exceeds the - * threshold, the HashMap calls <pre>rehash()</pre>. + * threshold, the HashMap calls <pre>rehash()</pre>. */ private int threshold; @@ -119,17 +148,20 @@ public class WeakHashMap extends AbstractMap implements Map * by the garbage collection. Instead the iterators must make * sure to have strong references to the entries they rely on. */ - private int modCount; + // Package visible for use by nested classes. + int modCount; - /** + /** * The entry set. There is only one instance per hashmap, namely * theEntrySet. Note that the entry set may silently shrink, just * like the WeakHashMap. */ - private class WeakEntrySet extends AbstractSet + private final class WeakEntrySet extends AbstractSet { /** - * Returns the size of this set. + * Returns the size of this set. + * + * @return the set size */ public int size() { @@ -138,151 +170,150 @@ public class WeakHashMap extends AbstractMap implements Map /** * Returns an iterator for all entries. + * + * @return an Entry iterator */ public Iterator iterator() { return new Iterator() { - /** - * The entry that was returned by the last - * <code>next()</code> call. This is also the entry whose - * bucket should be removed by the <code>remove</code> call. <br> - * - * It is null, if the <code>next</code> method wasn't - * called yet, or if the entry was already removed. <br> - * - * Remembering this entry here will also prevent it from - * being removed under us, since the entry strongly refers - * to the key. - */ - WeakBucket.Entry lastEntry; - - /** - * The entry that will be returned by the next - * <code>next()</code> call. It is <code>null</code> if there - * is no further entry. <br> - * - * Remembering this entry here will also prevent it from - * being removed under us, since the entry strongly refers - * to the key. - */ - WeakBucket.Entry nextEntry = findNext(null); - - /** - * The known number of modification to the list, if it differs - * from the real number, we through an exception. - */ - int knownMod = modCount; - - /** - * Check the known number of modification to the number of - * modifications of the table. If it differs from the real - * number, we throw an exception. - * @exception ConcurrentModificationException if the number - * of modifications doesn't match. - */ - private void checkMod() - { - /* This method will get inlined */ - if (knownMod != modCount) - throw new ConcurrentModificationException(); - } - - /** - * Get a strong reference to the next entry after - * lastBucket. - * @param lastBucket the previous bucket, or null if we should - * get the first entry. - * @return the next entry. - */ - private WeakBucket.Entry findNext(WeakBucket.Entry lastEntry) - { - int slot; - WeakBucket nextBucket; - if (lastEntry != null) - { - nextBucket = lastEntry.getBucket().next; - slot = lastEntry.getBucket().slot; - } - else - { - nextBucket = buckets[0]; - slot = 0; - } - - while (true) - { - while (nextBucket != null) - { - WeakBucket.Entry entry = nextBucket.getEntry(); - if (entry != null) - /* This is the next entry */ - return entry; - - /* entry was cleared, try next */ - nextBucket = nextBucket.next; - } - - slot++; - if (slot == buckets.length) - /* No more buckets, we are through */ - return null; - - nextBucket = buckets[slot]; - } - } - - - /** - * Checks if there are more entries. - * @return true, iff there are more elements. - * @exception IllegalModificationException if the hash map was - * modified. - */ - public boolean hasNext() - { - cleanQueue(); - checkMod(); - return (nextEntry != null); - } - - /** - * Returns the next entry. - * @return the next entry. - * @exception IllegalModificationException if the hash map was - * modified. - * @exception NoSuchElementException if there is no entry. - */ - public Object next() - { - cleanQueue(); - checkMod(); - if (nextEntry == null) - throw new NoSuchElementException(); - lastEntry = nextEntry; - nextEntry = findNext(lastEntry); - return lastEntry; - } - - /** - * Removes the last returned entry from this set. This will - * also remove the bucket of the underlying weak hash map. - * @exception IllegalModificationException if the hash map was - * modified. - * @exception IllegalStateException if <code>next()</code> was - * never called or the element was already removed. - */ - public void remove() - { - cleanQueue(); - checkMod(); - if (lastEntry == null) - throw new IllegalStateException(); - internalRemove(lastEntry.getBucket()); - lastEntry = null; - modCount++; - knownMod = modCount; - } + /** + * The entry that was returned by the last + * <code>next()</code> call. This is also the entry whose + * bucket should be removed by the <code>remove</code> call. <br> + * + * It is null, if the <code>next</code> method wasn't + * called yet, or if the entry was already removed. <br> + * + * Remembering this entry here will also prevent it from + * being removed under us, since the entry strongly refers + * to the key. + */ + WeakBucket.WeakEntry lastEntry; + + /** + * The entry that will be returned by the next + * <code>next()</code> call. It is <code>null</code> if there + * is no further entry. <br> + * + * Remembering this entry here will also prevent it from + * being removed under us, since the entry strongly refers + * to the key. + */ + WeakBucket.WeakEntry nextEntry = findNext(null); + + /** + * The known number of modification to the list, if it differs + * from the real number, we throw an exception. + */ + int knownMod = modCount; + + /** + * Check the known number of modification to the number of + * modifications of the table. If it differs from the real + * number, we throw an exception. + * @throws ConcurrentModificationException if the number + * of modifications doesn't match. + */ + private void checkMod() + { + // This method will get inlined. + cleanQueue(); + if (knownMod != modCount) + throw new ConcurrentModificationException(); + } + + /** + * Get a strong reference to the next entry after + * lastBucket. + * @param lastEntry the previous bucket, or null if we should + * get the first entry. + * @return the next entry. + */ + private WeakBucket.WeakEntry findNext(WeakBucket.WeakEntry lastEntry) + { + int slot; + WeakBucket nextBucket; + if (lastEntry != null) + { + nextBucket = lastEntry.getBucket().next; + slot = lastEntry.getBucket().slot; + } + else + { + nextBucket = buckets[0]; + slot = 0; + } + + while (true) + { + while (nextBucket != null) + { + WeakBucket.WeakEntry entry = nextBucket.getEntry(); + if (entry != null) + // This is the next entry. + return entry; + + // Entry was cleared, try next. + nextBucket = nextBucket.next; + } + + slot++; + if (slot == buckets.length) + // No more buckets, we are through. + return null; + + nextBucket = buckets[slot]; + } + } + + /** + * Checks if there are more entries. + * @return true, iff there are more elements. + * @throws ConcurrentModificationException if the hash map was + * modified. + */ + public boolean hasNext() + { + checkMod(); + return nextEntry != null; + } + + /** + * Returns the next entry. + * @return the next entry. + * @throws ConcurrentModificationException if the hash map was + * modified. + * @throws NoSuchElementException if there is no entry. + */ + public Object next() + { + checkMod(); + if (nextEntry == null) + throw new NoSuchElementException(); + lastEntry = nextEntry; + nextEntry = findNext(lastEntry); + return lastEntry; + } + + /** + * Removes the last returned entry from this set. This will + * also remove the bucket of the underlying weak hash map. + * @throws ConcurrentModificationException if the hash map was + * modified. + * @throws IllegalStateException if <code>next()</code> was + * never called or the element was already removed. + */ + public void remove() + { + checkMod(); + if (lastEntry == null) + throw new IllegalStateException(); + modCount++; + internalRemove(lastEntry.getBucket()); + lastEntry = null; + knownMod++; + } }; } } @@ -293,28 +324,28 @@ public class WeakHashMap extends AbstractMap implements Map * number. <br> * * It would be cleaner to have a WeakReference as field, instead of - * extending it, but if a weak reference get cleared, we only get + * extending it, but if a weak reference gets cleared, we only get * the weak reference (by queue.poll) and wouldn't know where to * look for this reference in the hashtable, to remove that entry. * - * @author Jochen Hoenicke + * @author Jochen Hoenicke */ private static class WeakBucket extends WeakReference { /** * The value of this entry. The key is stored in the weak - * reference that we extend. + * reference that we extend. */ Object value; /** * The next bucket describing another entry that uses the same - * slot. + * slot. */ WeakBucket next; /** - * The slot of this entry. This should be + * The slot of this entry. This should be * <pre> * Math.abs(key.hashCode() % buckets.length) * </pre> @@ -329,12 +360,13 @@ public class WeakHashMap extends AbstractMap implements Map * Creates a new bucket for the given key/value pair and the specified * slot. * @param key the key + * @param queue the queue the weak reference belongs to * @param value the value * @param slot the slot. This must match the slot where this bucket - * will be enqueued. + * will be enqueued. */ public WeakBucket(Object key, ReferenceQueue queue, Object value, - int slot) + int slot) { super(key, queue); this.value = value; @@ -342,11 +374,11 @@ public class WeakHashMap extends AbstractMap implements Map } /** - * This class gives the <code>Entry</code> representation of the + * This class gives the <code>Entry</code> representation of the * current bucket. It also keeps a strong reference to the * key; bad things may happen otherwise. */ - class Entry implements Map.Entry + class WeakEntry implements Map.Entry { /** * The strong ref to the key. @@ -355,93 +387,105 @@ public class WeakHashMap extends AbstractMap implements Map /** * Creates a new entry for the key. + * @param key the key */ - public Entry(Object key) + public WeakEntry(Object key) { - this.key = key; + this.key = key; } /** * Returns the underlying bucket. + * @return the owning bucket */ public WeakBucket getBucket() { - return WeakBucket.this; + return WeakBucket.this; } /** * Returns the key. + * @return the key */ public Object getKey() { - return key == NULL_KEY ? null : key; + return key == NULL_KEY ? null : key; } /** * Returns the value. + * @return the value */ public Object getValue() { - return value; + return value; } /** - * This changes the value. This change takes place in + * This changes the value. This change takes place in * the underlying hash map. + * @param newVal the new value + * @return the old value */ public Object setValue(Object newVal) { - Object oldVal = value; - value = newVal; - return oldVal; + Object oldVal = value; + value = newVal; + return oldVal; } /** * The hashCode as specified in the Entry interface. + * @return the hash code */ public int hashCode() { - return (key == NULL_KEY ? 0 : key.hashCode()) - ^ (value == null ? 0 : value.hashCode()); + return key.hashCode() ^ WeakHashMap.hashCode(value); } /** * The equals method as specified in the Entry interface. + * @param o the object to compare to + * @return true iff o represents the same key/value pair */ public boolean equals(Object o) { - if (o instanceof Map.Entry) - { - Map.Entry e = (Map.Entry) o; - return (key == NULL_KEY - ? e.getKey() == null : key.equals(e.getKey())) - && (value == null - ? e.getValue() == null : value.equals(e.getValue())); - } - return false; + if (o instanceof Map.Entry) + { + Map.Entry e = (Map.Entry) o; + return key.equals(e.getKey()) + && WeakHashMap.equals(value, e.getValue()); + } + return false; + } + + public String toString() + { + return key + "=" + value; } } /** * This returns the entry stored in this bucket, or null, if the * bucket got cleared in the mean time. + * @return the Entry for this bucket, if it exists */ - Entry getEntry() + WeakEntry getEntry() { - final Object key = this.get(); + final Object key = get(); if (key == null) - return null; - return new Entry(key); + return null; + return new WeakEntry(key); } } /** * The entry set returned by <code>entrySet()</code>. */ - private WeakEntrySet theEntrySet; + private final WeakEntrySet theEntrySet; /** - * The hash buckets. This are linked lists. + * The hash buckets. These are linked lists. */ private WeakBucket[] buckets; @@ -457,7 +501,8 @@ public class WeakHashMap extends AbstractMap implements Map /** * Creates a new weak hash map with default load factor and the given * capacity. - * @param initialCapacity the initial capacity + * @param initialCapacity the initial capacity + * @throws IllegalArgumentException if initialCapacity is negative */ public WeakHashMap(int initialCapacity) { @@ -466,13 +511,16 @@ public class WeakHashMap extends AbstractMap implements Map /** * Creates a new weak hash map with the given initial capacity and - * load factor. + * load factor. * @param initialCapacity the initial capacity. * @param loadFactor the load factor (see class description of HashMap). + * @throws IllegalArgumentException if initialCapacity is negative, or + * loadFactor is non-positive */ public WeakHashMap(int initialCapacity, float loadFactor) { - if (initialCapacity < 0 || loadFactor <= 0 || loadFactor > 1) + // Check loadFactor for NaN as well. + if (initialCapacity < 0 || ! (loadFactor > 0)) throw new IllegalArgumentException(); this.loadFactor = loadFactor; threshold = (int) (initialCapacity * loadFactor); @@ -481,8 +529,24 @@ public class WeakHashMap extends AbstractMap implements Map buckets = new WeakBucket[initialCapacity]; } - /** - * simply hashes a non-null Object to its array index + /** + * Construct a new WeakHashMap with the same mappings as the given map. + * The WeakHashMap has a default load factor of 0.75. + * + * @param m the map to copy + * @throws NullPointerException if m is null + * @since 1.3 + */ + public WeakHashMap(Map m) + { + this(m.size(), DEFAULT_LOAD_FACTOR); + putAll(m); + } + + /** + * Simply hashes a non-null Object to its array index. + * @param key the key to hash + * @return its slot number */ private int hash(Object key) { @@ -498,68 +562,68 @@ public class WeakHashMap extends AbstractMap implements Map * Currently the iterator maintains a strong reference to the key, so * that is no problem. */ - private void cleanQueue() + // Package visible for use by nested classes. + void cleanQueue() { Object bucket = queue.poll(); while (bucket != null) { - internalRemove((WeakBucket) bucket); - bucket = queue.poll(); + internalRemove((WeakBucket) bucket); + bucket = queue.poll(); } } /** * Rehashes this hashtable. This will be called by the - * <code>add()</code> method if the size grows beyond the threshold. + * <code>add()</code> method if the size grows beyond the threshold. * It will grow the bucket size at least by factor two and allocates * new buckets. */ private void rehash() { WeakBucket[] oldBuckets = buckets; - int newsize = buckets.length * 2 + 1; // XXX should be prime. + int newsize = buckets.length * 2 + 1; // XXX should be prime. threshold = (int) (newsize * loadFactor); buckets = new WeakBucket[newsize]; - /* Now we have to insert the buckets again. - */ + // Now we have to insert the buckets again. for (int i = 0; i < oldBuckets.length; i++) { - WeakBucket bucket = oldBuckets[i]; - WeakBucket nextBucket; - while (bucket != null) - { - nextBucket = bucket.next; - - Object key = bucket.get(); - if (key == null) - { - /* This bucket should be removed; it is probably - * already on the reference queue. We don't insert it - * at all, and mark it as cleared. */ - bucket.slot = -1; - size--; - } - else - { - /* add this bucket to its new slot */ - int slot = hash(key); - bucket.slot = slot; - bucket.next = buckets[slot]; - buckets[slot] = bucket; - } - bucket = nextBucket; - } + WeakBucket bucket = oldBuckets[i]; + WeakBucket nextBucket; + while (bucket != null) + { + nextBucket = bucket.next; + + Object key = bucket.get(); + if (key == null) + { + // This bucket should be removed; it is probably + // already on the reference queue. We don't insert it + // at all, and mark it as cleared. + bucket.slot = -1; + size--; + } + else + { + // Add this bucket to its new slot. + int slot = hash(key); + bucket.slot = slot; + bucket.next = buckets[slot]; + buckets[slot] = bucket; + } + bucket = nextBucket; + } } } /** * Finds the entry corresponding to key. Since it returns an Entry * it will also prevent the key from being removed under us. - * @param key the key. It may be null. - * @return The WeakBucket.Entry or null, if the key wasn't found. + * @param key the key, may be null + * @return The WeakBucket.WeakEntry or null, if the key wasn't found. */ - private WeakBucket.Entry internalGet(Object key) + private WeakBucket.WeakEntry internalGet(Object key) { if (key == null) key = NULL_KEY; @@ -567,11 +631,11 @@ public class WeakHashMap extends AbstractMap implements Map WeakBucket bucket = buckets[slot]; while (bucket != null) { - WeakBucket.Entry entry = bucket.getEntry(); - if (entry != null && key.equals(entry.key)) - return entry; + WeakBucket.WeakEntry entry = bucket.getEntry(); + if (entry != null && key.equals(entry.key)) + return entry; - bucket = bucket.next; + bucket = bucket.next; } return null; } @@ -595,41 +659,40 @@ public class WeakHashMap extends AbstractMap implements Map /** * Removes a bucket from this hash map, if it wasn't removed before * (e.g. one time through rehashing and one time through reference queue) - * @param bucket the bucket to remove. + * @param bucket the bucket to remove. */ private void internalRemove(WeakBucket bucket) { int slot = bucket.slot; if (slot == -1) - /* this bucket was already removed. */ + // This bucket was already removed. return; - /* mark the bucket as removed. This is necessary, since the - * bucket may be enqueued later by the garbage collection and - * internalRemove, will be called a second time. - */ + // Mark the bucket as removed. This is necessary, since the + // bucket may be enqueued later by the garbage collection, and + // internalRemove will be called a second time. bucket.slot = -1; if (buckets[slot] == bucket) buckets[slot] = bucket.next; else { - WeakBucket prev = buckets[slot]; - /* This may throw a NullPointerException. It shouldn't but if - * a race condition occurred (two threads removing the same - * bucket at the same time) it may happen. <br> - * But with race condition many much worse things may happen - * anyway. - */ - while (prev.next != bucket) - prev = prev.next; - prev.next = bucket.next; + WeakBucket prev = buckets[slot]; + /* This may throw a NullPointerException. It shouldn't but if + * a race condition occurred (two threads removing the same + * bucket at the same time) it may happen. <br> + * But with race condition many much worse things may happen + * anyway. + */ + while (prev.next != bucket) + prev = prev.next; + prev.next = bucket.next; } size--; } /** * Returns the size of this hash map. Note that the size() may shrink - * spontanously, if the some of the keys were only weakly reachable. + * spontaneously, if the some of the keys were only weakly reachable. * @return the number of entries in this hash map. */ public int size() @@ -651,9 +714,10 @@ public class WeakHashMap extends AbstractMap implements Map /** * Tells if the map contains the given key. Note that the result - * may change spontanously, if all the key was only weakly - * reachable. - * @return true, iff the map contains an entry for the given key. + * may change spontanously, if the key was only weakly + * reachable. + * @param key the key to look for + * @return true, iff the map contains an entry for the given key. */ public boolean containsKey(Object key) { @@ -662,38 +726,38 @@ public class WeakHashMap extends AbstractMap implements Map } /** - * Gets the value the key will be mapped to. + * Gets the value the key is mapped to. * @return the value the key was mapped to. It returns null if - * the key wasn't in this map, or if the mapped value was explicitly - * set to null. + * the key wasn't in this map, or if the mapped value was + * explicitly set to null. */ public Object get(Object key) { cleanQueue(); - WeakBucket.Entry entry = internalGet(key); + WeakBucket.WeakEntry entry = internalGet(key); return entry == null ? null : entry.getValue(); } /** * Adds a new key/value mapping to this map. - * @param key the key. This may be null. - * @param value the value. This may be null. + * @param key the key, may be null + * @param value the value, may be null * @return the value the key was mapped to previously. It returns - * null if the key wasn't in this map, or if the mapped value was - * explicitly set to null. + * null if the key wasn't in this map, or if the mapped value + * was explicitly set to null. */ public Object put(Object key, Object value) { cleanQueue(); - WeakBucket.Entry entry = internalGet(key); + WeakBucket.WeakEntry entry = internalGet(key); if (entry != null) return entry.setValue(value); + modCount++; if (size >= threshold) rehash(); internalAdd(key, value); - modCount++; return null; } @@ -701,18 +765,18 @@ public class WeakHashMap extends AbstractMap implements Map * Removes the key and the corresponding value from this map. * @param key the key. This may be null. * @return the value the key was mapped to previously. It returns - * null if the key wasn't in this map, or if the mapped value was - * explicitly set to null. */ + * null if the key wasn't in this map, or if the mapped value was + * explicitly set to null. + */ public Object remove(Object key) { cleanQueue(); - WeakBucket.Entry entry = internalGet(key); + WeakBucket.WeakEntry entry = internalGet(key); if (entry == null) - { - return null; - } - internalRemove(entry.getBucket()); + return null; + modCount++; + internalRemove(entry.getBucket()); return entry.getValue(); } @@ -721,11 +785,71 @@ public class WeakHashMap extends AbstractMap implements Map * set will not have strong references to the keys, so they can be * silently removed. The returned set has therefore the same * strange behaviour (shrinking size(), disappearing entries) as - * this weak hash map. - * @return a set representation of the entries. */ + * this weak hash map. + * @return a set representation of the entries. + */ public Set entrySet() { cleanQueue(); return theEntrySet; } + + /** + * Clears all entries from this map. + */ + public void clear() + { + super.clear(); + } + + /** + * Returns true if the map contains at least one key which points to + * the specified object as a value. Note that the result + * may change spontanously, if its key was only weakly reachable. + * @param value the value to search for + * @return true if it is found in the set. + */ + public boolean containsValue(Object value) + { + cleanQueue(); + return super.containsValue(value); + } + + /** + * Returns a set representation of the keys in this map. This + * set will not have strong references to the keys, so they can be + * silently removed. The returned set has therefore the same + * strange behaviour (shrinking size(), disappearing entries) as + * this weak hash map. + * @return a set representation of the keys. + */ + public Set keySet() + { + cleanQueue(); + return super.keySet(); + } + + /** + * Puts all of the mappings from the given map into this one. If the + * key already exists in this map, its value is replaced. + * @param m the map to copy in + */ + public void putAll(Map m) + { + super.putAll(m); + } + + /** + * Returns a collection representation of the values in this map. This + * collection will not have strong references to the keys, so mappings + * can be silently removed. The returned collection has therefore the same + * strange behaviour (shrinking size(), disappearing entries) as + * this weak hash map. + * @return a collection representation of the values. + */ + public Collection values() + { + cleanQueue(); + return super.values(); + } } |