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