• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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