1 /* 2 * Copyright (C) 2021 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 com.android.server.utils; 18 19 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.Size; 24 25 import com.android.internal.annotations.VisibleForTesting; 26 import com.android.internal.util.ArrayUtils; 27 import com.android.internal.util.GrowingArrayUtils; 28 29 import java.util.Arrays; 30 31 /** 32 * A {@link WatchedSparseBooleanMatrix} is an compact NxN array of booleans. The rows and 33 * columns of the array are indexed by integers, which need not be contiguous. The matrix 34 * is square and the row and column indices are identical. This matrix is intended to be 35 * very memory efficient. 36 * 37 * The matrix contains a map from indices to columns: this map requires 2*N integers. The 38 * boolean array is bit-packed and requires N*N/8 bytes. The memory required for an 39 * order-N matrix is therefore 2*N*4 + N*N bytes. 40 * 41 * See {@link SparseBooleanArray} for a discussion of sparse arrays. 42 */ 43 public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappable { 44 45 /** 46 * The matrix is implemented through four arrays. First, the matrix of booleans is 47 * stored in a two-dimensional {@code mValues} array of bit-packed booleans. 48 * {@code mValues} is always of size {@code mOrder * mOrder / 8}. The factor of 8 is 49 * present because there are 8 bits in a byte. Elements of {@code mValues} are 50 * addressed with arithmetic: the element {@code {row, col}} is bit {@code col % 8} in 51 * byte * {@code (row * mOrder + col) / 8}. The term "storage index" applies to 52 * {@code mValues}. A storage index designates a row (column) in the underlying 53 * storage. This is not the same as the row seen by client code. 54 * 55 * Client code addresses the matrix through indices. These are integers that need not 56 * be contiguous. Client indices are mapped to storage indices through two linear 57 * integer arrays. {@code mKeys} is a sorted list of client indices. 58 * {@code mIndices} is a parallel array that contains storage indices. The storage 59 * index of a client index {@code k} is {@code mIndices[i]}, where 60 * {@code mKeys[i] == k}. 61 * 62 * A final array, {@code mInUse} records if storage indices are free or in use. This 63 * array is of size {@code mOrder}. A client index is deleted by removing it from 64 * {@code mKeys} and {@code mIndices} and then setting the original storage index 65 * false in {@code mInUse}. 66 * 67 * Some notes: 68 * <ul> 69 * <li> The matrix does not automatically shrink but there is a compress() method that 70 * will recover unused space. 71 * <li> Equality is a very, very expensive operation because it must walk the matrices 72 * beimg compared element by element. 73 * </ul> 74 */ 75 76 /** 77 * mOrder is always a multiple of this value. A minimal matrix therefore holds 2^12 78 * values and requires 1024 bytes. The value is visible for testing. 79 */ 80 @VisibleForTesting(visibility = PRIVATE) 81 static final int STEP = 64; 82 83 /** 84 * The number of bits in the mValues array element. 85 */ 86 private static final int PACKING = 32; 87 88 /** 89 * Constants that index into the string array returned by matrixToString. The primary 90 * consumer is test code. 91 */ 92 static final int STRING_KEY_INDEX = 0; 93 static final int STRING_MAP_INDEX = 1; 94 static final int STRING_INUSE_INDEX = 2; 95 96 /** 97 * The order of the matrix storage, including any padding. The matrix is always 98 * square. mOrder is always greater than or equal to mSize. 99 */ 100 private int mOrder; 101 102 /** 103 * The number of client keys. This is always less than or equal to mOrder. It is the 104 * order of the matrix as seen by the client. 105 */ 106 private int mSize; 107 108 /** 109 * The in-use list. 110 */ 111 private boolean[] mInUse; 112 113 /** 114 * The array of client keys (indices), in sorted order. 115 */ 116 private int[] mKeys; 117 118 /** 119 * The mapping from a client key to an storage index. If client key K is at index N 120 * in mKeys, then the storage index for K is at mMap[N]. 121 */ 122 private int[] mMap; 123 124 /** 125 * The boolean array. This array is always {@code mOrder x mOrder} in size. 126 */ 127 private int[] mValues; 128 129 /** 130 * A convenience function called when the elements are added to or removed from the storage. 131 * The watchable is always {@link this}. 132 */ onChanged()133 private void onChanged() { 134 dispatchChange(this); 135 } 136 137 /** 138 * Creates a new WatchedSparseBooleanMatrix containing no mappings. 139 */ WatchedSparseBooleanMatrix()140 public WatchedSparseBooleanMatrix() { 141 this(STEP); 142 } 143 144 /** 145 * Creates a new SparseBooleanMatrix containing no mappings that will not require any 146 * additional memory allocation to store the specified number of mappings. The 147 * capacity is always rounded up to a non-zero multiple of STEP. 148 */ WatchedSparseBooleanMatrix(int initialCapacity)149 public WatchedSparseBooleanMatrix(int initialCapacity) { 150 mOrder = initialCapacity; 151 if (mOrder < STEP) { 152 mOrder = STEP; 153 } 154 if (mOrder % STEP != 0) { 155 mOrder = ((initialCapacity / STEP) + 1) * STEP; 156 } 157 if (mOrder < STEP || (mOrder % STEP != 0)) { 158 throw new RuntimeException("mOrder is " + mOrder + " initCap is " + initialCapacity); 159 } 160 161 mInUse = ArrayUtils.newUnpaddedBooleanArray(mOrder); 162 mKeys = ArrayUtils.newUnpaddedIntArray(mOrder); 163 mMap = ArrayUtils.newUnpaddedIntArray(mOrder); 164 mValues = ArrayUtils.newUnpaddedIntArray(mOrder * mOrder / PACKING); 165 mSize = 0; 166 } 167 168 /** 169 * A copy constructor that can be used for snapshotting. 170 */ WatchedSparseBooleanMatrix(WatchedSparseBooleanMatrix r)171 private WatchedSparseBooleanMatrix(WatchedSparseBooleanMatrix r) { 172 copyFrom(r); 173 } 174 175 /** 176 * Copy from src to this. 177 */ copyFrom(@onNull WatchedSparseBooleanMatrix src)178 public void copyFrom(@NonNull WatchedSparseBooleanMatrix src) { 179 mOrder = src.mOrder; 180 mSize = src.mSize; 181 mKeys = src.mKeys.clone(); 182 mMap = src.mMap.clone(); 183 mInUse = src.mInUse.clone(); 184 mValues = src.mValues.clone(); 185 } 186 187 /** 188 * Return a copy of this object. 189 */ snapshot()190 public WatchedSparseBooleanMatrix snapshot() { 191 return new WatchedSparseBooleanMatrix(this); 192 } 193 194 /** 195 * Gets the boolean mapped from the specified key, or <code>false</code> 196 * if no such mapping has been made. 197 */ get(int row, int col)198 public boolean get(int row, int col) { 199 return get(row, col, false); 200 } 201 202 /** 203 * Gets the boolean mapped from the specified key, or the specified value 204 * if no such mapping has been made. 205 */ get(int row, int col, boolean valueIfKeyNotFound)206 public boolean get(int row, int col, boolean valueIfKeyNotFound) { 207 int r = indexOfKey(row, false); 208 int c = indexOfKey(col, false); 209 if (r >= 0 && c >= 0) { 210 return valueAt(r, c); 211 } else { 212 return valueIfKeyNotFound; 213 } 214 } 215 216 /** 217 * Adds a mapping from the specified keys to the specified value, replacing the 218 * previous mapping from the specified keys if there was one. 219 */ put(int row, int col, boolean value)220 public void put(int row, int col, boolean value) { 221 int r = indexOfKey(row); 222 int c = indexOfKey(col); 223 if (r < 0 || c < 0) { 224 // One or both of the keys has not be installed yet. Install them now. 225 // Installing either key may shift the other key. The safest course is to 226 // install the keys that are not present and then recompute both indices. 227 if (r < 0) { 228 r = indexOfKey(row, true); 229 } 230 if (c < 0) { 231 c = indexOfKey(col, true); 232 } 233 r = indexOfKey(row); 234 c = indexOfKey(col); 235 } 236 if (r >= 0 && c >= 0) { 237 setValueAt(r, c, value); 238 // setValueAt() will call onChanged(). 239 } else { 240 throw new RuntimeException("matrix overflow"); 241 } 242 } 243 244 /** 245 * Removes the mapping from the specified key, if there was any. Note that deletion 246 * applies to a single index, not to an element. The matrix never shrinks but the 247 * space will be reused the next time an index is added. 248 */ deleteKey(int key)249 public void deleteKey(int key) { 250 int i = indexOfKey(key, false); 251 if (i >= 0) { 252 removeAt(i); 253 } 254 } 255 256 /** 257 * Removes the mapping at the specified index. The matrix does not shrink. This 258 * throws ArrayIndexOutOfBounds if the index out outside the range {@code 0..size()-1}. 259 */ removeAt(int index)260 public void removeAt(int index) { 261 validateIndex(index); 262 mInUse[mMap[index]] = false; 263 // Remove the specified index and ensure that unused words in mKeys and mMap are 264 // always zero, to simplify the equality function. 265 System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1)); 266 mKeys[mSize - 1] = 0; 267 System.arraycopy(mMap, index + 1, mMap, index, mSize - (index + 1)); 268 mMap[mSize - 1] = 0; 269 mSize--; 270 onChanged(); 271 } 272 273 /** 274 * Removes all of the mappings whose index is between {@code fromIndex}, inclusive, and 275 * {@code toIndex}, exclusive. The matrix does not shrink. 276 */ removeRange(int fromIndex, int toIndex)277 public void removeRange(int fromIndex, int toIndex) { 278 if (toIndex < fromIndex) { 279 throw new ArrayIndexOutOfBoundsException("toIndex < fromIndex"); 280 } 281 final int num = toIndex - fromIndex; 282 if (num == 0) { 283 return; 284 } 285 validateIndex(fromIndex); 286 validateIndex(toIndex - 1); 287 for (int i = fromIndex; i < toIndex; i++) { 288 mInUse[mMap[i]] = false; 289 } 290 System.arraycopy(mKeys, toIndex, mKeys, fromIndex, mSize - toIndex); 291 System.arraycopy(mMap, toIndex, mMap, fromIndex, mSize - toIndex); 292 for (int i = mSize - num; i < mSize; i++) { 293 mKeys[i] = 0; 294 mMap[i] = 0; 295 } 296 mSize -= num; 297 onChanged(); 298 } 299 300 /** 301 * Returns the number of key-value mappings that this WatchedSparseBooleanMatrix 302 * currently stores. 303 */ size()304 public int size() { 305 return mSize; 306 } 307 308 /** 309 * Removes all key-value mappings from this WatchedSparseBooleanMatrix. 310 */ clear()311 public void clear() { 312 mSize = 0; 313 Arrays.fill(mInUse, false); 314 onChanged(); 315 } 316 317 /** 318 * Given an index in the range <code>0...size()-1</code>, returns the key from the 319 * <code>index</code>th key-value mapping that this WatchedSparseBooleanMatrix stores. 320 * 321 * <p>The keys corresponding to indices in ascending order are guaranteed to be in 322 * ascending order, e.g., <code>keyAt(0)</code> will return the smallest key and 323 * <code>keyAt(size()-1)</code> will return the largest key.</p> 324 * 325 * <p>{@link ArrayIndexOutOfBoundsException} is thrown for indices outside of the 326 * range <code>0...size()-1</code></p> 327 */ keyAt(int index)328 public int keyAt(int index) { 329 validateIndex(index); 330 return mKeys[index]; 331 } 332 333 /** 334 * An internal method to fetch the boolean value given the mValues row and column 335 * indices. These are not the indices used by the *At() methods. 336 */ valueAtInternal(int row, int col)337 private boolean valueAtInternal(int row, int col) { 338 int element = row * mOrder + col; 339 int offset = element / PACKING; 340 int mask = 1 << (element % PACKING); 341 return (mValues[offset] & mask) != 0; 342 } 343 344 /** 345 * Given a row and column, each in the range <code>0...size()-1</code>, returns the 346 * value from the <code>index</code>th key-value mapping that this WatchedSparseBooleanMatrix 347 * stores. 348 */ valueAt(int rowIndex, int colIndex)349 public boolean valueAt(int rowIndex, int colIndex) { 350 validateIndex(rowIndex, colIndex); 351 int r = mMap[rowIndex]; 352 int c = mMap[colIndex]; 353 return valueAtInternal(r, c); 354 } 355 356 /** 357 * An internal method to set the boolean value given the mValues row and column 358 * indices. These are not the indices used by the *At() methods. 359 */ setValueAtInternal(int row, int col, boolean value)360 private void setValueAtInternal(int row, int col, boolean value) { 361 int element = row * mOrder + col; 362 int offset = element / PACKING; 363 int mask = 1 << (element % PACKING); 364 if (value) { 365 mValues[offset] |= mask; 366 } else { 367 mValues[offset] &= ~mask; 368 } 369 } 370 371 /** 372 * Directly set the value at a particular index. 373 */ setValueAt(int rowIndex, int colIndex, boolean value)374 public void setValueAt(int rowIndex, int colIndex, boolean value) { 375 validateIndex(rowIndex, colIndex); 376 int r = mMap[rowIndex]; 377 int c = mMap[colIndex]; 378 setValueAtInternal(r, c, value); 379 onChanged(); 380 } 381 382 /** 383 * Returns the index for which {@link #keyAt} would return the specified key, or a 384 * negative number if the specified key is not mapped. 385 */ indexOfKey(int key)386 public int indexOfKey(int key) { 387 return binarySearch(mKeys, mSize, key); 388 } 389 390 /** 391 * Return true if the matrix knows the user index. 392 */ contains(int key)393 public boolean contains(int key) { 394 return indexOfKey(key) >= 0; 395 } 396 397 /** 398 * Fetch the index of a key. If the key does not exist and grow is true, then add the 399 * key. If the does not exist and grow is false, return -1. 400 */ indexOfKey(int key, boolean grow)401 private int indexOfKey(int key, boolean grow) { 402 int i = binarySearch(mKeys, mSize, key); 403 if (i < 0 && grow) { 404 i = ~i; 405 if (mSize >= mOrder) { 406 // Preemptively grow the matrix, which also grows the free list. 407 growMatrix(); 408 } 409 int newIndex = nextFree(true /* acquire */); 410 mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key); 411 mMap = GrowingArrayUtils.insert(mMap, mSize, i, newIndex); 412 mSize++; 413 414 // Initialize the row and column corresponding to the new index. 415 int valueRow = mOrder / PACKING; 416 int offset = newIndex / PACKING; 417 int mask = ~(1 << (newIndex % PACKING)); 418 Arrays.fill(mValues, newIndex * valueRow, (newIndex + 1) * valueRow, 0); 419 for (int n = 0; n < mSize; n++) { 420 mValues[n * valueRow + offset] &= mask; 421 } 422 // Do not report onChanged() from this private method. onChanged() is the 423 // responsibility of public methods that call this one. 424 } 425 return i; 426 } 427 428 /** 429 * Validate the index. This can throw. 430 */ validateIndex(int index)431 private void validateIndex(int index) { 432 if (index >= mSize) { 433 // The array might be slightly bigger than mSize, in which case, indexing won't fail. 434 throw new ArrayIndexOutOfBoundsException(index); 435 } 436 } 437 438 /** 439 * Validate two indices. 440 */ validateIndex(int row, int col)441 private void validateIndex(int row, int col) { 442 validateIndex(row); 443 validateIndex(col); 444 } 445 446 /** 447 * Expand the 2D array. This also extends the free list. 448 */ growMatrix()449 private void growMatrix() { 450 resizeMatrix(mOrder + STEP); 451 } 452 453 /** 454 * Resize the values array to the new dimension. 455 */ resizeMatrix(int newOrder)456 private void resizeMatrix(int newOrder) { 457 if (newOrder % STEP != 0) { 458 throw new IllegalArgumentException("matrix order " + newOrder 459 + " is not a multiple of " + STEP); 460 } 461 int minOrder = Math.min(mOrder, newOrder); 462 463 boolean[] newInUse = ArrayUtils.newUnpaddedBooleanArray(newOrder); 464 System.arraycopy(mInUse, 0, newInUse, 0, minOrder); 465 int[] newMap = ArrayUtils.newUnpaddedIntArray(newOrder); 466 System.arraycopy(mMap, 0, newMap, 0, minOrder); 467 int[] newKeys = ArrayUtils.newUnpaddedIntArray(newOrder); 468 System.arraycopy(mKeys, 0, newKeys, 0, minOrder); 469 470 int[] newValues = ArrayUtils.newUnpaddedIntArray(newOrder * newOrder / PACKING); 471 for (int i = 0; i < minOrder; i++) { 472 int row = mOrder * i / PACKING; 473 int newRow = newOrder * i / PACKING; 474 System.arraycopy(mValues, row, newValues, newRow, minOrder / PACKING); 475 } 476 477 mInUse = newInUse; 478 mMap = newMap; 479 mKeys = newKeys; 480 mValues = newValues; 481 mOrder = newOrder; 482 } 483 484 /** 485 * Find an unused storage index, and return it. Mark it in-use if the {@code acquire} is true. 486 */ nextFree(boolean acquire)487 private int nextFree(boolean acquire) { 488 for (int i = 0; i < mInUse.length; i++) { 489 if (!mInUse[i]) { 490 mInUse[i] = acquire; 491 return i; 492 } 493 } 494 throw new RuntimeException(); 495 } 496 497 /** 498 * Return the index of the key that uses the highest row index in use. This returns 499 * -1 if the matrix is empty. Note that the return is an index suitable for the *At() 500 * methods. It is not the index in the mInUse array. 501 */ lastInuse()502 private int lastInuse() { 503 for (int i = mOrder - 1; i >= 0; i--) { 504 if (mInUse[i]) { 505 for (int j = 0; j < mSize; j++) { 506 if (mMap[j] == i) { 507 return j; 508 } 509 } 510 throw new IndexOutOfBoundsException(); 511 } 512 } 513 return -1; 514 } 515 516 /** 517 * Compress the matrix by packing keys into consecutive indices. If the compression 518 * is sufficient, the mValues array can be shrunk. 519 */ pack()520 private void pack() { 521 if (mSize == 0 || mSize == mOrder) { 522 return; 523 } 524 // dst and src are identify raw (row, col) in mValues. srcIndex is the index (as 525 // in the result of keyAt()) of the key being relocated. 526 for (int dst = nextFree(false); dst < mSize; dst = nextFree(false)) { 527 mInUse[dst] = true; 528 int srcIndex = lastInuse(); 529 int src = mMap[srcIndex]; 530 mInUse[src] = false; 531 mMap[srcIndex] = dst; 532 System.arraycopy(mValues, src * mOrder / PACKING, 533 mValues, dst * mOrder / PACKING, 534 mOrder / PACKING); 535 int srcOffset = (src / PACKING); 536 int srcMask = 1 << (src % PACKING); 537 int dstOffset = (dst / PACKING); 538 int dstMask = 1 << (dst % PACKING); 539 for (int i = 0; i < mOrder; i++) { 540 if ((mValues[srcOffset] & srcMask) == 0) { 541 mValues[dstOffset] &= ~dstMask; 542 } else { 543 mValues[dstOffset] |= dstMask; 544 } 545 srcOffset += mOrder / PACKING; 546 dstOffset += mOrder / PACKING; 547 } 548 } 549 } 550 551 /** 552 * Shrink the matrix, if possible. 553 */ compact()554 public void compact() { 555 pack(); 556 int unused = (mOrder - mSize) / STEP; 557 if (unused > 0) { 558 resizeMatrix(mOrder - (unused * STEP)); 559 } 560 } 561 562 /** 563 * Return a copy of the keys that are in use by the matrix. 564 */ keys()565 public int[] keys() { 566 return Arrays.copyOf(mKeys, mSize); 567 } 568 569 /** 570 * Return the size of the 2D matrix. This is always greater than or equal to size(). 571 * This does not reflect the sizes of the meta-information arrays (such as mKeys). 572 */ capacity()573 public int capacity() { 574 return mOrder; 575 } 576 577 /** 578 * Set capacity to enlarge the size of the 2D matrix. Capacity less than the {@link #capacity()} 579 * is not supported. 580 */ setCapacity(int capacity)581 public void setCapacity(int capacity) { 582 if (capacity <= mOrder) { 583 return; 584 } 585 if (capacity % STEP != 0) { 586 capacity = ((capacity / STEP) + 1) * STEP; 587 } 588 resizeMatrix(capacity); 589 } 590 591 /** 592 * {@inheritDoc} 593 */ 594 @Override hashCode()595 public int hashCode() { 596 int hashCode = mSize; 597 hashCode = 31 * hashCode + Arrays.hashCode(mKeys); 598 hashCode = 31 * hashCode + Arrays.hashCode(mMap); 599 for (int i = 0; i < mSize; i++) { 600 int row = mMap[i]; 601 for (int j = 0; j < mSize; j++) { 602 hashCode = 31 * hashCode + (valueAtInternal(row, mMap[j]) ? 1 : 0); 603 } 604 } 605 return hashCode; 606 } 607 608 /** 609 * {@inheritDoc} 610 */ 611 @Override equals(@ullable Object that)612 public boolean equals(@Nullable Object that) { 613 if (this == that) { 614 return true; 615 } 616 617 if (!(that instanceof WatchedSparseBooleanMatrix)) { 618 return false; 619 } 620 621 WatchedSparseBooleanMatrix other = (WatchedSparseBooleanMatrix) that; 622 if (mSize != other.mSize) { 623 return false; 624 } 625 if (!Arrays.equals(mKeys, other.mKeys)) { 626 // mKeys is zero padded at the end and is sorted, so the arrays can always be 627 // directly compared. 628 return false; 629 } 630 for (int i = 0; i < mSize; i++) { 631 int row = mMap[i]; 632 for (int j = 0; j < mSize; j++) { 633 int col = mMap[j]; 634 if (valueAtInternal(row, col) != other.valueAtInternal(row, col)) { 635 return false; 636 } 637 } 638 } 639 return true; 640 } 641 642 /** 643 * Return the matrix meta information. This is always three strings long. The 644 * strings are indexed by the constants STRING_KEY_INDEX, STRING_MAP_INDEX, and 645 * STRING_INUSE_INDEX. 646 */ 647 @VisibleForTesting(visibility = PRIVATE) matrixToStringMeta()648 @Size(3) String[] matrixToStringMeta() { 649 String[] result = new String[3]; 650 651 StringBuilder k = new StringBuilder(); 652 for (int i = 0; i < mSize; i++) { 653 k.append(mKeys[i]); 654 if (i < mSize - 1) { 655 k.append(" "); 656 } 657 } 658 result[STRING_KEY_INDEX] = k.substring(0); 659 660 StringBuilder m = new StringBuilder(); 661 for (int i = 0; i < mSize; i++) { 662 m.append(mMap[i]); 663 if (i < mSize - 1) { 664 m.append(" "); 665 } 666 } 667 result[STRING_MAP_INDEX] = m.substring(0); 668 669 StringBuilder u = new StringBuilder(); 670 for (int i = 0; i < mOrder; i++) { 671 u.append(mInUse[i] ? "1" : "0"); 672 } 673 result[STRING_INUSE_INDEX] = u.substring(0); 674 return result; 675 } 676 677 /** 678 * Return the matrix as an array of strings. There is one string per row. Each 679 * string has a '1' or a '0' in the proper column. This is the raw data indexed by 680 * row/column disregarding the key map. 681 */ 682 @VisibleForTesting(visibility = PRIVATE) matrixToStringRaw()683 String[] matrixToStringRaw() { 684 String[] result = new String[mOrder]; 685 for (int i = 0; i < mOrder; i++) { 686 StringBuilder line = new StringBuilder(mOrder); 687 for (int j = 0; j < mOrder; j++) { 688 line.append(valueAtInternal(i, j) ? "1" : "0"); 689 } 690 result[i] = line.substring(0); 691 } 692 return result; 693 } 694 695 /** 696 * Return the matrix as an array of strings. There is one string per row. Each 697 * string has a '1' or a '0' in the proper column. This is the cooked data indexed by 698 * keys, in key order. 699 */ 700 @VisibleForTesting(visibility = PRIVATE) matrixToStringCooked()701 String[] matrixToStringCooked() { 702 String[] result = new String[mSize]; 703 for (int i = 0; i < mSize; i++) { 704 int row = mMap[i]; 705 StringBuilder line = new StringBuilder(mSize); 706 for (int j = 0; j < mSize; j++) { 707 line.append(valueAtInternal(row, mMap[j]) ? "1" : "0"); 708 } 709 result[i] = line.substring(0); 710 } 711 return result; 712 } 713 matrixToString(boolean raw)714 public String[] matrixToString(boolean raw) { 715 String[] meta = matrixToStringMeta(); 716 String[] data; 717 if (raw) { 718 data = matrixToStringRaw(); 719 } else { 720 data = matrixToStringCooked(); 721 } 722 String[] result = new String[meta.length + data.length]; 723 System.arraycopy(meta, 0, result, 0, meta.length); 724 System.arraycopy(data, 0, result, meta.length, data.length); 725 return result; 726 } 727 728 /** 729 * {@inheritDoc} 730 * 731 * <p>This implementation creates a string that describes the size of the array. A 732 * string with all the values could easily exceed 1Mb. 733 */ 734 @Override toString()735 public String toString() { 736 return "{" + mSize + "x" + mSize + "}"; 737 } 738 739 // Copied from android.util.ContainerHelpers, which is not visible outside the 740 // android.util package. binarySearch(int[] array, int size, int value)741 private static int binarySearch(int[] array, int size, int value) { 742 int lo = 0; 743 int hi = size - 1; 744 745 while (lo <= hi) { 746 final int mid = (lo + hi) >>> 1; 747 final int midVal = array[mid]; 748 749 if (midVal < value) { 750 lo = mid + 1; 751 } else if (midVal > value) { 752 hi = mid - 1; 753 } else { 754 return mid; // value found 755 } 756 } 757 return ~lo; // value not present 758 } 759 } 760