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