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