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