| /* |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.utils; |
| |
| import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; |
| |
| import android.annotation.Nullable; |
| import android.annotation.Size; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.util.ArrayUtils; |
| import com.android.internal.util.GrowingArrayUtils; |
| |
| import java.util.Arrays; |
| |
| /** |
| * A {@link WatchedSparseBooleanMatrix} is an compact NxN array of booleans. The rows and |
| * columns of the array are indexed by integers, which need not be contiguous. The matrix |
| * is square and the row and column indices are identical. This matrix is intended to be |
| * very memory efficient. |
| * |
| * The matrix contains a map from indices to columns: this map requires 2*N integers. The |
| * boolean array is bit-packed and requires N*N/8 bytes. The memory required for an |
| * order-N matrix is therefore 2*N*4 + N*N bytes. |
| * |
| * See {@link SparseBooleanArray} for a discussion of sparse arrays. |
| */ |
| public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappable { |
| |
| /** |
| * The matrix is implemented through four arrays. First, the matrix of booleans is |
| * stored in a two-dimensional {@code mValues} array of bit-packed booleans. |
| * {@code mValues} is always of size {@code mOrder * mOrder / 8}. The factor of 8 is |
| * present because there are 8 bits in a byte. Elements of {@code mValues} are |
| * addressed with arithmetic: the element {@code {row, col}} is bit {@code col % 8} in |
| * byte * {@code (row * mOrder + col) / 8}. The term "storage index" applies to |
| * {@code mValues}. A storage index designates a row (column) in the underlying |
| * storage. This is not the same as the row seen by client code. |
| * |
| * Client code addresses the matrix through indices. These are integers that need not |
| * be contiguous. Client indices are mapped to storage indices through two linear |
| * integer arrays. {@code mKeys} is a sorted list of client indices. |
| * {@code mIndices} is a parallel array that contains storage indices. The storage |
| * index of a client index {@code k} is {@code mIndices[i]}, where |
| * {@code mKeys[i] == k}. |
| * |
| * A final array, {@code mInUse} records if storage indices are free or in use. This |
| * array is of size {@code mOrder}. A client index is deleted by removing it from |
| * {@code mKeys} and {@code mIndices} and then setting the original storage index |
| * false in {@code mInUse}. |
| * |
| * Some notes: |
| * <ul> |
| * <li> The matrix does not automatically shrink but there is a compress() method that |
| * will recover unused space. |
| * <li> Equality is a very, very expensive operation because it must walk the matrices |
| * beimg compared element by element. |
| * </ul> |
| */ |
| |
| /** |
| * mOrder is always a multiple of this value. A minimal matrix therefore holds 2^12 |
| * values and requires 1024 bytes. The value is visible for testing. |
| */ |
| @VisibleForTesting(visibility = PRIVATE) |
| static final int STEP = 64; |
| |
| /** |
| * The number of bits in the mValues array element. |
| */ |
| private static final int PACKING = 32; |
| |
| /** |
| * Constants that index into the string array returned by matrixToString. The primary |
| * consumer is test code. |
| */ |
| static final int STRING_KEY_INDEX = 0; |
| static final int STRING_MAP_INDEX = 1; |
| static final int STRING_INUSE_INDEX = 2; |
| |
| /** |
| * The order of the matrix storage, including any padding. The matrix is always |
| * square. mOrder is always greater than or equal to mSize. |
| */ |
| private int mOrder; |
| |
| /** |
| * The number of client keys. This is always less than or equal to mOrder. It is the |
| * order of the matrix as seen by the client. |
| */ |
| private int mSize; |
| |
| /** |
| * The in-use list. |
| */ |
| private boolean[] mInUse; |
| |
| /** |
| * The array of client keys (indices), in sorted order. |
| */ |
| private int[] mKeys; |
| |
| /** |
| * The mapping from a client key to an storage index. If client key K is at index N |
| * in mKeys, then the storage index for K is at mMap[N]. |
| */ |
| private int[] mMap; |
| |
| /** |
| * The boolean array. This array is always {@code mOrder x mOrder} in size. |
| */ |
| private int[] mValues; |
| |
| /** |
| * A convenience function called when the elements are added to or removed from the storage. |
| * The watchable is always {@link this}. |
| */ |
| private void onChanged() { |
| dispatchChange(this); |
| } |
| |
| /** |
| * Creates a new WatchedSparseBooleanMatrix containing no mappings. |
| */ |
| public WatchedSparseBooleanMatrix() { |
| this(STEP); |
| } |
| |
| /** |
| * Creates a new SparseBooleanMatrix containing no mappings that will not require any |
| * additional memory allocation to store the specified number of mappings. The |
| * capacity is always rounded up to a non-zero multiple of STEP. |
| */ |
| public WatchedSparseBooleanMatrix(int initialCapacity) { |
| mOrder = initialCapacity; |
| if (mOrder < STEP) { |
| mOrder = STEP; |
| } |
| if (mOrder % STEP != 0) { |
| mOrder = ((initialCapacity / STEP) + 1) * STEP; |
| } |
| if (mOrder < STEP || (mOrder % STEP != 0)) { |
| throw new RuntimeException("mOrder is " + mOrder + " initCap is " + initialCapacity); |
| } |
| |
| mInUse = ArrayUtils.newUnpaddedBooleanArray(mOrder); |
| mKeys = ArrayUtils.newUnpaddedIntArray(mOrder); |
| mMap = ArrayUtils.newUnpaddedIntArray(mOrder); |
| mValues = ArrayUtils.newUnpaddedIntArray(mOrder * mOrder / PACKING); |
| mSize = 0; |
| } |
| |
| /** |
| * A copy constructor that can be used for snapshotting. |
| */ |
| private WatchedSparseBooleanMatrix(WatchedSparseBooleanMatrix r) { |
| mOrder = r.mOrder; |
| mSize = r.mSize; |
| mKeys = r.mKeys.clone(); |
| mMap = r.mMap.clone(); |
| mInUse = r.mInUse.clone(); |
| mValues = r.mValues.clone(); |
| } |
| |
| /** |
| * Return a copy of this object. |
| */ |
| public WatchedSparseBooleanMatrix snapshot() { |
| return new WatchedSparseBooleanMatrix(this); |
| } |
| |
| /** |
| * Gets the boolean mapped from the specified key, or <code>false</code> |
| * if no such mapping has been made. |
| */ |
| public boolean get(int row, int col) { |
| return get(row, col, false); |
| } |
| |
| /** |
| * Gets the boolean mapped from the specified key, or the specified value |
| * if no such mapping has been made. |
| */ |
| public boolean get(int row, int col, boolean valueIfKeyNotFound) { |
| int r = indexOfKey(row, false); |
| int c = indexOfKey(col, false); |
| if (r >= 0 && c >= 0) { |
| return valueAt(r, c); |
| } else { |
| return valueIfKeyNotFound; |
| } |
| } |
| |
| /** |
| * Adds a mapping from the specified keys to the specified value, replacing the |
| * previous mapping from the specified keys if there was one. |
| */ |
| public void put(int row, int col, boolean value) { |
| int r = indexOfKey(row); |
| int c = indexOfKey(col); |
| if (r < 0 || c < 0) { |
| // One or both of the keys has not be installed yet. Install them now. |
| // Installing either key may shift the other key. The safest course is to |
| // install the keys that are not present and then recompute both indices. |
| if (r < 0) { |
| r = indexOfKey(row, true); |
| } |
| if (c < 0) { |
| c = indexOfKey(col, true); |
| } |
| r = indexOfKey(row); |
| c = indexOfKey(col); |
| } |
| if (r >= 0 && c >= 0) { |
| setValueAt(r, c, value); |
| // setValueAt() will call onChanged(). |
| } else { |
| throw new RuntimeException("matrix overflow"); |
| } |
| } |
| |
| /** |
| * Removes the mapping from the specified key, if there was any. Note that deletion |
| * applies to a single index, not to an element. The matrix never shrinks but the |
| * space will be reused the next time an index is added. |
| */ |
| public void deleteKey(int key) { |
| int i = indexOfKey(key, false); |
| if (i >= 0) { |
| removeAt(i); |
| } |
| } |
| |
| /** |
| * Removes the mapping at the specified index. The matrix does not shrink. This |
| * throws ArrayIndexOutOfBounds if the index out outside the range {@code 0..size()-1}. |
| */ |
| public void removeAt(int index) { |
| validateIndex(index); |
| mInUse[mMap[index]] = false; |
| // Remove the specified index and ensure that unused words in mKeys and mMap are |
| // always zero, to simplify the equality function. |
| System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1)); |
| mKeys[mSize - 1] = 0; |
| System.arraycopy(mMap, index + 1, mMap, index, mSize - (index + 1)); |
| mMap[mSize - 1] = 0; |
| mSize--; |
| onChanged(); |
| } |
| |
| /** |
| * Returns the number of key-value mappings that this WatchedSparseBooleanMatrix |
| * currently stores. |
| */ |
| public int size() { |
| return mSize; |
| } |
| |
| /** |
| * Removes all key-value mappings from this WatchedSparseBooleanMatrix. |
| */ |
| public void clear() { |
| mSize = 0; |
| Arrays.fill(mInUse, false); |
| onChanged(); |
| } |
| |
| /** |
| * Given an index in the range <code>0...size()-1</code>, returns the key from the |
| * <code>index</code>th key-value mapping that this WatchedSparseBooleanMatrix stores. |
| * |
| * <p>The keys corresponding to indices in ascending order are guaranteed to be in |
| * ascending order, e.g., <code>keyAt(0)</code> will return the smallest key and |
| * <code>keyAt(size()-1)</code> will return the largest key.</p> |
| * |
| * <p>{@link ArrayIndexOutOfBoundsException} is thrown for indices outside of the |
| * range <code>0...size()-1</code></p> |
| */ |
| public int keyAt(int index) { |
| validateIndex(index); |
| return mKeys[index]; |
| } |
| |
| /** |
| * An internal method to fetch the boolean value given the mValues row and column |
| * indices. These are not the indices used by the *At() methods. |
| */ |
| private boolean valueAtInternal(int row, int col) { |
| int element = row * mOrder + col; |
| int offset = element / PACKING; |
| int mask = 1 << (element % PACKING); |
| return (mValues[offset] & mask) != 0; |
| } |
| |
| /** |
| * Given a row and column, each in the range <code>0...size()-1</code>, returns the |
| * value from the <code>index</code>th key-value mapping that this WatchedSparseBooleanMatrix |
| * stores. |
| */ |
| public boolean valueAt(int rowIndex, int colIndex) { |
| validateIndex(rowIndex, colIndex); |
| int r = mMap[rowIndex]; |
| int c = mMap[colIndex]; |
| return valueAtInternal(r, c); |
| } |
| |
| /** |
| * An internal method to set the boolean value given the mValues row and column |
| * indices. These are not the indices used by the *At() methods. |
| */ |
| private void setValueAtInternal(int row, int col, boolean value) { |
| int element = row * mOrder + col; |
| int offset = element / PACKING; |
| int mask = 1 << (element % PACKING); |
| if (value) { |
| mValues[offset] |= mask; |
| } else { |
| mValues[offset] &= ~mask; |
| } |
| } |
| |
| /** |
| * Directly set the value at a particular index. |
| */ |
| public void setValueAt(int rowIndex, int colIndex, boolean value) { |
| validateIndex(rowIndex, colIndex); |
| int r = mMap[rowIndex]; |
| int c = mMap[colIndex]; |
| setValueAtInternal(r, c, value); |
| onChanged(); |
| } |
| |
| /** |
| * Returns the index for which {@link #keyAt} would return the specified key, or a |
| * negative number if the specified key is not mapped. |
| */ |
| public int indexOfKey(int key) { |
| return binarySearch(mKeys, mSize, key); |
| } |
| |
| /** |
| * Return true if the matrix knows the user index. |
| */ |
| public boolean contains(int key) { |
| return indexOfKey(key) >= 0; |
| } |
| |
| /** |
| * Fetch the index of a key. If the key does not exist and grow is true, then add the |
| * key. If the does not exist and grow is false, return -1. |
| */ |
| private int indexOfKey(int key, boolean grow) { |
| int i = binarySearch(mKeys, mSize, key); |
| if (i < 0 && grow) { |
| i = ~i; |
| if (mSize >= mOrder) { |
| // Preemptively grow the matrix, which also grows the free list. |
| growMatrix(); |
| } |
| int newIndex = nextFree(); |
| mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key); |
| mMap = GrowingArrayUtils.insert(mMap, mSize, i, newIndex); |
| mSize++; |
| |
| // Initialize the row and column corresponding to the new index. |
| int valueRow = mOrder / PACKING; |
| int offset = newIndex / PACKING; |
| int mask = ~(1 << (newIndex % PACKING)); |
| Arrays.fill(mValues, newIndex * valueRow, (newIndex + 1) * valueRow, 0); |
| for (int n = 0; n < mSize; n++) { |
| mValues[n * valueRow + offset] &= mask; |
| } |
| // Do not report onChanged() from this private method. onChanged() is the |
| // responsibility of public methods that call this one. |
| } |
| return i; |
| } |
| |
| /** |
| * Validate the index. This can throw. |
| */ |
| private void validateIndex(int index) { |
| if (index >= mSize) { |
| // The array might be slightly bigger than mSize, in which case, indexing won't fail. |
| throw new ArrayIndexOutOfBoundsException(index); |
| } |
| } |
| |
| /** |
| * Validate two indices. |
| */ |
| private void validateIndex(int row, int col) { |
| validateIndex(row); |
| validateIndex(col); |
| } |
| |
| /** |
| * Expand the 2D array. This also extends the free list. |
| */ |
| private void growMatrix() { |
| resizeMatrix(mOrder + STEP); |
| } |
| |
| /** |
| * Resize the values array to the new dimension. |
| */ |
| private void resizeMatrix(int newOrder) { |
| if (newOrder % STEP != 0) { |
| throw new IllegalArgumentException("matrix order " + newOrder |
| + " is not a multiple of " + STEP); |
| } |
| int minOrder = Math.min(mOrder, newOrder); |
| |
| boolean[] newInUse = ArrayUtils.newUnpaddedBooleanArray(newOrder); |
| System.arraycopy(mInUse, 0, newInUse, 0, minOrder); |
| int[] newMap = ArrayUtils.newUnpaddedIntArray(newOrder); |
| System.arraycopy(mMap, 0, newMap, 0, minOrder); |
| int[] newKeys = ArrayUtils.newUnpaddedIntArray(newOrder); |
| System.arraycopy(mKeys, 0, newKeys, 0, minOrder); |
| |
| int[] newValues = ArrayUtils.newUnpaddedIntArray(newOrder * newOrder / PACKING); |
| for (int i = 0; i < minOrder; i++) { |
| int row = mOrder * i / PACKING; |
| int newRow = newOrder * i / PACKING; |
| System.arraycopy(mValues, row, newValues, newRow, minOrder / PACKING); |
| } |
| |
| mInUse = newInUse; |
| mMap = newMap; |
| mKeys = newKeys; |
| mValues = newValues; |
| mOrder = newOrder; |
| } |
| |
| /** |
| * Find an unused storage index, mark it in-use, and return it. |
| */ |
| private int nextFree() { |
| for (int i = 0; i < mInUse.length; i++) { |
| if (!mInUse[i]) { |
| mInUse[i] = true; |
| return i; |
| } |
| } |
| throw new RuntimeException(); |
| } |
| |
| /** |
| * Return the index of the key that uses the highest row index in use. This returns |
| * -1 if the matrix is empty. Note that the return is an index suitable for the *At() |
| * methods. It is not the index in the mInUse array. |
| */ |
| private int lastInuse() { |
| for (int i = mOrder - 1; i >= 0; i--) { |
| if (mInUse[i]) { |
| for (int j = 0; j < mSize; j++) { |
| if (mMap[j] == i) { |
| return j; |
| } |
| } |
| throw new IndexOutOfBoundsException(); |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * Compress the matrix by packing keys into consecutive indices. If the compression |
| * is sufficient, the mValues array can be shrunk. |
| */ |
| private void pack() { |
| if (mSize == 0 || mSize == mOrder) { |
| return; |
| } |
| // dst and src are identify raw (row, col) in mValues. srcIndex is the index (as |
| // in the result of keyAt()) of the key being relocated. |
| for (int dst = nextFree(); dst < mSize; dst = nextFree()) { |
| int srcIndex = lastInuse(); |
| int src = mMap[srcIndex]; |
| mInUse[src] = false; |
| mMap[srcIndex] = dst; |
| System.arraycopy(mValues, src * mOrder / PACKING, |
| mValues, dst * mOrder / PACKING, |
| mOrder / PACKING); |
| int srcOffset = (src / PACKING); |
| int srcMask = 1 << (src % PACKING); |
| int dstOffset = (dst / PACKING); |
| int dstMask = 1 << (dst % PACKING); |
| for (int i = 0; i < mOrder; i++) { |
| if ((mValues[srcOffset] & srcMask) == 0) { |
| mValues[dstOffset] &= ~dstMask; |
| } else { |
| mValues[dstOffset] |= dstMask; |
| } |
| srcOffset += mOrder / PACKING; |
| dstOffset += mOrder / PACKING; |
| } |
| } |
| } |
| |
| /** |
| * Shrink the matrix, if possible. |
| */ |
| public void compact() { |
| pack(); |
| int unused = (mOrder - mSize) / STEP; |
| if (unused > 0) { |
| resizeMatrix(mOrder - (unused * STEP)); |
| } |
| } |
| |
| /** |
| * Return a copy of the keys that are in use by the matrix. |
| */ |
| public int[] keys() { |
| return Arrays.copyOf(mKeys, mSize); |
| } |
| |
| /** |
| * Return the size of the 2D matrix. This is always greater than or equal to size(). |
| * This does not reflect the sizes of the meta-information arrays (such as mKeys). |
| */ |
| public int capacity() { |
| return mOrder; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public int hashCode() { |
| int hashCode = mSize; |
| hashCode = 31 * hashCode + Arrays.hashCode(mKeys); |
| hashCode = 31 * hashCode + Arrays.hashCode(mMap); |
| for (int i = 0; i < mSize; i++) { |
| int row = mMap[i]; |
| for (int j = 0; j < mSize; j++) { |
| hashCode = 31 * hashCode + (valueAtInternal(row, mMap[j]) ? 1 : 0); |
| } |
| } |
| return hashCode; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean equals(@Nullable Object that) { |
| if (this == that) { |
| return true; |
| } |
| |
| if (!(that instanceof WatchedSparseBooleanMatrix)) { |
| return false; |
| } |
| |
| WatchedSparseBooleanMatrix other = (WatchedSparseBooleanMatrix) that; |
| if (mSize != other.mSize) { |
| return false; |
| } |
| if (!Arrays.equals(mKeys, other.mKeys)) { |
| // mKeys is zero padded at the end and is sorted, so the arrays can always be |
| // directly compared. |
| return false; |
| } |
| for (int i = 0; i < mSize; i++) { |
| int row = mMap[i]; |
| for (int j = 0; j < mSize; j++) { |
| int col = mMap[j]; |
| if (valueAtInternal(row, col) != other.valueAtInternal(row, col)) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Return the matrix meta information. This is always three strings long. The |
| * strings are indexed by the constants STRING_KEY_INDEX, STRING_MAP_INDEX, and |
| * STRING_INUSE_INDEX. |
| */ |
| @VisibleForTesting(visibility = PRIVATE) |
| @Size(3) String[] matrixToStringMeta() { |
| String[] result = new String[3]; |
| |
| StringBuilder k = new StringBuilder(); |
| for (int i = 0; i < mSize; i++) { |
| k.append(mKeys[i]); |
| if (i < mSize - 1) { |
| k.append(" "); |
| } |
| } |
| result[STRING_KEY_INDEX] = k.substring(0); |
| |
| StringBuilder m = new StringBuilder(); |
| for (int i = 0; i < mSize; i++) { |
| m.append(mMap[i]); |
| if (i < mSize - 1) { |
| m.append(" "); |
| } |
| } |
| result[STRING_MAP_INDEX] = m.substring(0); |
| |
| StringBuilder u = new StringBuilder(); |
| for (int i = 0; i < mOrder; i++) { |
| u.append(mInUse[i] ? "1" : "0"); |
| } |
| result[STRING_INUSE_INDEX] = u.substring(0); |
| return result; |
| } |
| |
| /** |
| * Return the matrix as an array of strings. There is one string per row. Each |
| * string has a '1' or a '0' in the proper column. This is the raw data indexed by |
| * row/column disregarding the key map. |
| */ |
| @VisibleForTesting(visibility = PRIVATE) |
| String[] matrixToStringRaw() { |
| String[] result = new String[mOrder]; |
| for (int i = 0; i < mOrder; i++) { |
| StringBuilder line = new StringBuilder(mOrder); |
| for (int j = 0; j < mOrder; j++) { |
| line.append(valueAtInternal(i, j) ? "1" : "0"); |
| } |
| result[i] = line.substring(0); |
| } |
| return result; |
| } |
| |
| /** |
| * Return the matrix as an array of strings. There is one string per row. Each |
| * string has a '1' or a '0' in the proper column. This is the cooked data indexed by |
| * keys, in key order. |
| */ |
| @VisibleForTesting(visibility = PRIVATE) |
| String[] matrixToStringCooked() { |
| String[] result = new String[mSize]; |
| for (int i = 0; i < mSize; i++) { |
| int row = mMap[i]; |
| StringBuilder line = new StringBuilder(mSize); |
| for (int j = 0; j < mSize; j++) { |
| line.append(valueAtInternal(row, mMap[j]) ? "1" : "0"); |
| } |
| result[i] = line.substring(0); |
| } |
| return result; |
| } |
| |
| public String[] matrixToString(boolean raw) { |
| String[] meta = matrixToStringMeta(); |
| String[] data; |
| if (raw) { |
| data = matrixToStringRaw(); |
| } else { |
| data = matrixToStringCooked(); |
| } |
| String[] result = new String[meta.length + data.length]; |
| System.arraycopy(meta, 0, result, 0, meta.length); |
| System.arraycopy(data, 0, result, meta.length, data.length); |
| return result; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * <p>This implementation creates a string that describes the size of the array. A |
| * string with all the values could easily exceed 1Mb. |
| */ |
| @Override |
| public String toString() { |
| return "{" + mSize + "x" + mSize + "}"; |
| } |
| |
| // Copied from android.util.ContainerHelpers, which is not visible outside the |
| // android.util package. |
| private static int binarySearch(int[] array, int size, int value) { |
| int lo = 0; |
| int hi = size - 1; |
| |
| while (lo <= hi) { |
| final int mid = (lo + hi) >>> 1; |
| final int midVal = array[mid]; |
| |
| if (midVal < value) { |
| lo = mid + 1; |
| } else if (midVal > value) { |
| hi = mid - 1; |
| } else { |
| return mid; // value found |
| } |
| } |
| return ~lo; // value not present |
| } |
| } |