1 /* 2 * Copyright (C) 2023 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.car.internal.util; 18 19 import android.util.ArraySet; 20 import android.util.LongSparseArray; 21 import android.util.SparseArray; 22 23 import com.android.internal.util.Preconditions; 24 25 /** 26 * SparseArray mapping two integer keys to an Object. It encodes the two {@code int}s into a {@code 27 * long} with the first key stored in the most significant 32 bits and the second key stored in the 28 * least significant 32 bits. This class is wrapper for {@link LongSparseArray} and handles encoding 29 * and decoding the keys. 30 * 31 * @see LongSparseArray 32 * @param <E> value to be stored 33 */ 34 public class PairSparseArray<E> implements Cloneable { 35 /** Bitmask for casting an {@code int} into a {@code long} without sign extension. */ 36 private static final long LEAST_SIGNIFICANT_BITMASK = 0xffffffffL; 37 private static final int INITIAL_CAPACITY = 10; 38 39 /** 40 * Underlying long->Object map data structure. 41 */ 42 private final LongSparseArray<E> mValues; 43 /** 44 * First key to second keys mapping to allow easier operation applied to all the entries with 45 * the first key. 46 */ 47 private final SparseArray<ArraySet<Integer>> mSecondKeysByFirstKey; 48 49 /** Creates a new PairSparseArray with initial capacity of {@link #INITIAL_CAPACITY}. */ PairSparseArray()50 public PairSparseArray() { 51 this(INITIAL_CAPACITY); 52 } 53 54 /** Creates a new PairSparseArray. */ PairSparseArray(int initialCapacity)55 public PairSparseArray(int initialCapacity) { 56 mValues = new LongSparseArray<>(initialCapacity); 57 mSecondKeysByFirstKey = new SparseArray<>(); 58 } 59 PairSparseArray(PairSparseArray<E> other)60 private PairSparseArray(PairSparseArray<E> other) { 61 mValues = other.mValues.clone(); 62 mSecondKeysByFirstKey = other.mSecondKeysByFirstKey.clone(); 63 } 64 65 /** Creates a clone. */ 66 @Override clone()67 public PairSparseArray<E> clone() { 68 return new PairSparseArray<E>(this); 69 } 70 71 /** 72 * Gets all the second keys for the first key. 73 */ getSecondKeysForFirstKey(int firstKey)74 public ArraySet<Integer> getSecondKeysForFirstKey(int firstKey) { 75 if (!mSecondKeysByFirstKey.contains(firstKey)) { 76 return new ArraySet<>(); 77 } 78 return new ArraySet<>(mSecondKeysByFirstKey.get(firstKey)); 79 } 80 81 /** 82 * Gets all the first keys. 83 */ getFirstKeys()84 public ArraySet<Integer> getFirstKeys() { 85 ArraySet<Integer> firstKeys = new ArraySet<>(); 86 for (int i = 0; i < mSecondKeysByFirstKey.size(); i++) { 87 firstKeys.add(mSecondKeysByFirstKey.keyAt(i)); 88 } 89 return new ArraySet<>(firstKeys); 90 } 91 92 /** 93 * Puts the keys and value into the array, optimizing for the case where 94 * the encoded key is greater than all existing keys in the array. 95 */ append(int firstKey, int secondKey, E value)96 public void append(int firstKey, int secondKey, E value) { 97 Preconditions.checkArgument(value != null, "Value must not be null"); 98 long key = encodeKeyPair(firstKey, secondKey); 99 mValues.append(key, value); 100 addSecondKeyByFirstKey(firstKey, secondKey); 101 } 102 103 /** 104 * Removes all key-value mappings from this array. 105 */ clear()106 public void clear() { 107 mValues.clear(); 108 mSecondKeysByFirstKey.clear(); 109 } 110 111 /** 112 * Returns true if the key pair exists in the array. This is equivalent to 113 * {@link #indexOfKeyPair(int, int)} >= 0. 114 * 115 * @return {@code true} if the keys are mapped 116 */ contains(int firstKey, int secondKey)117 public boolean contains(int firstKey, int secondKey) { 118 return indexOfKeyPair(firstKey, secondKey) >= 0; 119 } 120 121 /** 122 * Removes the mapping from the specified keys, if there was any. 123 */ delete(int firstKey, int secondKey)124 public void delete(int firstKey, int secondKey) { 125 long key = encodeKeyPair(firstKey, secondKey); 126 mValues.delete(key); 127 removeSecondKeyByFirstKey(firstKey, secondKey); 128 } 129 130 /** 131 * Gets the Object mapped from the specified key, or {@code null} 132 * if no such mapping has been made. 133 * 134 * @see #get(int, int, Object) 135 */ get(int firstKey, int secondKey)136 public E get(int firstKey, int secondKey) { 137 return get(firstKey, secondKey, null); 138 } 139 140 /** 141 * Gets the Object mapped from the specified key, or the specified Object 142 * if no such mapping has been made. 143 * 144 * @param firstKey the integer key stored in the most significant bits 145 * @param secondKey the integer key stored in the least significant bits 146 * @param valueIfKeyPairNotFound the value to return if {@code firstKey} and {@code secondKey} 147 * have not been mapped. 148 * 149 * @return the value mapped to {@code firstKey} and {@code secondKey}, or {@code 150 * valueIfKeyPairNotFound} if keys have not been mapped. 151 */ get(int firstKey, int secondKey, E valueIfKeyPairNotFound)152 public E get(int firstKey, int secondKey, E valueIfKeyPairNotFound) { 153 int index = indexOfKeyPair(firstKey, secondKey); 154 if (index < 0) { 155 return valueIfKeyPairNotFound; 156 } 157 return valueAt(index); 158 } 159 160 /** 161 * Returns the index for which {@link #keyPairAt} would return the 162 * specified keys, or a negative number if the specified 163 * keys are not mapped. 164 */ indexOfKeyPair(int firstKey, int secondKey)165 public int indexOfKeyPair(int firstKey, int secondKey) { 166 long key = encodeKeyPair(firstKey, secondKey); 167 return mValues.indexOfKey(key); 168 } 169 170 /** 171 * Returns an index for which {@link #valueAt} would return the 172 * specified key, or a negative number if no keys map to the 173 * specified value. 174 * 175 * @see LongSparseArray#indexOfValue(Object) 176 */ indexOfValue(E value)177 public int indexOfValue(E value) { 178 Preconditions.checkArgument(value != null, "Value must not be null"); 179 return mValues.indexOfValue(value); 180 } 181 182 /** 183 * Given an index in the range <code>0...size()-1</code>, returns 184 * the key pair from the <code>index</code>th key-value mapping that this 185 * PairSparseArray stores. 186 * 187 * @return int array of size 2 with the first and second key at indices 0 and 1 respectively. 188 * @see LongSparseArray#keyAt(int) 189 */ keyPairAt(int index)190 public int[] keyPairAt(int index) { 191 return decodeKeyPair(mValues.keyAt(index)); 192 } 193 194 /** 195 * Adds a mapping from the specified keys to the specified value, 196 * replacing the previous mapping if there was one. 197 */ put(int firstKey, int secondKey, E value)198 public void put(int firstKey, int secondKey, E value) { 199 Preconditions.checkArgument(value != null, "Value must not be null"); 200 long key = encodeKeyPair(firstKey, secondKey); 201 mValues.put(key, value); 202 addSecondKeyByFirstKey(firstKey, secondKey); 203 } 204 205 /** 206 * Alias for {@link #delete(int, int)} 207 */ remove(int firstKey, int secondKey)208 public void remove(int firstKey, int secondKey) { 209 delete(firstKey, secondKey); 210 } 211 212 /** 213 * Removes the mapping at the specified index. 214 * 215 * @see LongSparseArray#removeAt(int) 216 */ removeAt(int index)217 public void removeAt(int index) { 218 int[] keyPair = keyPairAt(index); 219 removeSecondKeyByFirstKey(keyPair[0], keyPair[1]); 220 mValues.removeAt(index); 221 } 222 223 /** 224 * Given an index in the range <code>0...size()-1</code>, sets a new 225 * value for the <code>index</code>th key-value mapping that this 226 * PairSparseArray stores. 227 * 228 * @see LongSparseArray#setValueAt(int, Object) 229 */ setValueAt(int index, E value)230 public void setValueAt(int index, E value) { 231 Preconditions.checkArgument(value != null, "Value must not be null"); 232 mValues.setValueAt(index, value); 233 } 234 235 /** Returns the number of key-value mappings that this PairSparseArray currently stores. */ size()236 public int size() { 237 return mValues.size(); 238 } 239 240 /** 241 * {@inheritDoc} 242 * 243 * <p>This implementation composes a string by iterating over its mappings. 244 */ 245 @Override toString()246 public String toString() { 247 if (size() <= 0) { 248 return "{}"; 249 } 250 251 // 34 is an overestimate of the number of characters 252 // to expect per mapping to avoid resizing the buffer. 253 StringBuilder buffer = new StringBuilder(size() * 34); 254 buffer.append('{'); 255 for (int i = 0; i < size(); i++) { 256 if (i > 0) { 257 buffer.append(", "); 258 } 259 buffer.append('['); 260 int[] keyPair = keyPairAt(i); 261 buffer.append(keyPair[0]); 262 buffer.append(", "); 263 buffer.append(keyPair[1]); 264 buffer.append(']'); 265 buffer.append('='); 266 Object value = valueAt(i); 267 if (value != this) { 268 buffer.append(value); 269 } else { 270 buffer.append("(this Map)"); 271 } 272 } 273 buffer.append('}'); 274 return buffer.toString(); 275 } 276 277 /** 278 * Given an index in the range <code>0...size()-1</code>, returns 279 * the value from the <code>index</code>th key-value mapping that this 280 * PairSparseArray stores. 281 * 282 * @see LongSparseArray#valueAt(int) 283 */ valueAt(int index)284 public E valueAt(int index) { 285 return mValues.valueAt(index); 286 } 287 288 /** 289 * Encodes the given pair of integer keys into a combined long with 290 * {@code firstKey} stored in the most significant 32 bits and 291 * {@code secondKey} stored in the least significant 32 bits. 292 */ encodeKeyPair(int firstKey, int secondKey)293 private long encodeKeyPair(int firstKey, int secondKey) { 294 return (((long) firstKey) << 32) | (secondKey & LEAST_SIGNIFICANT_BITMASK); 295 } 296 297 /** 298 * Decode the {@code long} key used for {@link #mValues} into an 299 * integer array of size two, with the first key extracted from 300 * the most significant 32 bits and the second key extracted from 301 * the least significant 32 bits. 302 */ decodeKeyPair(long key)303 private int[] decodeKeyPair(long key) { 304 int firstKey = (int) (key >> 32); 305 int secondKey = (int) key; 306 return new int[] {firstKey, secondKey}; 307 } 308 addSecondKeyByFirstKey(int firstKey, int secondKey)309 private void addSecondKeyByFirstKey(int firstKey, int secondKey) { 310 if (!mSecondKeysByFirstKey.contains(firstKey)) { 311 mSecondKeysByFirstKey.put(firstKey, new ArraySet<>()); 312 } 313 mSecondKeysByFirstKey.get(firstKey).add(secondKey); 314 } 315 removeSecondKeyByFirstKey(int firstKey, int secondKey)316 private void removeSecondKeyByFirstKey(int firstKey, int secondKey) { 317 int index = mSecondKeysByFirstKey.indexOfKey(firstKey); 318 if (index < 0) { 319 // First key not found, do nothing. 320 return; 321 } 322 mSecondKeysByFirstKey.valueAt(index).remove(secondKey); 323 if (mSecondKeysByFirstKey.valueAt(index).isEmpty()) { 324 mSecondKeysByFirstKey.removeAt(index); 325 } 326 } 327 } 328