• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.util;
18 
19 import android.annotation.TestApi;
20 import android.annotation.UnsupportedAppUsage;
21 
22 import libcore.util.EmptyArray;
23 
24 import java.lang.reflect.Array;
25 import java.util.Collection;
26 import java.util.Iterator;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.function.Predicate;
30 
31 /**
32  * ArraySet is a generic set data structure that is designed to be more memory efficient than a
33  * traditional {@link java.util.HashSet}.  The design is very similar to
34  * {@link ArrayMap}, with all of the caveats described there.  This implementation is
35  * separate from ArrayMap, however, so the Object array contains only one item for each
36  * entry in the set (instead of a pair for a mapping).
37  *
38  * <p>Note that this implementation is not intended to be appropriate for data structures
39  * that may contain large numbers of items.  It is generally slower than a traditional
40  * HashSet, since lookups require a binary search and adds and removes require inserting
41  * and deleting entries in the array.  For containers holding up to hundreds of items,
42  * the performance difference is not significant, less than 50%.</p>
43  *
44  * <p>Because this container is intended to better balance memory use, unlike most other
45  * standard Java containers it will shrink its array as items are removed from it.  Currently
46  * you have no control over this shrinking -- if you set a capacity and then remove an
47  * item, it may reduce the capacity to better match the current size.  In the future an
48  * explicit call to set the capacity should turn off this aggressive shrinking behavior.</p>
49  */
50 public final class ArraySet<E> implements Collection<E>, Set<E> {
51     private static final boolean DEBUG = false;
52     private static final String TAG = "ArraySet";
53 
54     /**
55      * The minimum amount by which the capacity of a ArraySet will increase.
56      * This is tuned to be relatively space-efficient.
57      */
58     private static final int BASE_SIZE = 4;
59 
60     /**
61      * Maximum number of entries to have in array caches.
62      */
63     private static final int CACHE_SIZE = 10;
64 
65     /**
66      * Caches of small array objects to avoid spamming garbage.  The cache
67      * Object[] variable is a pointer to a linked list of array objects.
68      * The first entry in the array is a pointer to the next array in the
69      * list; the second entry is a pointer to the int[] hash code array for it.
70      */
71     static Object[] sBaseCache;
72     static int sBaseCacheSize;
73     static Object[] sTwiceBaseCache;
74     static int sTwiceBaseCacheSize;
75 
76     final boolean mIdentityHashCode;
77     @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use public API.
78     int[] mHashes;
79     @UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use public API.
80     Object[] mArray;
81     @UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
82     int mSize;
83     MapCollections<E, E> mCollections;
84 
85     @UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use indexOfKey(Object).
indexOf(Object key, int hash)86     private int indexOf(Object key, int hash) {
87         final int N = mSize;
88 
89         // Important fast case: if nothing is in here, nothing to look for.
90         if (N == 0) {
91             return ~0;
92         }
93 
94         int index = ContainerHelpers.binarySearch(mHashes, N, hash);
95 
96         // If the hash code wasn't found, then we have no entry for this key.
97         if (index < 0) {
98             return index;
99         }
100 
101         // If the key at the returned index matches, that's what we want.
102         if (key.equals(mArray[index])) {
103             return index;
104         }
105 
106         // Search for a matching key after the index.
107         int end;
108         for (end = index + 1; end < N && mHashes[end] == hash; end++) {
109             if (key.equals(mArray[end])) return end;
110         }
111 
112         // Search for a matching key before the index.
113         for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
114             if (key.equals(mArray[i])) return i;
115         }
116 
117         // Key not found -- return negative value indicating where a
118         // new entry for this key should go.  We use the end of the
119         // hash chain to reduce the number of array entries that will
120         // need to be copied when inserting.
121         return ~end;
122     }
123 
124     @UnsupportedAppUsage(maxTargetSdk = 28) // Use indexOf(null)
indexOfNull()125     private int indexOfNull() {
126         final int N = mSize;
127 
128         // Important fast case: if nothing is in here, nothing to look for.
129         if (N == 0) {
130             return ~0;
131         }
132 
133         int index = ContainerHelpers.binarySearch(mHashes, N, 0);
134 
135         // If the hash code wasn't found, then we have no entry for this key.
136         if (index < 0) {
137             return index;
138         }
139 
140         // If the key at the returned index matches, that's what we want.
141         if (null == mArray[index]) {
142             return index;
143         }
144 
145         // Search for a matching key after the index.
146         int end;
147         for (end = index + 1; end < N && mHashes[end] == 0; end++) {
148             if (null == mArray[end]) return end;
149         }
150 
151         // Search for a matching key before the index.
152         for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) {
153             if (null == mArray[i]) return i;
154         }
155 
156         // Key not found -- return negative value indicating where a
157         // new entry for this key should go.  We use the end of the
158         // hash chain to reduce the number of array entries that will
159         // need to be copied when inserting.
160         return ~end;
161     }
162 
163     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
allocArrays(final int size)164     private void allocArrays(final int size) {
165         if (size == (BASE_SIZE * 2)) {
166             synchronized (ArraySet.class) {
167                 if (sTwiceBaseCache != null) {
168                     final Object[] array = sTwiceBaseCache;
169                     try {
170                         mArray = array;
171                         sTwiceBaseCache = (Object[]) array[0];
172                         mHashes = (int[]) array[1];
173                         array[0] = array[1] = null;
174                         sTwiceBaseCacheSize--;
175                         if (DEBUG) {
176                             Log.d(TAG, "Retrieving 2x cache " + mHashes + " now have "
177                                     + sTwiceBaseCacheSize + " entries");
178                     }
179                     return;
180                     } catch (ClassCastException e) {
181                     }
182                     // Whoops!  Someone trampled the array (probably due to not protecting
183                     // their access with a lock).  Our cache is corrupt; report and give up.
184                     Slog.wtf(TAG, "Found corrupt ArraySet cache: [0]=" + array[0]
185                             + " [1]=" + array[1]);
186                     sTwiceBaseCache = null;
187                     sTwiceBaseCacheSize = 0;
188                 }
189             }
190         } else if (size == BASE_SIZE) {
191             synchronized (ArraySet.class) {
192                 if (sBaseCache != null) {
193                     final Object[] array = sBaseCache;
194                     try {
195                         mArray = array;
196                         sBaseCache = (Object[]) array[0];
197                         mHashes = (int[]) array[1];
198                         array[0] = array[1] = null;
199                         sBaseCacheSize--;
200                         if (DEBUG) {
201                             Log.d(TAG, "Retrieving 1x cache " + mHashes + " now have " + sBaseCacheSize
202                                     + " entries");
203                         }
204                         return;
205                     } catch (ClassCastException e) {
206                     }
207                     // Whoops!  Someone trampled the array (probably due to not protecting
208                     // their access with a lock).  Our cache is corrupt; report and give up.
209                     Slog.wtf(TAG, "Found corrupt ArraySet cache: [0]=" + array[0]
210                             + " [1]=" + array[1]);
211                     sBaseCache = null;
212                     sBaseCacheSize = 0;
213                 }
214             }
215         }
216 
217         mHashes = new int[size];
218         mArray = new Object[size];
219     }
220 
221     @UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
freeArrays(final int[] hashes, final Object[] array, final int size)222     private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
223         if (hashes.length == (BASE_SIZE * 2)) {
224             synchronized (ArraySet.class) {
225                 if (sTwiceBaseCacheSize < CACHE_SIZE) {
226                     array[0] = sTwiceBaseCache;
227                     array[1] = hashes;
228                     for (int i = size - 1; i >= 2; i--) {
229                         array[i] = null;
230                     }
231                     sTwiceBaseCache = array;
232                     sTwiceBaseCacheSize++;
233                     if (DEBUG) {
234                         Log.d(TAG, "Storing 2x cache " + array + " now have " + sTwiceBaseCacheSize
235                                 + " entries");
236                     }
237                 }
238             }
239         } else if (hashes.length == BASE_SIZE) {
240             synchronized (ArraySet.class) {
241                 if (sBaseCacheSize < CACHE_SIZE) {
242                     array[0] = sBaseCache;
243                     array[1] = hashes;
244                     for (int i = size - 1; i >= 2; i--) {
245                         array[i] = null;
246                     }
247                     sBaseCache = array;
248                     sBaseCacheSize++;
249                     if (DEBUG) {
250                         Log.d(TAG, "Storing 1x cache " + array + " now have "
251                                 + sBaseCacheSize + " entries");
252                     }
253                 }
254             }
255         }
256     }
257 
258     /**
259      * Create a new empty ArraySet.  The default capacity of an array map is 0, and
260      * will grow once items are added to it.
261      */
ArraySet()262     public ArraySet() {
263         this(0, false);
264     }
265 
266     /**
267      * Create a new ArraySet with a given initial capacity.
268      */
ArraySet(int capacity)269     public ArraySet(int capacity) {
270         this(capacity, false);
271     }
272 
273     /** {@hide} */
ArraySet(int capacity, boolean identityHashCode)274     public ArraySet(int capacity, boolean identityHashCode) {
275         mIdentityHashCode = identityHashCode;
276         if (capacity == 0) {
277             mHashes = EmptyArray.INT;
278             mArray = EmptyArray.OBJECT;
279         } else {
280             allocArrays(capacity);
281         }
282         mSize = 0;
283     }
284 
285     /**
286      * Create a new ArraySet with the mappings from the given ArraySet.
287      */
ArraySet(ArraySet<E> set)288     public ArraySet(ArraySet<E> set) {
289         this();
290         if (set != null) {
291             addAll(set);
292         }
293     }
294 
295     /**
296      * Create a new ArraySet with items from the given collection.
297      */
ArraySet(Collection<? extends E> set)298     public ArraySet(Collection<? extends E> set) {
299         this();
300         if (set != null) {
301             addAll(set);
302         }
303     }
304 
305     /**
306      * Make the array map empty.  All storage is released.
307      */
308     @Override
clear()309     public void clear() {
310         if (mSize != 0) {
311             freeArrays(mHashes, mArray, mSize);
312             mHashes = EmptyArray.INT;
313             mArray = EmptyArray.OBJECT;
314             mSize = 0;
315         }
316     }
317 
318     /**
319      * Ensure the array map can hold at least <var>minimumCapacity</var>
320      * items.
321      */
ensureCapacity(int minimumCapacity)322     public void ensureCapacity(int minimumCapacity) {
323         if (mHashes.length < minimumCapacity) {
324             final int[] ohashes = mHashes;
325             final Object[] oarray = mArray;
326             allocArrays(minimumCapacity);
327             if (mSize > 0) {
328                 System.arraycopy(ohashes, 0, mHashes, 0, mSize);
329                 System.arraycopy(oarray, 0, mArray, 0, mSize);
330             }
331             freeArrays(ohashes, oarray, mSize);
332         }
333     }
334 
335     /**
336      * Check whether a value exists in the set.
337      *
338      * @param key The value to search for.
339      * @return Returns true if the value exists, else false.
340      */
341     @Override
contains(Object key)342     public boolean contains(Object key) {
343         return indexOf(key) >= 0;
344     }
345 
346     /**
347      * Returns the index of a value in the set.
348      *
349      * @param key The value to search for.
350      * @return Returns the index of the value if it exists, else a negative integer.
351      */
indexOf(Object key)352     public int indexOf(Object key) {
353         return key == null ? indexOfNull()
354                 : indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
355     }
356 
357     /**
358      * Return the value at the given index in the array.
359      *
360      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
361      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
362      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
363      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
364      *
365      * @param index The desired index, must be between 0 and {@link #size()}-1.
366      * @return Returns the value stored at the given index.
367      */
valueAt(int index)368     public E valueAt(int index) {
369         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
370             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
371             // Check if exception should be thrown outside of the critical path.
372             throw new ArrayIndexOutOfBoundsException(index);
373         }
374         return valueAtUnchecked(index);
375     }
376 
377     /**
378      * Returns the value at the given index in the array without checking that the index is within
379      * bounds. This allows testing values at the end of the internal array, outside of the
380      * [0, mSize) bounds.
381      *
382      * @hide
383      */
384     @TestApi
valueAtUnchecked(int index)385     public E valueAtUnchecked(int index) {
386         return (E) mArray[index];
387     }
388 
389     /**
390      * Return true if the array map contains no items.
391      */
392     @Override
isEmpty()393     public boolean isEmpty() {
394         return mSize <= 0;
395     }
396 
397     /**
398      * Adds the specified object to this set. The set is not modified if it
399      * already contains the object.
400      *
401      * @param value the object to add.
402      * @return {@code true} if this set is modified, {@code false} otherwise.
403      * @throws ClassCastException
404      *             when the class of the object is inappropriate for this set.
405      */
406     @Override
add(E value)407     public boolean add(E value) {
408         final int hash;
409         int index;
410         if (value == null) {
411             hash = 0;
412             index = indexOfNull();
413         } else {
414             hash = mIdentityHashCode ? System.identityHashCode(value) : value.hashCode();
415             index = indexOf(value, hash);
416         }
417         if (index >= 0) {
418             return false;
419         }
420 
421         index = ~index;
422         if (mSize >= mHashes.length) {
423             final int n = mSize >= (BASE_SIZE * 2) ? (mSize + (mSize >> 1))
424                     : (mSize >= BASE_SIZE ? (BASE_SIZE * 2) : BASE_SIZE);
425 
426             if (DEBUG) Log.d(TAG, "add: grow from " + mHashes.length + " to " + n);
427 
428             final int[] ohashes = mHashes;
429             final Object[] oarray = mArray;
430             allocArrays(n);
431 
432             if (mHashes.length > 0) {
433                 if (DEBUG) Log.d(TAG, "add: copy 0-" + mSize + " to 0");
434                 System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
435                 System.arraycopy(oarray, 0, mArray, 0, oarray.length);
436             }
437 
438             freeArrays(ohashes, oarray, mSize);
439         }
440 
441         if (index < mSize) {
442             if (DEBUG) {
443                 Log.d(TAG, "add: move " + index + "-" + (mSize - index) + " to " + (index + 1));
444             }
445             System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);
446             System.arraycopy(mArray, index, mArray, index + 1, mSize - index);
447         }
448 
449         mHashes[index] = hash;
450         mArray[index] = value;
451         mSize++;
452         return true;
453     }
454 
455     /**
456      * Special fast path for appending items to the end of the array without validation.
457      * The array must already be large enough to contain the item.
458      * @hide
459      */
append(E value)460     public void append(E value) {
461         final int index = mSize;
462         final int hash = value == null ? 0
463                 : (mIdentityHashCode ? System.identityHashCode(value) : value.hashCode());
464         if (index >= mHashes.length) {
465             throw new IllegalStateException("Array is full");
466         }
467         if (index > 0 && mHashes[index - 1] > hash) {
468             // Cannot optimize since it would break the sorted order - fallback to add()
469             if (DEBUG) {
470                 RuntimeException e = new RuntimeException("here");
471                 e.fillInStackTrace();
472                 Log.w(TAG, "New hash " + hash
473                         + " is before end of array hash " + mHashes[index - 1]
474                         + " at index " + index, e);
475             }
476             add(value);
477             return;
478         }
479         mSize = index + 1;
480         mHashes[index] = hash;
481         mArray[index] = value;
482     }
483 
484     /**
485      * Perform a {@link #add(Object)} of all values in <var>array</var>
486      * @param array The array whose contents are to be retrieved.
487      */
addAll(ArraySet<? extends E> array)488     public void addAll(ArraySet<? extends E> array) {
489         final int N = array.mSize;
490         ensureCapacity(mSize + N);
491         if (mSize == 0) {
492             if (N > 0) {
493                 System.arraycopy(array.mHashes, 0, mHashes, 0, N);
494                 System.arraycopy(array.mArray, 0, mArray, 0, N);
495                 mSize = N;
496             }
497         } else {
498             for (int i = 0; i < N; i++) {
499                 add(array.valueAt(i));
500             }
501         }
502     }
503 
504     /**
505      * Removes the specified object from this set.
506      *
507      * @param object the object to remove.
508      * @return {@code true} if this set was modified, {@code false} otherwise.
509      */
510     @Override
remove(Object object)511     public boolean remove(Object object) {
512         final int index = indexOf(object);
513         if (index >= 0) {
514             removeAt(index);
515             return true;
516         }
517         return false;
518     }
519 
520     /** Returns true if the array size should be decreased. */
shouldShrink()521     private boolean shouldShrink() {
522         return mHashes.length > (BASE_SIZE * 2) && mSize < mHashes.length / 3;
523     }
524 
525     /**
526      * Returns the new size the array should have. Is only valid if {@link #shouldShrink} returns
527      * true.
528      */
getNewShrunkenSize()529     private int getNewShrunkenSize() {
530         // We don't allow it to shrink smaller than (BASE_SIZE*2) to avoid flapping between that
531         // and BASE_SIZE.
532         return mSize > (BASE_SIZE * 2) ? (mSize + (mSize >> 1)) : (BASE_SIZE * 2);
533     }
534 
535     /**
536      * Remove the key/value mapping at the given index.
537      *
538      * <p>For indices outside of the range <code>0...size()-1</code>, the behavior is undefined for
539      * apps targeting {@link android.os.Build.VERSION_CODES#P} and earlier, and an
540      * {@link ArrayIndexOutOfBoundsException} is thrown for apps targeting
541      * {@link android.os.Build.VERSION_CODES#Q} and later.</p>
542      *
543      * @param index The desired index, must be between 0 and {@link #size()}-1.
544      * @return Returns the value that was stored at this index.
545      */
removeAt(int index)546     public E removeAt(int index) {
547         if (index >= mSize && UtilConfig.sThrowExceptionForUpperArrayOutOfBounds) {
548             // The array might be slightly bigger than mSize, in which case, indexing won't fail.
549             // Check if exception should be thrown outside of the critical path.
550             throw new ArrayIndexOutOfBoundsException(index);
551         }
552         final Object old = mArray[index];
553         if (mSize <= 1) {
554             // Now empty.
555             if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
556             clear();
557         } else {
558             if (shouldShrink()) {
559                 // Shrunk enough to reduce size of arrays.
560                 final int n = getNewShrunkenSize();
561 
562                 if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n);
563 
564                 final int[] ohashes = mHashes;
565                 final Object[] oarray = mArray;
566                 allocArrays(n);
567 
568                 mSize--;
569                 if (index > 0) {
570                     if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
571                     System.arraycopy(ohashes, 0, mHashes, 0, index);
572                     System.arraycopy(oarray, 0, mArray, 0, index);
573                 }
574                 if (index < mSize) {
575                     if (DEBUG) {
576                         Log.d(TAG, "remove: copy from " + (index + 1) + "-" + mSize
577                                 + " to " + index);
578                     }
579                     System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index);
580                     System.arraycopy(oarray, index + 1, mArray, index, mSize - index);
581                 }
582             } else {
583                 mSize--;
584                 if (index < mSize) {
585                     if (DEBUG) {
586                         Log.d(TAG, "remove: move " + (index + 1) + "-" + mSize + " to " + index);
587                     }
588                     System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index);
589                     System.arraycopy(mArray, index + 1, mArray, index, mSize - index);
590                 }
591                 mArray[mSize] = null;
592             }
593         }
594         return (E) old;
595     }
596 
597     /**
598      * Perform a {@link #remove(Object)} of all values in <var>array</var>
599      * @param array The array whose contents are to be removed.
600      */
removeAll(ArraySet<? extends E> array)601     public boolean removeAll(ArraySet<? extends E> array) {
602         // TODO: If array is sufficiently large, a marking approach might be beneficial. In a first
603         //       pass, use the property that the sets are sorted by hash to make this linear passes
604         //       (except for hash collisions, which means worst case still n*m), then do one
605         //       collection pass into a new array. This avoids binary searches and excessive memcpy.
606         final int N = array.mSize;
607 
608         // Note: ArraySet does not make thread-safety guarantees. So instead of OR-ing together all
609         //       the single results, compare size before and after.
610         final int originalSize = mSize;
611         for (int i = 0; i < N; i++) {
612             remove(array.valueAt(i));
613         }
614         return originalSize != mSize;
615     }
616 
617     /**
618      * Removes all values that satisfy the predicate. This implementation avoids using the
619      * {@link #iterator()}.
620      *
621      * @param filter A predicate which returns true for elements to be removed
622      */
623     @Override
removeIf(Predicate<? super E> filter)624     public boolean removeIf(Predicate<? super E> filter) {
625         if (mSize == 0) {
626             return false;
627         }
628 
629         // Intentionally not using removeAt() to avoid unnecessary intermediate resizing.
630 
631         int replaceIndex = 0;
632         int numRemoved = 0;
633         for (int i = 0; i < mSize; ++i) {
634             if (filter.test((E) mArray[i])) {
635                 numRemoved++;
636             } else {
637                 if (replaceIndex != i) {
638                     mArray[replaceIndex] = mArray[i];
639                     mHashes[replaceIndex] = mHashes[i];
640                 }
641                 replaceIndex++;
642             }
643         }
644 
645         if (numRemoved == 0) {
646             return false;
647         } else if (numRemoved == mSize) {
648             clear();
649             return true;
650         }
651 
652         mSize -= numRemoved;
653         if (shouldShrink()) {
654             // Shrunk enough to reduce size of arrays.
655             final int n = getNewShrunkenSize();
656             final int[] ohashes = mHashes;
657             final Object[] oarray = mArray;
658             allocArrays(n);
659 
660             System.arraycopy(ohashes, 0, mHashes, 0, mSize);
661             System.arraycopy(oarray, 0, mArray, 0, mSize);
662         } else {
663             // Null out values at the end of the array. Not doing it in the loop above to avoid
664             // writing twice to the same index or writing unnecessarily if the array would have been
665             // discarded anyway.
666             for (int i = mSize; i < mArray.length; ++i) {
667                 mArray[i] = null;
668             }
669         }
670         return true;
671     }
672 
673     /**
674      * Return the number of items in this array map.
675      */
676     @Override
size()677     public int size() {
678         return mSize;
679     }
680 
681     @Override
toArray()682     public Object[] toArray() {
683         Object[] result = new Object[mSize];
684         System.arraycopy(mArray, 0, result, 0, mSize);
685         return result;
686     }
687 
688     @Override
toArray(T[] array)689     public <T> T[] toArray(T[] array) {
690         if (array.length < mSize) {
691             @SuppressWarnings("unchecked") T[] newArray =
692                     (T[]) Array.newInstance(array.getClass().getComponentType(), mSize);
693             array = newArray;
694         }
695         System.arraycopy(mArray, 0, array, 0, mSize);
696         if (array.length > mSize) {
697             array[mSize] = null;
698         }
699         return array;
700     }
701 
702     /**
703      * {@inheritDoc}
704      *
705      * <p>This implementation returns false if the object is not a set, or
706      * if the sets have different sizes.  Otherwise, for each value in this
707      * set, it checks to make sure the value also exists in the other set.
708      * If any value doesn't exist, the method returns false; otherwise, it
709      * returns true.
710      */
711     @Override
equals(Object object)712     public boolean equals(Object object) {
713         if (this == object) {
714             return true;
715         }
716         if (object instanceof Set) {
717             Set<?> set = (Set<?>) object;
718             if (size() != set.size()) {
719                 return false;
720             }
721 
722             try {
723                 for (int i = 0; i < mSize; i++) {
724                     E mine = valueAt(i);
725                     if (!set.contains(mine)) {
726                         return false;
727                     }
728                 }
729             } catch (NullPointerException ignored) {
730                 return false;
731             } catch (ClassCastException ignored) {
732                 return false;
733             }
734             return true;
735         }
736         return false;
737     }
738 
739     /**
740      * {@inheritDoc}
741      */
742     @Override
hashCode()743     public int hashCode() {
744         final int[] hashes = mHashes;
745         int result = 0;
746         for (int i = 0, s = mSize; i < s; i++) {
747             result += hashes[i];
748         }
749         return result;
750     }
751 
752     /**
753      * {@inheritDoc}
754      *
755      * <p>This implementation composes a string by iterating over its values. If
756      * this set contains itself as a value, the string "(this Set)"
757      * will appear in its place.
758      */
759     @Override
toString()760     public String toString() {
761         if (isEmpty()) {
762             return "{}";
763         }
764 
765         StringBuilder buffer = new StringBuilder(mSize * 14);
766         buffer.append('{');
767         for (int i = 0; i < mSize; i++) {
768             if (i > 0) {
769                 buffer.append(", ");
770             }
771             Object value = valueAt(i);
772             if (value != this) {
773                 buffer.append(value);
774             } else {
775                 buffer.append("(this Set)");
776             }
777         }
778         buffer.append('}');
779         return buffer.toString();
780     }
781 
782     // ------------------------------------------------------------------------
783     // Interop with traditional Java containers.  Not as efficient as using
784     // specialized collection APIs.
785     // ------------------------------------------------------------------------
786 
getCollection()787     private MapCollections<E, E> getCollection() {
788         if (mCollections == null) {
789             mCollections = new MapCollections<E, E>() {
790                 @Override
791                 protected int colGetSize() {
792                     return mSize;
793                 }
794 
795                 @Override
796                 protected Object colGetEntry(int index, int offset) {
797                     return mArray[index];
798                 }
799 
800                 @Override
801                 protected int colIndexOfKey(Object key) {
802                     return indexOf(key);
803                 }
804 
805                 @Override
806                 protected int colIndexOfValue(Object value) {
807                     return indexOf(value);
808                 }
809 
810                 @Override
811                 protected Map<E, E> colGetMap() {
812                     throw new UnsupportedOperationException("not a map");
813                 }
814 
815                 @Override
816                 protected void colPut(E key, E value) {
817                     add(key);
818                 }
819 
820                 @Override
821                 protected E colSetValue(int index, E value) {
822                     throw new UnsupportedOperationException("not a map");
823                 }
824 
825                 @Override
826                 protected void colRemoveAt(int index) {
827                     removeAt(index);
828                 }
829 
830                 @Override
831                 protected void colClear() {
832                     clear();
833                 }
834             };
835         }
836         return mCollections;
837     }
838 
839     /**
840      * Return an {@link java.util.Iterator} over all values in the set.
841      *
842      * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
843      * requires generating a number of temporary objects and allocates additional state
844      * information associated with the container that will remain for the life of the container.</p>
845      */
846     @Override
iterator()847     public Iterator<E> iterator() {
848         return getCollection().getKeySet().iterator();
849     }
850 
851     /**
852      * Determine if the array set contains all of the values in the given collection.
853      * @param collection The collection whose contents are to be checked against.
854      * @return Returns true if this array set contains a value for every entry
855      * in <var>collection</var>, else returns false.
856      */
857     @Override
containsAll(Collection<?> collection)858     public boolean containsAll(Collection<?> collection) {
859         Iterator<?> it = collection.iterator();
860         while (it.hasNext()) {
861             if (!contains(it.next())) {
862                 return false;
863             }
864         }
865         return true;
866     }
867 
868     /**
869      * Perform an {@link #add(Object)} of all values in <var>collection</var>
870      * @param collection The collection whose contents are to be retrieved.
871      */
872     @Override
addAll(Collection<? extends E> collection)873     public boolean addAll(Collection<? extends E> collection) {
874         ensureCapacity(mSize + collection.size());
875         boolean added = false;
876         for (E value : collection) {
877             added |= add(value);
878         }
879         return added;
880     }
881 
882     /**
883      * Remove all values in the array set that exist in the given collection.
884      * @param collection The collection whose contents are to be used to remove values.
885      * @return Returns true if any values were removed from the array set, else false.
886      */
887     @Override
removeAll(Collection<?> collection)888     public boolean removeAll(Collection<?> collection) {
889         boolean removed = false;
890         for (Object value : collection) {
891             removed |= remove(value);
892         }
893         return removed;
894     }
895 
896     /**
897      * Remove all values in the array set that do <b>not</b> exist in the given collection.
898      * @param collection The collection whose contents are to be used to determine which
899      * values to keep.
900      * @return Returns true if any values were removed from the array set, else false.
901      */
902     @Override
retainAll(Collection<?> collection)903     public boolean retainAll(Collection<?> collection) {
904         boolean removed = false;
905         for (int i = mSize - 1; i >= 0; i--) {
906             if (!collection.contains(mArray[i])) {
907                 removeAt(i);
908                 removed = true;
909             }
910         }
911         return removed;
912     }
913 }
914