blob: c43e7f9babe6215013d8adf27f268955eafac55d [file] [log] [blame]
Aurimas Liutikas88c7ff12023-08-10 12:42:26 -07001/*
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
17package com.android.server.utils;
18
19import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
20
21import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.annotation.Size;
24
25import com.android.internal.annotations.VisibleForTesting;
26import com.android.internal.util.ArrayUtils;
27import com.android.internal.util.GrowingArrayUtils;
28
29import 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 */
43public 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 */
133 private void onChanged() {
134 dispatchChange(this);
135 }
136
137 /**
138 * Creates a new WatchedSparseBooleanMatrix containing no mappings.
139 */
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 */
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 */
171 private WatchedSparseBooleanMatrix(WatchedSparseBooleanMatrix r) {
172 copyFrom(r);
173 }
174
175 /**
176 * Copy from src to this.
177 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
304 public int size() {
305 return mSize;
306 }
307
308 /**
309 * Removes all key-value mappings from this WatchedSparseBooleanMatrix.
310 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
449 private void growMatrix() {
450 resizeMatrix(mOrder + STEP);
451 }
452
453 /**
454 * Resize the values array to the new dimension.
455 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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
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
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)
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)
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)
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
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
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.
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}