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