blob: bdb1fdcb9ff8e93b6bc9d61457d42504635f4bf2 [file] [log] [blame]
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -07001/*
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
Adam Lesinski776abc22014-03-07 11:30:59 -050019import libcore.util.EmptyArray;
20
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070021import java.util.Collection;
22import java.util.Map;
23import 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,
Dianne Hackbornb993f412013-07-12 17:46:45 -070039 * the performance difference is not significant, less than 50%.</p>
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070040 *
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070041 * <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
Dianne Hackbornb993f412013-07-12 17:46:45 -070045 * explicit call to set the capacity should turn off this aggressive shrinking behavior.</p>
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070046 */
47public 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 /**
Dianne Hackborn894ce602015-12-04 13:25:48 -080063 * Special hash array value that indicates the container is immutable.
64 */
65 static final int[] EMPTY_IMMUTABLE_INTS = new int[0];
66
67 /**
Dianne Hackbornb87655b2013-07-17 19:06:22 -070068 * @hide Special immutable empty ArrayMap.
69 */
70 public static final ArrayMap EMPTY = new ArrayMap(true);
71
72 /**
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070073 * 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 int[] mHashes;
84 Object[] mArray;
85 int mSize;
86 MapCollections<K, V> mCollections;
87
Dianne Hackborn62d708f2013-07-25 16:42:59 -070088 int indexOf(Object key, int hash) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070089 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
Dianne Hackborn3e82ba12013-07-16 13:23:55 -070096 int index = ContainerHelpers.binarySearch(mHashes, N, hash);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070097
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.
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700104 if (key.equals(mArray[index<<1])) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700105 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++) {
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700111 if (key.equals(mArray[end << 1])) return end;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700112 }
113
114 // Search for a matching key before the index.
115 for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700116 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;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700155 }
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) {
Dianne Hackbornb87655b2013-07-17 19:06:22 -0700165 if (mHashes == EMPTY_IMMUTABLE_INTS) {
166 throw new UnsupportedOperationException("ArrayMap is immutable");
167 }
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700168 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() {
Adam Lesinski776abc22014-03-07 11:30:59 -0500239 mHashes = EmptyArray.INT;
240 mArray = EmptyArray.OBJECT;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700241 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) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500249 mHashes = EmptyArray.INT;
250 mArray = EmptyArray.OBJECT;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700251 } else {
252 allocArrays(capacity);
253 }
254 mSize = 0;
255 }
Dianne Hackborn21ab6f42013-06-10 15:59:15 -0700256
Dianne Hackbornb87655b2013-07-17 19:06:22 -0700257 private ArrayMap(boolean immutable) {
Adam Lesinskib6bdb0f2015-02-05 11:11:03 -0800258 // If this is immutable, use the sentinal EMPTY_IMMUTABLE_INTS
259 // instance instead of the usual EmptyArray.INT. The reference
260 // is checked later to see if the array is allowed to grow.
261 mHashes = immutable ? EMPTY_IMMUTABLE_INTS : EmptyArray.INT;
Adam Lesinski776abc22014-03-07 11:30:59 -0500262 mArray = EmptyArray.OBJECT;
Dianne Hackbornb87655b2013-07-17 19:06:22 -0700263 mSize = 0;
264 }
265
Chet Haase08735182013-06-04 10:44:40 -0700266 /**
267 * Create a new ArrayMap with the mappings from the given ArrayMap.
268 */
Dianne Hackbornf16747d2015-06-10 17:49:43 -0700269 public ArrayMap(ArrayMap<K, V> map) {
Chet Haase08735182013-06-04 10:44:40 -0700270 this();
271 if (map != null) {
272 putAll(map);
273 }
274 }
275
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700276 /**
277 * Make the array map empty. All storage is released.
278 */
279 @Override
280 public void clear() {
Dianne Hackbornb87655b2013-07-17 19:06:22 -0700281 if (mSize > 0) {
Dianne Hackborn390517b2013-05-30 15:03:32 -0700282 freeArrays(mHashes, mArray, mSize);
Adam Lesinski776abc22014-03-07 11:30:59 -0500283 mHashes = EmptyArray.INT;
284 mArray = EmptyArray.OBJECT;
Dianne Hackborn390517b2013-05-30 15:03:32 -0700285 mSize = 0;
286 }
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700287 }
288
289 /**
Dianne Hackborn450d8c52013-07-19 16:32:02 -0700290 * @hide
291 * Like {@link #clear}, but doesn't reduce the capacity of the ArrayMap.
292 */
293 public void erase() {
294 if (mSize > 0) {
295 final int N = mSize<<1;
296 final Object[] array = mArray;
297 for (int i=0; i<N; i++) {
298 array[i] = null;
299 }
Dianne Hackborn4a7d8242013-10-03 10:19:20 -0700300 mSize = 0;
Dianne Hackborn450d8c52013-07-19 16:32:02 -0700301 }
302 }
303
304 /**
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700305 * Ensure the array map can hold at least <var>minimumCapacity</var>
306 * items.
307 */
308 public void ensureCapacity(int minimumCapacity) {
309 if (mHashes.length < minimumCapacity) {
Dianne Hackborn21ab6f42013-06-10 15:59:15 -0700310 final int[] ohashes = mHashes;
311 final Object[] oarray = mArray;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700312 allocArrays(minimumCapacity);
Dianne Hackborn390517b2013-05-30 15:03:32 -0700313 if (mSize > 0) {
314 System.arraycopy(ohashes, 0, mHashes, 0, mSize);
315 System.arraycopy(oarray, 0, mArray, 0, mSize<<1);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700316 }
317 freeArrays(ohashes, oarray, mSize);
318 }
319 }
320
321 /**
322 * Check whether a key exists in the array.
323 *
324 * @param key The key to search for.
325 * @return Returns true if the key exists, else false.
326 */
327 @Override
328 public boolean containsKey(Object key) {
Adam Lesinski4e9c07c2014-08-26 11:53:32 -0700329 return indexOfKey(key) >= 0;
330 }
331
332 /**
333 * Returns the index of a key in the set.
334 *
335 * @param key The key to search for.
336 * @return Returns the index of the key if it exists, else a negative integer.
337 */
338 public int indexOfKey(Object key) {
339 return key == null ? indexOfNull() : indexOf(key, key.hashCode());
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700340 }
341
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700342 int indexOfValue(Object value) {
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700343 final int N = mSize*2;
344 final Object[] array = mArray;
345 if (value == null) {
346 for (int i=1; i<N; i+=2) {
347 if (array[i] == null) {
348 return i>>1;
349 }
350 }
351 } else {
352 for (int i=1; i<N; i+=2) {
353 if (value.equals(array[i])) {
354 return i>>1;
355 }
356 }
357 }
358 return -1;
359 }
360
361 /**
362 * Check whether a value exists in the array. This requires a linear search
363 * through the entire array.
364 *
365 * @param value The value to search for.
366 * @return Returns true if the value exists, else false.
367 */
368 @Override
369 public boolean containsValue(Object value) {
370 return indexOfValue(value) >= 0;
371 }
372
373 /**
374 * Retrieve a value from the array.
375 * @param key The key of the value to retrieve.
376 * @return Returns the value associated with the given key,
377 * or null if there is no such key.
378 */
379 @Override
380 public V get(Object key) {
Adam Lesinski4e9c07c2014-08-26 11:53:32 -0700381 final int index = indexOfKey(key);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700382 return index >= 0 ? (V)mArray[(index<<1)+1] : null;
383 }
384
385 /**
386 * Return the key at the given index in the array.
387 * @param index The desired index, must be between 0 and {@link #size()}-1.
388 * @return Returns the key stored at the given index.
389 */
390 public K keyAt(int index) {
391 return (K)mArray[index << 1];
392 }
393
394 /**
395 * Return the value at the given index in the array.
396 * @param index The desired index, must be between 0 and {@link #size()}-1.
397 * @return Returns the value stored at the given index.
398 */
399 public V valueAt(int index) {
400 return (V)mArray[(index << 1) + 1];
401 }
402
403 /**
404 * Set the value at a given index in the array.
405 * @param index The desired index, must be between 0 and {@link #size()}-1.
406 * @param value The new value to store at this index.
407 * @return Returns the previous value at the given index.
408 */
409 public V setValueAt(int index, V value) {
410 index = (index << 1) + 1;
411 V old = (V)mArray[index];
412 mArray[index] = value;
413 return old;
414 }
415
416 /**
417 * Return true if the array map contains no items.
418 */
419 @Override
420 public boolean isEmpty() {
421 return mSize <= 0;
422 }
423
424 /**
425 * Add a new value to the array map.
Dianne Hackborna3fb40d2014-08-12 15:06:50 -0700426 * @param key The key under which to store the value. If
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700427 * this key already exists in the array, its value will be replaced.
428 * @param value The value to store for the given key.
429 * @return Returns the old value that was stored for the given key, or null if there
430 * was no such key.
431 */
432 @Override
433 public V put(K key, V value) {
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700434 final int hash;
435 int index;
436 if (key == null) {
437 hash = 0;
438 index = indexOfNull();
439 } else {
440 hash = key.hashCode();
441 index = indexOf(key, hash);
442 }
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700443 if (index >= 0) {
444 index = (index<<1) + 1;
445 final V old = (V)mArray[index];
446 mArray[index] = value;
447 return old;
448 }
449
450 index = ~index;
451 if (mSize >= mHashes.length) {
452 final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1))
453 : (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
454
455 if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n);
456
457 final int[] ohashes = mHashes;
458 final Object[] oarray = mArray;
459 allocArrays(n);
460
461 if (mHashes.length > 0) {
462 if (DEBUG) Log.d(TAG, "put: copy 0-" + mSize + " to 0");
463 System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
464 System.arraycopy(oarray, 0, mArray, 0, oarray.length);
465 }
466
467 freeArrays(ohashes, oarray, mSize);
468 }
469
470 if (index < mSize) {
471 if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (mSize-index)
472 + " to " + (index+1));
473 System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);
474 System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
475 }
476
477 mHashes[index] = hash;
478 mArray[index<<1] = key;
479 mArray[(index<<1)+1] = value;
480 mSize++;
481 return null;
482 }
483
484 /**
Dianne Hackbornb87655b2013-07-17 19:06:22 -0700485 * Special fast path for appending items to the end of the array without validation.
486 * The array must already be large enough to contain the item.
487 * @hide
488 */
489 public void append(K key, V value) {
490 int index = mSize;
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700491 final int hash = key == null ? 0 : key.hashCode();
Dianne Hackbornb87655b2013-07-17 19:06:22 -0700492 if (index >= mHashes.length) {
493 throw new IllegalStateException("Array is full");
494 }
495 if (index > 0 && mHashes[index-1] > hash) {
Dianne Hackborn450d8c52013-07-19 16:32:02 -0700496 RuntimeException e = new RuntimeException("here");
497 e.fillInStackTrace();
498 Log.w(TAG, "New hash " + hash
499 + " is before end of array hash " + mHashes[index-1]
500 + " at index " + index + " key " + key, e);
501 put(key, value);
502 return;
Dianne Hackbornb87655b2013-07-17 19:06:22 -0700503 }
504 mSize = index+1;
505 mHashes[index] = hash;
506 index <<= 1;
507 mArray[index] = key;
508 mArray[index+1] = value;
509 }
510
511 /**
Dianne Hackborn9c3e74f2014-08-13 15:39:50 -0700512 * The use of the {@link #append} function can result in invalid array maps, in particular
513 * an array map where the same key appears multiple times. This function verifies that
514 * the array map is valid, throwing IllegalArgumentException if a problem is found. The
515 * main use for this method is validating an array map after unpacking from an IPC, to
516 * protect against malicious callers.
517 * @hide
518 */
519 public void validate() {
520 final int N = mSize;
521 if (N <= 1) {
522 // There can't be dups.
523 return;
524 }
525 int basehash = mHashes[0];
526 int basei = 0;
527 for (int i=1; i<N; i++) {
528 int hash = mHashes[i];
529 if (hash != basehash) {
530 basehash = hash;
531 basei = i;
532 continue;
533 }
534 // We are in a run of entries with the same hash code. Go backwards through
535 // the array to see if any keys are the same.
536 final Object cur = mArray[i<<1];
537 for (int j=i-1; j>=basei; j--) {
538 final Object prev = mArray[j<<1];
539 if (cur == prev) {
540 throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);
541 }
542 if (cur != null && prev != null && cur.equals(prev)) {
543 throw new IllegalArgumentException("Duplicate key in ArrayMap: " + cur);
544 }
545 }
546 }
547 }
548
549 /**
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700550 * Perform a {@link #put(Object, Object)} of all key/value pairs in <var>array</var>
551 * @param array The array whose contents are to be retrieved.
552 */
553 public void putAll(ArrayMap<? extends K, ? extends V> array) {
554 final int N = array.mSize;
555 ensureCapacity(mSize + N);
Chet Haasef4130cf2013-06-06 16:34:33 -0700556 if (mSize == 0) {
557 if (N > 0) {
558 System.arraycopy(array.mHashes, 0, mHashes, 0, N);
559 System.arraycopy(array.mArray, 0, mArray, 0, N<<1);
560 mSize = N;
561 }
562 } else {
563 for (int i=0; i<N; i++) {
564 put(array.keyAt(i), array.valueAt(i));
565 }
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700566 }
567 }
568
569 /**
570 * Remove an existing key from the array map.
571 * @param key The key of the mapping to remove.
572 * @return Returns the value that was stored under the key, or null if there
573 * was no such key.
574 */
575 @Override
576 public V remove(Object key) {
Adam Lesinski4e9c07c2014-08-26 11:53:32 -0700577 final int index = indexOfKey(key);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700578 if (index >= 0) {
579 return removeAt(index);
580 }
581
582 return null;
583 }
584
585 /**
586 * Remove the key/value mapping at the given index.
587 * @param index The desired index, must be between 0 and {@link #size()}-1.
588 * @return Returns the value that was stored at this index.
589 */
590 public V removeAt(int index) {
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700591 final Object old = mArray[(index << 1) + 1];
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700592 if (mSize <= 1) {
593 // Now empty.
594 if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
595 freeArrays(mHashes, mArray, mSize);
Adam Lesinski776abc22014-03-07 11:30:59 -0500596 mHashes = EmptyArray.INT;
597 mArray = EmptyArray.OBJECT;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700598 mSize = 0;
599 } else {
600 if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
601 // Shrunk enough to reduce size of arrays. We don't allow it to
602 // shrink smaller than (BASE_SIZE*2) to avoid flapping between
603 // that and BASE_SIZE.
604 final int n = mSize > (BASE_SIZE*2) ? (mSize + (mSize>>1)) : (BASE_SIZE*2);
605
606 if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n);
607
608 final int[] ohashes = mHashes;
609 final Object[] oarray = mArray;
610 allocArrays(n);
611
612 mSize--;
613 if (index > 0) {
614 if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
615 System.arraycopy(ohashes, 0, mHashes, 0, index);
616 System.arraycopy(oarray, 0, mArray, 0, index << 1);
617 }
618 if (index < mSize) {
619 if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + mSize
620 + " to " + index);
621 System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index);
622 System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1,
623 (mSize - index) << 1);
624 }
625 } else {
626 mSize--;
627 if (index < mSize) {
628 if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + mSize
629 + " to " + index);
630 System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index);
631 System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1,
632 (mSize - index) << 1);
633 }
634 mArray[mSize << 1] = null;
635 mArray[(mSize << 1) + 1] = null;
636 }
637 }
Dianne Hackborn62d708f2013-07-25 16:42:59 -0700638 return (V)old;
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700639 }
640
641 /**
642 * Return the number of items in this array map.
643 */
644 @Override
645 public int size() {
646 return mSize;
647 }
648
Dianne Hackborn21ab6f42013-06-10 15:59:15 -0700649 /**
650 * {@inheritDoc}
651 *
652 * <p>This implementation returns false if the object is not a map, or
653 * if the maps have different sizes. Otherwise, for each key in this map,
654 * values of both maps are compared. If the values for any key are not
655 * equal, the method returns false, otherwise it returns true.
656 */
657 @Override
658 public boolean equals(Object object) {
659 if (this == object) {
660 return true;
661 }
662 if (object instanceof Map) {
663 Map<?, ?> map = (Map<?, ?>) object;
664 if (size() != map.size()) {
665 return false;
666 }
667
668 try {
669 for (int i=0; i<mSize; i++) {
670 K key = keyAt(i);
671 V mine = valueAt(i);
672 Object theirs = map.get(key);
673 if (mine == null) {
674 if (theirs != null || !map.containsKey(key)) {
675 return false;
676 }
677 } else if (!mine.equals(theirs)) {
678 return false;
679 }
680 }
681 } catch (NullPointerException ignored) {
682 return false;
683 } catch (ClassCastException ignored) {
684 return false;
685 }
686 return true;
687 }
688 return false;
689 }
690
691 /**
692 * {@inheritDoc}
693 */
694 @Override
695 public int hashCode() {
696 final int[] hashes = mHashes;
697 final Object[] array = mArray;
698 int result = 0;
699 for (int i = 0, v = 1, s = mSize; i < s; i++, v+=2) {
700 Object value = array[v];
701 result += hashes[i] ^ (value == null ? 0 : value.hashCode());
702 }
703 return result;
704 }
705
Dianne Hackborna17c0f52013-06-20 18:59:11 -0700706 /**
707 * {@inheritDoc}
708 *
709 * <p>This implementation composes a string by iterating over its mappings. If
710 * this map contains itself as a key or a value, the string "(this Map)"
711 * will appear in its place.
712 */
713 @Override
714 public String toString() {
715 if (isEmpty()) {
716 return "{}";
717 }
718
719 StringBuilder buffer = new StringBuilder(mSize * 28);
720 buffer.append('{');
721 for (int i=0; i<mSize; i++) {
722 if (i > 0) {
723 buffer.append(", ");
724 }
725 Object key = keyAt(i);
726 if (key != this) {
727 buffer.append(key);
728 } else {
729 buffer.append("(this Map)");
730 }
731 buffer.append('=');
732 Object value = valueAt(i);
733 if (value != this) {
734 buffer.append(value);
735 } else {
736 buffer.append("(this Map)");
737 }
738 }
739 buffer.append('}');
740 return buffer.toString();
741 }
742
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700743 // ------------------------------------------------------------------------
744 // Interop with traditional Java containers. Not as efficient as using
745 // specialized collection APIs.
746 // ------------------------------------------------------------------------
747
748 private MapCollections<K, V> getCollection() {
749 if (mCollections == null) {
750 mCollections = new MapCollections<K, V>() {
751 @Override
752 protected int colGetSize() {
753 return mSize;
754 }
755
756 @Override
757 protected Object colGetEntry(int index, int offset) {
758 return mArray[(index<<1) + offset];
759 }
760
761 @Override
762 protected int colIndexOfKey(Object key) {
Adam Lesinski4e9c07c2014-08-26 11:53:32 -0700763 return indexOfKey(key);
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700764 }
765
766 @Override
767 protected int colIndexOfValue(Object value) {
768 return indexOfValue(value);
769 }
770
771 @Override
772 protected Map<K, V> colGetMap() {
773 return ArrayMap.this;
774 }
775
776 @Override
777 protected void colPut(K key, V value) {
778 put(key, value);
779 }
780
781 @Override
782 protected V colSetValue(int index, V value) {
783 return setValueAt(index, value);
784 }
785
786 @Override
787 protected void colRemoveAt(int index) {
788 removeAt(index);
789 }
790
791 @Override
792 protected void colClear() {
793 clear();
794 }
795 };
796 }
797 return mCollections;
798 }
799
800 /**
801 * Determine if the array map contains all of the keys in the given collection.
802 * @param collection The collection whose contents are to be checked against.
803 * @return Returns true if this array map contains a key for every entry
804 * in <var>collection</var>, else returns false.
805 */
806 public boolean containsAll(Collection<?> collection) {
807 return MapCollections.containsAllHelper(this, collection);
808 }
809
810 /**
811 * Perform a {@link #put(Object, Object)} of all key/value pairs in <var>map</var>
812 * @param map The map whose contents are to be retrieved.
813 */
814 @Override
815 public void putAll(Map<? extends K, ? extends V> map) {
816 ensureCapacity(mSize + map.size());
817 for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
818 put(entry.getKey(), entry.getValue());
819 }
820 }
821
822 /**
823 * Remove all keys in the array map that exist in the given collection.
824 * @param collection The collection whose contents are to be used to remove keys.
825 * @return Returns true if any keys were removed from the array map, else false.
826 */
827 public boolean removeAll(Collection<?> collection) {
828 return MapCollections.removeAllHelper(this, collection);
829 }
830
831 /**
832 * Remove all keys in the array map that do <b>not</b> exist in the given collection.
833 * @param collection The collection whose contents are to be used to determine which
834 * keys to keep.
835 * @return Returns true if any keys were removed from the array map, else false.
836 */
837 public boolean retainAll(Collection<?> collection) {
838 return MapCollections.retainAllHelper(this, collection);
839 }
840
841 /**
842 * Return a {@link java.util.Set} for iterating over and interacting with all mappings
843 * in the array map.
844 *
845 * <p><b>Note:</b> this is a very inefficient way to access the array contents, it
Dianne Hackbornf16747d2015-06-10 17:49:43 -0700846 * requires generating a number of temporary objects and allocates additional state
847 * information associated with the container that will remain for the life of the container.</p>
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700848 *
849 * <p><b>Note:</b></p> the semantics of this
850 * Set are subtly different than that of a {@link java.util.HashMap}: most important,
851 * the {@link java.util.Map.Entry Map.Entry} object returned by its iterator is a single
852 * object that exists for the entire iterator, so you can <b>not</b> hold on to it
853 * after calling {@link java.util.Iterator#next() Iterator.next}.</p>
854 */
855 @Override
856 public Set<Map.Entry<K, V>> entrySet() {
857 return getCollection().getEntrySet();
858 }
859
860 /**
861 * Return a {@link java.util.Set} for iterating over and interacting with all keys
862 * in the array map.
863 *
Chet Haasef4130cf2013-06-06 16:34:33 -0700864 * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
Dianne Hackbornf16747d2015-06-10 17:49:43 -0700865 * requires generating a number of temporary objects and allocates additional state
866 * information associated with the container that will remain for the life of the container.</p>
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700867 */
868 @Override
869 public Set<K> keySet() {
870 return getCollection().getKeySet();
871 }
872
873 /**
874 * Return a {@link java.util.Collection} for iterating over and interacting with all values
875 * in the array map.
876 *
Chet Haasef4130cf2013-06-06 16:34:33 -0700877 * <p><b>Note:</b> this is a fairly inefficient way to access the array contents, it
Dianne Hackbornf16747d2015-06-10 17:49:43 -0700878 * requires generating a number of temporary objects and allocates additional state
879 * information associated with the container that will remain for the life of the container.</p>
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700880 */
881 @Override
882 public Collection<V> values() {
883 return getCollection().getValues();
884 }
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -0700885}