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