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