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