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