1/*
2 * Copyright (C) 2009 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
17package android.util;
18
19import com.android.internal.util.ArrayUtils;
20
21/**
22 * SparseArrays map longs to Objects.  Unlike a normal array of Objects,
23 * there can be gaps in the indices.  It is intended to be more efficient
24 * than using a HashMap to map Longs to Objects.
25 *
26 * @hide
27 */
28public class LongSparseArray<E> {
29    private static final Object DELETED = new Object();
30    private boolean mGarbage = false;
31
32    /**
33     * Creates a new SparseArray containing no mappings.
34     */
35    public LongSparseArray() {
36        this(10);
37    }
38
39    /**
40     * Creates a new SparseArray containing no mappings that will not
41     * require any additional memory allocation to store the specified
42     * number of mappings.
43     */
44    public LongSparseArray(int initialCapacity) {
45        initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
46
47        mKeys = new long[initialCapacity];
48        mValues = new Object[initialCapacity];
49        mSize = 0;
50    }
51
52    /**
53     * @return A copy of all keys contained in the sparse array.
54     */
55    public long[] getKeys() {
56        int length = mKeys.length;
57        long[] result = new long[length];
58        System.arraycopy(mKeys, 0, result, 0, length);
59        return result;
60    }
61
62    /**
63     * Sets all supplied keys to the given unique value.
64     * @param keys Keys to set
65     * @param uniqueValue Value to set all supplied keys to
66     */
67    public void setValues(long[] keys, E uniqueValue) {
68        int length = keys.length;
69        for (int i = 0; i < length; i++) {
70            put(keys[i], uniqueValue);
71        }
72    }
73
74    /**
75     * Gets the Object mapped from the specified key, or <code>null</code>
76     * if no such mapping has been made.
77     */
78    public E get(long key) {
79        return get(key, null);
80    }
81
82    /**
83     * Gets the Object mapped from the specified key, or the specified Object
84     * if no such mapping has been made.
85     */
86    public E get(long key, E valueIfKeyNotFound) {
87        int i = binarySearch(mKeys, 0, mSize, key);
88
89        if (i < 0 || mValues[i] == DELETED) {
90            return valueIfKeyNotFound;
91        } else {
92            return (E) mValues[i];
93        }
94    }
95
96    /**
97     * Removes the mapping from the specified key, if there was any.
98     */
99    public void delete(long key) {
100        int i = binarySearch(mKeys, 0, mSize, key);
101
102        if (i >= 0) {
103            if (mValues[i] != DELETED) {
104                mValues[i] = DELETED;
105                mGarbage = true;
106            }
107        }
108    }
109
110    /**
111     * Alias for {@link #delete(long)}.
112     */
113    public void remove(long key) {
114        delete(key);
115    }
116
117    private void gc() {
118        // Log.e("SparseArray", "gc start with " + mSize);
119
120        int n = mSize;
121        int o = 0;
122        long[] keys = mKeys;
123        Object[] values = mValues;
124
125        for (int i = 0; i < n; i++) {
126            Object val = values[i];
127
128            if (val != DELETED) {
129                if (i != o) {
130                    keys[o] = keys[i];
131                    values[o] = val;
132                }
133
134                o++;
135            }
136        }
137
138        mGarbage = false;
139        mSize = o;
140
141        // Log.e("SparseArray", "gc end with " + mSize);
142    }
143
144    /**
145     * Adds a mapping from the specified key to the specified value,
146     * replacing the previous mapping from the specified key if there
147     * was one.
148     */
149    public void put(long key, E value) {
150        int i = binarySearch(mKeys, 0, mSize, key);
151
152        if (i >= 0) {
153            mValues[i] = value;
154        } else {
155            i = ~i;
156
157            if (i < mSize && mValues[i] == DELETED) {
158                mKeys[i] = key;
159                mValues[i] = value;
160                return;
161            }
162
163            if (mGarbage && mSize >= mKeys.length) {
164                gc();
165
166                // Search again because indices may have changed.
167                i = ~binarySearch(mKeys, 0, mSize, key);
168            }
169
170            if (mSize >= mKeys.length) {
171                int n = ArrayUtils.idealIntArraySize(mSize + 1);
172
173                long[] nkeys = new long[n];
174                Object[] nvalues = new Object[n];
175
176                // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
177                System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
178                System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
179
180                mKeys = nkeys;
181                mValues = nvalues;
182            }
183
184            if (mSize - i != 0) {
185                // Log.e("SparseArray", "move " + (mSize - i));
186                System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
187                System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
188            }
189
190            mKeys[i] = key;
191            mValues[i] = value;
192            mSize++;
193        }
194    }
195
196    /**
197     * Returns the number of key-value mappings that this SparseArray
198     * currently stores.
199     */
200    public int size() {
201        if (mGarbage) {
202            gc();
203        }
204
205        return mSize;
206    }
207
208    /**
209     * Given an index in the range <code>0...size()-1</code>, returns
210     * the key from the <code>index</code>th key-value mapping that this
211     * SparseArray stores.
212     */
213    public long keyAt(int index) {
214        if (mGarbage) {
215            gc();
216        }
217
218        return mKeys[index];
219    }
220
221    /**
222     * Given an index in the range <code>0...size()-1</code>, returns
223     * the value from the <code>index</code>th key-value mapping that this
224     * SparseArray stores.
225     */
226    public E valueAt(int index) {
227        if (mGarbage) {
228            gc();
229        }
230
231        return (E) mValues[index];
232    }
233
234    /**
235     * Given an index in the range <code>0...size()-1</code>, sets a new
236     * value for the <code>index</code>th key-value mapping that this
237     * SparseArray stores.
238     */
239    public void setValueAt(int index, E value) {
240        if (mGarbage) {
241            gc();
242        }
243
244        mValues[index] = value;
245    }
246
247    /**
248     * Returns the index for which {@link #keyAt} would return the
249     * specified key, or a negative number if the specified
250     * key is not mapped.
251     */
252    public int indexOfKey(long key) {
253        if (mGarbage) {
254            gc();
255        }
256
257        return binarySearch(mKeys, 0, mSize, key);
258    }
259
260    /**
261     * Returns an index for which {@link #valueAt} would return the
262     * specified key, or a negative number if no keys map to the
263     * specified value.
264     * Beware that this is a linear search, unlike lookups by key,
265     * and that multiple keys can map to the same value and this will
266     * find only one of them.
267     */
268    public int indexOfValue(E value) {
269        if (mGarbage) {
270            gc();
271        }
272
273        for (int i = 0; i < mSize; i++)
274            if (mValues[i] == value)
275                return i;
276
277        return -1;
278    }
279
280    /**
281     * Removes all key-value mappings from this SparseArray.
282     */
283    public void clear() {
284        int n = mSize;
285        Object[] values = mValues;
286
287        for (int i = 0; i < n; i++) {
288            values[i] = null;
289        }
290
291        mSize = 0;
292        mGarbage = false;
293    }
294
295    /**
296     * Puts a key/value pair into the array, optimizing for the case where
297     * the key is greater than all existing keys in the array.
298     */
299    public void append(long key, E value) {
300        if (mSize != 0 && key <= mKeys[mSize - 1]) {
301            put(key, value);
302            return;
303        }
304
305        if (mGarbage && mSize >= mKeys.length) {
306            gc();
307        }
308
309        int pos = mSize;
310        if (pos >= mKeys.length) {
311            int n = ArrayUtils.idealIntArraySize(pos + 1);
312
313            long[] nkeys = new long[n];
314            Object[] nvalues = new Object[n];
315
316            // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
317            System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
318            System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
319
320            mKeys = nkeys;
321            mValues = nvalues;
322        }
323
324        mKeys[pos] = key;
325        mValues[pos] = value;
326        mSize = pos + 1;
327    }
328
329    private static int binarySearch(long[] a, int start, int len, long key) {
330        int high = start + len, low = start - 1, guess;
331
332        while (high - low > 1) {
333            guess = (high + low) / 2;
334
335            if (a[guess] < key)
336                low = guess;
337            else
338                high = guess;
339        }
340
341        if (high == start + len)
342            return ~(start + len);
343        else if (a[high] == key)
344            return high;
345        else
346            return ~high;
347    }
348
349    private void checkIntegrity() {
350        for (int i = 1; i < mSize; i++) {
351            if (mKeys[i] <= mKeys[i - 1]) {
352                for (int j = 0; j < mSize; j++) {
353                    Log.e("FAIL", j + ": " + mKeys[j] + " -> " + mValues[j]);
354                }
355
356                throw new RuntimeException();
357            }
358        }
359    }
360
361    private long[] mKeys;
362    private Object[] mValues;
363    private int mSize;
364}