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