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
17package android.util;
18
19import java.util.Collection;
20import java.util.Map;
21import java.util.Set;
22
23/**
24 * ArrayMap is a generic key->value mapping data structure that is
25 * designed to be more memory efficient than a traditional {@link java.util.HashMap}.
26 * It keeps its mappings in an array data structure -- an integer array of hash
27 * codes for each item, and an Object array of the key/value pairs.  This allows it to
28 * avoid having to create an extra object for every entry put in to the map, and it
29 * also tries to control the growth of the size of these arrays more aggressively
30 * (since growing them only requires copying the entries in the array, not rebuilding
31 * a hash map).
32 *
33 * <p>Note that this implementation is not intended to be appropriate for data structures
34 * that may contain large numbers of items.  It is generally slower than a traditional
35 * HashMap, since lookups require a binary search and adds and removes require inserting
36 * and deleting entries in the array.  For containers holding up to hundreds of items,
37 * the performance difference is not significant, less than 50%.</p>
38 *
39 * <p>Because this container is intended to better balance memory use, unlike most other
40 * standard Java containers it will shrink its array as items are removed from it.  Currently
41 * you have no control over this shrinking -- if you set a capacity and then remove an
42 * item, it may reduce the capacity to better match the current size.  In the future an
43 * explicit call to set the capacity should turn off this aggressive shrinking behavior.</p>
44 */
45public final class ArrayMap<K, V> implements Map<K, V> {
46    private static final boolean DEBUG = false;
47    private static final String TAG = "ArrayMap";
48
49    /**
50     * The minimum amount by which the capacity of a ArrayMap will increase.
51     * This is tuned to be relatively space-efficient.
52     */
53    private static final int BASE_SIZE = 4;
54
55    /**
56     * Maximum number of entries to have in array caches.
57     */
58    private static final int CACHE_SIZE = 10;
59
60    /**
61     * @hide Special immutable empty ArrayMap.
62     */
63    public static final ArrayMap EMPTY = new ArrayMap(true);
64
65    /**
66     * Caches of small array objects to avoid spamming garbage.  The cache
67     * Object[] variable is a pointer to a linked list of array objects.
68     * The first entry in the array is a pointer to the next array in the
69     * list; the second entry is a pointer to the int[] hash code array for it.
70     */
71    static Object[] mBaseCache;
72    static int mBaseCacheSize;
73    static Object[] mTwiceBaseCache;
74    static int mTwiceBaseCacheSize;
75
76    /**
77     * Special hash array value that indicates the container is immutable.
78     */
79    static final int[] EMPTY_IMMUTABLE_INTS = new int[0];
80
81    int[] mHashes;
82    Object[] mArray;
83    int mSize;
84    MapCollections<K, V> mCollections;
85
86    int indexOf(Object key, int hash) {
87        final int N = mSize;
88
89        // Important fast case: if nothing is in here, nothing to look for.
90        if (N == 0) {
91            return ~0;
92        }
93
94        int index = ContainerHelpers.binarySearch(mHashes, N, hash);
95
96        // If the hash code wasn't found, then we have no entry for this key.
97        if (index < 0) {
98            return index;
99        }
100
101        // If the key at the returned index matches, that's what we want.
102        if (key.equals(mArray[index<<1])) {
103            return index;
104        }
105
106        // Search for a matching key after the index.
107        int end;
108        for (end = index + 1; end < N && mHashes[end] == hash; end++) {
109            if (key.equals(mArray[end << 1])) return end;
110        }
111
112        // Search for a matching key before the index.
113        for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
114            if (key.equals(mArray[i << 1])) return i;
115        }
116
117        // Key not found -- return negative value indicating where a
118        // new entry for this key should go.  We use the end of the
119        // hash chain to reduce the number of array entries that will
120        // need to be copied when inserting.
121        return ~end;
122    }
123
124    int indexOfNull() {
125        final int N = mSize;
126
127        // Important fast case: if nothing is in here, nothing to look for.
128        if (N == 0) {
129            return ~0;
130        }
131
132        int index = ContainerHelpers.binarySearch(mHashes, N, 0);
133
134        // If the hash code wasn't found, then we have no entry for this key.
135        if (index < 0) {
136            return index;
137        }
138
139        // If the key at the returned index matches, that's what we want.
140        if (null == mArray[index<<1]) {
141            return index;
142        }
143
144        // Search for a matching key after the index.
145        int end;
146        for (end = index + 1; end < N && mHashes[end] == 0; end++) {
147            if (null == mArray[end << 1]) return end;
148        }
149
150        // Search for a matching key before the index.
151        for (int i = index - 1; i >= 0 && mHashes[i] == 0; i--) {
152            if (null == mArray[i << 1]) return i;
153        }
154
155        // Key not found -- return negative value indicating where a
156        // new entry for this key should go.  We use the end of the
157        // hash chain to reduce the number of array entries that will
158        // need to be copied when inserting.
159        return ~end;
160    }
161
162    private void allocArrays(final int size) {
163        if (mHashes == EMPTY_IMMUTABLE_INTS) {
164            throw new UnsupportedOperationException("ArrayMap is immutable");
165        }
166        if (size == (BASE_SIZE*2)) {
167            synchronized (ArrayMap.class) {
168                if (mTwiceBaseCache != null) {
169                    final Object[] array = mTwiceBaseCache;
170                    mArray = array;
171                    mTwiceBaseCache = (Object[])array[0];
172                    mHashes = (int[])array[1];
173                    array[0] = array[1] = null;
174                    mTwiceBaseCacheSize--;
175                    if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes
176                            + " now have " + mTwiceBaseCacheSize + " entries");
177                    return;
178                }
179            }
180        } else if (size == BASE_SIZE) {
181            synchronized (ArrayMap.class) {
182                if (mBaseCache != null) {
183                    final Object[] array = mBaseCache;
184                    mArray = array;
185                    mBaseCache = (Object[])array[0];
186                    mHashes = (int[])array[1];
187                    array[0] = array[1] = null;
188                    mBaseCacheSize--;
189                    if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes
190                            + " now have " + mBaseCacheSize + " entries");
191                    return;
192                }
193            }
194        }
195
196        mHashes = new int[size];
197        mArray = new Object[size<<1];
198    }
199
200    private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
201        if (hashes.length == (BASE_SIZE*2)) {
202            synchronized (ArrayMap.class) {
203                if (mTwiceBaseCacheSize < CACHE_SIZE) {
204                    array[0] = mTwiceBaseCache;
205                    array[1] = hashes;
206                    for (int i=(size<<1)-1; i>=2; i--) {
207                        array[i] = null;
208                    }
209                    mTwiceBaseCache = array;
210                    mTwiceBaseCacheSize++;
211                    if (DEBUG) Log.d(TAG, "Storing 2x cache " + array
212                            + " now have " + mTwiceBaseCacheSize + " entries");
213                }
214            }
215        } else if (hashes.length == BASE_SIZE) {
216            synchronized (ArrayMap.class) {
217                if (mBaseCacheSize < CACHE_SIZE) {
218                    array[0] = mBaseCache;
219                    array[1] = hashes;
220                    for (int i=(size<<1)-1; i>=2; i--) {
221                        array[i] = null;
222                    }
223                    mBaseCache = array;
224                    mBaseCacheSize++;
225                    if (DEBUG) Log.d(TAG, "Storing 1x cache " + array
226                            + " now have " + mBaseCacheSize + " entries");
227                }
228            }
229        }
230    }
231
232    /**
233     * Create a new empty ArrayMap.  The default capacity of an array map is 0, and
234     * will grow once items are added to it.
235     */
236    public ArrayMap() {
237        mHashes = ContainerHelpers.EMPTY_INTS;
238        mArray = ContainerHelpers.EMPTY_OBJECTS;
239        mSize = 0;
240    }
241
242    /**
243     * Create a new ArrayMap with a given initial capacity.
244     */
245    public ArrayMap(int capacity) {
246        if (capacity == 0) {
247            mHashes = ContainerHelpers.EMPTY_INTS;
248            mArray = ContainerHelpers.EMPTY_OBJECTS;
249        } else {
250            allocArrays(capacity);
251        }
252        mSize = 0;
253    }
254
255    private ArrayMap(boolean immutable) {
256        mHashes = EMPTY_IMMUTABLE_INTS;
257        mArray = ContainerHelpers.EMPTY_OBJECTS;
258        mSize = 0;
259    }
260
261    /**
262     * Create a new ArrayMap with the mappings from the given ArrayMap.
263     */
264    public ArrayMap(ArrayMap map) {
265        this();
266        if (map != null) {
267            putAll(map);
268        }
269    }
270
271    /**
272     * Make the array map empty.  All storage is released.
273     */
274    @Override
275    public void clear() {
276        if (mSize > 0) {
277            freeArrays(mHashes, mArray, mSize);
278            mHashes = ContainerHelpers.EMPTY_INTS;
279            mArray = ContainerHelpers.EMPTY_OBJECTS;
280            mSize = 0;
281        }
282    }
283
284    /**
285     * @hide
286     * Like {@link #clear}, but doesn't reduce the capacity of the ArrayMap.
287     */
288    public void erase() {
289        if (mSize > 0) {
290            final int N = mSize<<1;
291            final Object[] array = mArray;
292            for (int i=0; i<N; i++) {
293                array[i] = null;
294            }
295            mSize = 0;
296        }
297    }
298
299    /**
300     * Ensure the array map can hold at least <var>minimumCapacity</var>
301     * items.
302     */
303    public void ensureCapacity(int minimumCapacity) {
304        if (mHashes.length < minimumCapacity) {
305            final int[] ohashes = mHashes;
306            final Object[] oarray = mArray;
307            allocArrays(minimumCapacity);
308            if (mSize > 0) {
309                System.arraycopy(ohashes, 0, mHashes, 0, mSize);
310                System.arraycopy(oarray, 0, mArray, 0, mSize<<1);
311            }
312            freeArrays(ohashes, oarray, mSize);
313        }
314    }
315
316    /**
317     * Check whether a key exists in the array.
318     *
319     * @param key The key to search for.
320     * @return Returns true if the key exists, else false.
321     */
322    @Override
323    public boolean containsKey(Object key) {
324        return key == null ? (indexOfNull() >= 0) : (indexOf(key, key.hashCode()) >= 0);
325    }
326
327    int indexOfValue(Object value) {
328        final int N = mSize*2;
329        final Object[] array = mArray;
330        if (value == null) {
331            for (int i=1; i<N; i+=2) {
332                if (array[i] == null) {
333                    return i>>1;
334                }
335            }
336        } else {
337            for (int i=1; i<N; i+=2) {
338                if (value.equals(array[i])) {
339                    return i>>1;
340                }
341            }
342        }
343        return -1;
344    }
345
346    /**
347     * Check whether a value exists in the array.  This requires a linear search
348     * through the entire array.
349     *
350     * @param value The value to search for.
351     * @return Returns true if the value exists, else false.
352     */
353    @Override
354    public boolean containsValue(Object value) {
355        return indexOfValue(value) >= 0;
356    }
357
358    /**
359     * Retrieve a value from the array.
360     * @param key The key of the value to retrieve.
361     * @return Returns the value associated with the given key,
362     * or null if there is no such key.
363     */
364    @Override
365    public V get(Object key) {
366        final int index = key == null ? indexOfNull() : indexOf(key, key.hashCode());
367        return index >= 0 ? (V)mArray[(index<<1)+1] : null;
368    }
369
370    /**
371     * Return the key at the given index in the array.
372     * @param index The desired index, must be between 0 and {@link #size()}-1.
373     * @return Returns the key stored at the given index.
374     */
375    public K keyAt(int index) {
376        return (K)mArray[index << 1];
377    }
378
379    /**
380     * Return the value at the given index in the array.
381     * @param index The desired index, must be between 0 and {@link #size()}-1.
382     * @return Returns the value stored at the given index.
383     */
384    public V valueAt(int index) {
385        return (V)mArray[(index << 1) + 1];
386    }
387
388    /**
389     * Set the value at a given index in the array.
390     * @param index The desired index, must be between 0 and {@link #size()}-1.
391     * @param value The new value to store at this index.
392     * @return Returns the previous value at the given index.
393     */
394    public V setValueAt(int index, V value) {
395        index = (index << 1) + 1;
396        V old = (V)mArray[index];
397        mArray[index] = value;
398        return old;
399    }
400
401    /**
402     * Return true if the array map contains no items.
403     */
404    @Override
405    public boolean isEmpty() {
406        return mSize <= 0;
407    }
408
409    /**
410     * Add a new value to the array map.
411     * @param key The key under which to store the value.  <b>Must not be null.</b>  If
412     * this key already exists in the array, its value will be replaced.
413     * @param value The value to store for the given key.
414     * @return Returns the old value that was stored for the given key, or null if there
415     * was no such key.
416     */
417    @Override
418    public V put(K key, V value) {
419        final int hash;
420        int index;
421        if (key == null) {
422            hash = 0;
423            index = indexOfNull();
424        } else {
425            hash = key.hashCode();
426            index = indexOf(key, hash);
427        }
428        if (index >= 0) {
429            index = (index<<1) + 1;
430            final V old = (V)mArray[index];
431            mArray[index] = value;
432            return old;
433        }
434
435        index = ~index;
436        if (mSize >= mHashes.length) {
437            final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1))
438                    : (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
439
440            if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n);
441
442            final int[] ohashes = mHashes;
443            final Object[] oarray = mArray;
444            allocArrays(n);
445
446            if (mHashes.length > 0) {
447                if (DEBUG) Log.d(TAG, "put: copy 0-" + mSize + " to 0");
448                System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
449                System.arraycopy(oarray, 0, mArray, 0, oarray.length);
450            }
451
452            freeArrays(ohashes, oarray, mSize);
453        }
454
455        if (index < mSize) {
456            if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (mSize-index)
457                    + " to " + (index+1));
458            System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);
459            System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
460        }
461
462        mHashes[index] = hash;
463        mArray[index<<1] = key;
464        mArray[(index<<1)+1] = value;
465        mSize++;
466        return null;
467    }
468
469    /**
470     * Special fast path for appending items to the end of the array without validation.
471     * The array must already be large enough to contain the item.
472     * @hide
473     */
474    public void append(K key, V value) {
475        int index = mSize;
476        final int hash = key == null ? 0 : key.hashCode();
477        if (index >= mHashes.length) {
478            throw new IllegalStateException("Array is full");
479        }
480        if (index > 0 && mHashes[index-1] > hash) {
481            RuntimeException e = new RuntimeException("here");
482            e.fillInStackTrace();
483            Log.w(TAG, "New hash " + hash
484                    + " is before end of array hash " + mHashes[index-1]
485                    + " at index " + index + " key " + key, e);
486            put(key, value);
487            return;
488        }
489        mSize = index+1;
490        mHashes[index] = hash;
491        index <<= 1;
492        mArray[index] = key;
493        mArray[index+1] = value;
494    }
495
496    /**
497     * Perform a {@link #put(Object, Object)} of all key/value pairs in <var>array</var>
498     * @param array The array whose contents are to be retrieved.
499     */
500    public void putAll(ArrayMap<? extends K, ? extends V> array) {
501        final int N = array.mSize;
502        ensureCapacity(mSize + N);
503        if (mSize == 0) {
504            if (N > 0) {
505                System.arraycopy(array.mHashes, 0, mHashes, 0, N);
506                System.arraycopy(array.mArray, 0, mArray, 0, N<<1);
507                mSize = N;
508            }
509        } else {
510            for (int i=0; i<N; i++) {
511                put(array.keyAt(i), array.valueAt(i));
512            }
513        }
514    }
515
516    /**
517     * Remove an existing key from the array map.
518     * @param key The key of the mapping to remove.
519     * @return Returns the value that was stored under the key, or null if there
520     * was no such key.
521     */
522    @Override
523    public V remove(Object key) {
524        int index = key == null ? indexOfNull() : indexOf(key, key.hashCode());
525        if (index >= 0) {
526            return removeAt(index);
527        }
528
529        return null;
530    }
531
532    /**
533     * Remove the key/value mapping at the given index.
534     * @param index The desired index, must be between 0 and {@link #size()}-1.
535     * @return Returns the value that was stored at this index.
536     */
537    public V removeAt(int index) {
538        final Object old = mArray[(index << 1) + 1];
539        if (mSize <= 1) {
540            // Now empty.
541            if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
542            freeArrays(mHashes, mArray, mSize);
543            mHashes = ContainerHelpers.EMPTY_INTS;
544            mArray = ContainerHelpers.EMPTY_OBJECTS;
545            mSize = 0;
546        } else {
547            if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
548                // Shrunk enough to reduce size of arrays.  We don't allow it to
549                // shrink smaller than (BASE_SIZE*2) to avoid flapping between
550                // that and BASE_SIZE.
551                final int n = mSize > (BASE_SIZE*2) ? (mSize + (mSize>>1)) : (BASE_SIZE*2);
552
553                if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n);
554
555                final int[] ohashes = mHashes;
556                final Object[] oarray = mArray;
557                allocArrays(n);
558
559                mSize--;
560                if (index > 0) {
561                    if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
562                    System.arraycopy(ohashes, 0, mHashes, 0, index);
563                    System.arraycopy(oarray, 0, mArray, 0, index << 1);
564                }
565                if (index < mSize) {
566                    if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + mSize
567                            + " to " + index);
568                    System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index);
569                    System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,
570                            (mSize - index) << 1);
571                }
572            } else {
573                mSize--;
574                if (index < mSize) {
575                    if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + mSize
576                            + " to " + index);
577                    System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index);
578                    System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,
579                            (mSize - index) << 1);
580                }
581                mArray[mSize << 1] = null;
582                mArray[(mSize << 1) + 1] = null;
583            }
584        }
585        return (V)old;
586    }
587
588    /**
589     * Return the number of items in this array map.
590     */
591    @Override
592    public int size() {
593        return mSize;
594    }
595
596    /**
597     * {@inheritDoc}
598     *
599     * <p>This implementation returns false if the object is not a map, or
600     * if the maps have different sizes. Otherwise, for each key in this map,
601     * values of both maps are compared. If the values for any key are not
602     * equal, the method returns false, otherwise it returns true.
603     */
604    @Override
605    public boolean equals(Object object) {
606        if (this == object) {
607            return true;
608        }
609        if (object instanceof Map) {
610            Map<?, ?> map = (Map<?, ?>) object;
611            if (size() != map.size()) {
612                return false;
613            }
614
615            try {
616                for (int i=0; i<mSize; i++) {
617                    K key = keyAt(i);
618                    V mine = valueAt(i);
619                    Object theirs = map.get(key);
620                    if (mine == null) {
621                        if (theirs != null || !map.containsKey(key)) {
622                            return false;
623                        }
624                    } else if (!mine.equals(theirs)) {
625                        return false;
626                    }
627                }
628            } catch (NullPointerException ignored) {
629                return false;
630            } catch (ClassCastException ignored) {
631                return false;
632            }
633            return true;
634        }
635        return false;
636    }
637
638    /**
639     * {@inheritDoc}
640     */
641    @Override
642    public int hashCode() {
643        final int[] hashes = mHashes;
644        final Object[] array = mArray;
645        int result = 0;
646        for (int i = 0, v = 1, s = mSize; i < s; i++, v+=2) {
647            Object value = array[v];
648            result += hashes[i] ^ (value == null ? 0 : value.hashCode());
649        }
650        return result;
651    }
652
653    /**
654     * {@inheritDoc}
655     *
656     * <p>This implementation composes a string by iterating over its mappings. If
657     * this map contains itself as a key or a value, the string "(this Map)"
658     * will appear in its place.
659     */
660    @Override
661    public String toString() {
662        if (isEmpty()) {
663            return "{}";
664        }
665
666        StringBuilder buffer = new StringBuilder(mSize * 28);
667        buffer.append('{');
668        for (int i=0; i<mSize; i++) {
669            if (i > 0) {
670                buffer.append(", ");
671            }
672            Object key = keyAt(i);
673            if (key != this) {
674                buffer.append(key);
675            } else {
676                buffer.append("(this Map)");
677            }
678            buffer.append('=');
679            Object value = valueAt(i);
680            if (value != this) {
681                buffer.append(value);
682            } else {
683                buffer.append("(this Map)");
684            }
685        }
686        buffer.append('}');
687        return buffer.toString();
688    }
689
690    // ------------------------------------------------------------------------
691    // Interop with traditional Java containers.  Not as efficient as using
692    // specialized collection APIs.
693    // ------------------------------------------------------------------------
694
695    private MapCollections<K, V> getCollection() {
696        if (mCollections == null) {
697            mCollections = new MapCollections<K, V>() {
698                @Override
699                protected int colGetSize() {
700                    return mSize;
701                }
702
703                @Override
704                protected Object colGetEntry(int index, int offset) {
705                    return mArray[(index<<1) + offset];
706                }
707
708                @Override
709                protected int colIndexOfKey(Object key) {
710                    return key == null ? indexOfNull() : indexOf(key, key.hashCode());
711                }
712
713                @Override
714                protected int colIndexOfValue(Object value) {
715                    return indexOfValue(value);
716                }
717
718                @Override
719                protected Map<K, V> colGetMap() {
720                    return ArrayMap.this;
721                }
722
723                @Override
724                protected void colPut(K key, V value) {
725                    put(key, value);
726                }
727
728                @Override
729                protected V colSetValue(int index, V value) {
730                    return setValueAt(index, value);
731                }
732
733                @Override
734                protected void colRemoveAt(int index) {
735                    removeAt(index);
736                }
737
738                @Override
739                protected void colClear() {
740                    clear();
741                }
742            };
743        }
744        return mCollections;
745    }
746
747    /**
748     * Determine if the array map contains all of the keys in the given collection.
749     * @param collection The collection whose contents are to be checked against.
750     * @return Returns true if this array map contains a key for every entry
751     * in <var>collection</var>, else returns false.
752     */
753    public boolean containsAll(Collection<?> collection) {
754        return MapCollections.containsAllHelper(this, collection);
755    }
756
757    /**
758     * Perform a {@link #put(Object, Object)} of all key/value pairs in <var>map</var>
759     * @param map The map whose contents are to be retrieved.
760     */
761    @Override
762    public void putAll(Map<? extends K, ? extends V> map) {
763        ensureCapacity(mSize + map.size());
764        for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
765            put(entry.getKey(), entry.getValue());
766        }
767    }
768
769    /**
770     * Remove all keys in the array map that exist in the given collection.
771     * @param collection The collection whose contents are to be used to remove keys.
772     * @return Returns true if any keys were removed from the array map, else false.
773     */
774    public boolean removeAll(Collection<?> collection) {
775        return MapCollections.removeAllHelper(this, collection);
776    }
777
778    /**
779     * Remove all keys in the array map that do <b>not</b> exist in the given collection.
780     * @param collection The collection whose contents are to be used to determine which
781     * keys to keep.
782     * @return Returns true if any keys were removed from the array map, else false.
783     */
784    public boolean retainAll(Collection<?> collection) {
785        return MapCollections.retainAllHelper(this, collection);
786    }
787
788    /**
789     * Return a {@link java.util.Set} for iterating over and interacting with all mappings
790     * in the array map.
791     *
792     * <p><b>Note:</b> this is a very inefficient way to access the array contents, it
793     * requires generating a number of temporary objects.</p>
794     *
795     * <p><b>Note:</b></p> the semantics of this
796     * Set are subtly different than that of a {@link java.util.HashMap}: most important,
797     * the {@link java.util.Map.Entry Map.Entry} object returned by its iterator is a single
798     * object that exists for the entire iterator, so you can <b>not</b> hold on to it
799     * after calling {@link java.util.Iterator#next() Iterator.next}.</p>
800     */
801    @Override
802    public Set<Map.Entry<K, V>> entrySet() {
803        return getCollection().getEntrySet();
804    }
805
806    /**
807     * Return a {@link java.util.Set} for iterating over and interacting with all keys
808     * in the array map.
809     *
810     * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
811     * requires generating a number of temporary objects.</p>
812     */
813    @Override
814    public Set<K> keySet() {
815        return getCollection().getKeySet();
816    }
817
818    /**
819     * Return a {@link java.util.Collection} for iterating over and interacting with all values
820     * in the array map.
821     *
822     * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
823     * requires generating a number of temporary objects.</p>
824     */
825    @Override
826    public Collection<V> values() {
827        return getCollection().getValues();
828    }
829}
830