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