1/*
2 * Copyright 2013, Google Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 *     * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32package org.jf.util;
33
34import java.util.Arrays;
35import java.util.Collections;
36import java.util.List;
37
38/**
39 * SparseArrays map integers to Objects.  Unlike a normal array of Objects,
40 * there can be gaps in the indices.  It is intended to be more efficient
41 * than using a HashMap to map Integers to Objects.
42 */
43public class SparseArray<E> {
44    private static final Object DELETED = new Object();
45    private boolean mGarbage = false;
46
47    /**
48     * Creates a new SparseArray containing no mappings.
49     */
50    public SparseArray() {
51        this(10);
52    }
53
54    /**
55     * Creates a new SparseArray containing no mappings that will not
56     * require any additional memory allocation to store the specified
57     * number of mappings.
58     */
59    public SparseArray(int initialCapacity) {
60        mKeys = new int[initialCapacity];
61        mValues = new Object[initialCapacity];
62        mSize = 0;
63    }
64
65    /**
66     * Gets the Object mapped from the specified key, or <code>null</code>
67     * if no such mapping has been made.
68     */
69    public E get(int key) {
70        return get(key, null);
71    }
72
73    /**
74     * Gets the Object mapped from the specified key, or the specified Object
75     * if no such mapping has been made.
76     */
77    public E get(int key, E valueIfKeyNotFound) {
78        int i = binarySearch(mKeys, 0, mSize, key);
79
80        if (i < 0 || mValues[i] == DELETED) {
81            return valueIfKeyNotFound;
82        } else {
83            return (E) mValues[i];
84        }
85    }
86
87    /**
88     * Removes the mapping from the specified key, if there was any.
89     */
90    public void delete(int key) {
91        int i = binarySearch(mKeys, 0, mSize, key);
92
93        if (i >= 0) {
94            if (mValues[i] != DELETED) {
95                mValues[i] = DELETED;
96                mGarbage = true;
97            }
98        }
99    }
100
101    /**
102     * Alias for {@link #delete(int)}.
103     */
104    public void remove(int key) {
105        delete(key);
106    }
107
108    private void gc() {
109        // Log.e("SparseArray", "gc start with " + mSize);
110
111        int n = mSize;
112        int o = 0;
113        int[] keys = mKeys;
114        Object[] values = mValues;
115
116        for (int i = 0; i < n; i++) {
117            Object val = values[i];
118
119            if (val != DELETED) {
120                if (i != o) {
121                    keys[o] = keys[i];
122                    values[o] = val;
123                }
124
125                o++;
126            }
127        }
128
129        mGarbage = false;
130        mSize = o;
131
132        // Log.e("SparseArray", "gc end with " + mSize);
133    }
134
135    /**
136     * Adds a mapping from the specified key to the specified value,
137     * replacing the previous mapping from the specified key if there
138     * was one.
139     */
140    public void put(int key, E value) {
141        int i = binarySearch(mKeys, 0, mSize, key);
142
143        if (i >= 0) {
144            mValues[i] = value;
145        } else {
146            i = ~i;
147
148            if (i < mSize && mValues[i] == DELETED) {
149                mKeys[i] = key;
150                mValues[i] = value;
151                return;
152            }
153
154            if (mGarbage && mSize >= mKeys.length) {
155                gc();
156
157                // Search again because indices may have changed.
158                i = ~binarySearch(mKeys, 0, mSize, key);
159            }
160
161            if (mSize >= mKeys.length) {
162                int n = Math.max(mSize + 1, mKeys.length * 2);
163
164                int[] nkeys = new int[n];
165                Object[] nvalues = new Object[n];
166
167                // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
168                System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
169                System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
170
171                mKeys = nkeys;
172                mValues = nvalues;
173            }
174
175            if (mSize - i != 0) {
176                // Log.e("SparseArray", "move " + (mSize - i));
177                System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
178                System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
179            }
180
181            mKeys[i] = key;
182            mValues[i] = value;
183            mSize++;
184        }
185    }
186
187    /**
188     * Returns the number of key-value mappings that this SparseArray
189     * currently stores.
190     */
191    public int size() {
192        if (mGarbage) {
193            gc();
194        }
195
196        return mSize;
197    }
198
199    /**
200     * Given an index in the range <code>0...size()-1</code>, returns
201     * the key from the <code>index</code>th key-value mapping that this
202     * SparseArray stores.
203     */
204    public int keyAt(int index) {
205        if (mGarbage) {
206            gc();
207        }
208
209        return mKeys[index];
210    }
211
212    /**
213     * Given an index in the range <code>0...size()-1</code>, returns
214     * the value from the <code>index</code>th key-value mapping that this
215     * SparseArray stores.
216     */
217    public E valueAt(int index) {
218        if (mGarbage) {
219            gc();
220        }
221
222        return (E) mValues[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     * SparseArray stores.
229     */
230    public void setValueAt(int index, E value) {
231        if (mGarbage) {
232            gc();
233        }
234
235        mValues[index] = value;
236    }
237
238    /**
239     * Returns the index for which {@link #keyAt} would return the
240     * specified key, or a negative number if the specified
241     * key is not mapped.
242     */
243    public int indexOfKey(int key) {
244        if (mGarbage) {
245            gc();
246        }
247
248        return binarySearch(mKeys, 0, mSize, key);
249    }
250
251    /**
252     * Returns an index for which {@link #valueAt} would return the
253     * specified key, or a negative number if no keys map to the
254     * specified value.
255     * Beware that this is a linear search, unlike lookups by key,
256     * and that multiple keys can map to the same value and this will
257     * find only one of them.
258     */
259    public int indexOfValue(E value) {
260        if (mGarbage) {
261            gc();
262        }
263
264        for (int i = 0; i < mSize; i++)
265            if (mValues[i] == value)
266                return i;
267
268        return -1;
269    }
270
271    /**
272     * Removes all key-value mappings from this SparseArray.
273     */
274    public void clear() {
275        int n = mSize;
276        Object[] values = mValues;
277
278        for (int i = 0; i < n; i++) {
279            values[i] = null;
280        }
281
282        mSize = 0;
283        mGarbage = false;
284    }
285
286    /**
287     * Puts a key/value pair into the array, optimizing for the case where
288     * the key is greater than all existing keys in the array.
289     */
290    public void append(int key, E value) {
291        if (mSize != 0 && key <= mKeys[mSize - 1]) {
292            put(key, value);
293            return;
294        }
295
296        if (mGarbage && mSize >= mKeys.length) {
297            gc();
298        }
299
300        int pos = mSize;
301        if (pos >= mKeys.length) {
302            int n = Math.max(pos + 1, mKeys.length * 2);
303
304            int[] nkeys = new int[n];
305            Object[] nvalues = new Object[n];
306
307            // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
308            System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
309            System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
310
311            mKeys = nkeys;
312            mValues = nvalues;
313        }
314
315        mKeys[pos] = key;
316        mValues[pos] = value;
317        mSize = pos + 1;
318    }
319
320    /**
321     * Increases the size of the underlying storage if needed, to ensure that it can
322     * hold the specified number of items without having to allocate additional memory
323     * @param capacity the number of items
324     */
325    public void ensureCapacity(int capacity) {
326        if (mGarbage && mSize >= mKeys.length) {
327            gc();
328        }
329
330        if (mKeys.length < capacity) {
331            int[] nkeys = new int[capacity];
332            Object[] nvalues = new Object[capacity];
333
334            System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
335            System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
336
337            mKeys = nkeys;
338            mValues = nvalues;
339        }
340    }
341
342    private static int binarySearch(int[] a, int start, int len, int key) {
343        int high = start + len, low = start - 1, guess;
344
345        while (high - low > 1) {
346            guess = (high + low) / 2;
347
348            if (a[guess] < key)
349                low = guess;
350            else
351                high = guess;
352        }
353
354        if (high == start + len)
355            return ~(start + len);
356        else if (a[high] == key)
357            return high;
358        else
359            return ~high;
360    }
361
362    /**
363     * @return a read-only list of the values in this SparseArray which are in ascending order, based on their
364     * associated key
365     */
366    public List<E> getValues() {
367        return Collections.unmodifiableList(Arrays.asList((E[])mValues));
368    }
369
370    private int[] mKeys;
371    private Object[] mValues;
372    private int mSize;
373}
374