1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 * Copyright (C) 2011 Eric Bowman
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.xtremelabs.robolectric.shadows;
19
20import static com.xtremelabs.robolectric.Robolectric.shadowOf;
21
22import java.util.Arrays;
23
24import android.util.SparseArray;
25
26import com.xtremelabs.robolectric.Robolectric;
27import com.xtremelabs.robolectric.internal.Implementation;
28import com.xtremelabs.robolectric.internal.Implements;
29import com.xtremelabs.robolectric.internal.RealObject;
30
31/**
32 * Shadow implementation of SparseArray. Basically copied & pasted the
33 * real SparseArray implementation, without depending on the ArrayUtils
34 * class that is not in the SDK.
35 *
36 * @author Eric Bowman (ebowman@boboco.ie)
37 * @since 2011-02-25 11:01
38 */
39@SuppressWarnings({"JavaDoc"})
40@Implements(SparseArray.class)
41public class ShadowSparseArray<E> {
42
43    private static final Object DELETED = new Object();
44    private boolean mGarbage = false;
45
46    @RealObject
47    private SparseArray<E> realObject;
48
49    public void __constructor__() {
50        __constructor__(10);
51    }
52
53    /**
54     * Creates a new SparseArray containing no mappings that will not
55     * require any additional memory allocation to store the specified
56     * number of mappings.
57     */
58    public void __constructor__(int initialCapacity) {
59        initialCapacity = idealIntArraySize(initialCapacity);
60        mKeys = new int[initialCapacity];
61        mValues = new Object[initialCapacity];
62        mSize = 0;
63    }
64
65
66
67    /**
68     * Gets the Object mapped from the specified key, or <code>null</code>
69     * if no such mapping has been made.
70     */
71    @Implementation
72    public E get(int 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    @Implementation
81    public E get(int 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            //noinspection unchecked
88            return (E) mValues[i];
89        }
90    }
91
92    /**
93     * Removes the mapping from the specified key, if there was any.
94     */
95    @Implementation
96    public void delete(int key) {
97        int i = binarySearch(mKeys, 0, mSize, key);
98
99        if (i >= 0) {
100            if (mValues[i] != DELETED) {
101                mValues[i] = DELETED;
102                mGarbage = true;
103            }
104        }
105    }
106
107    /**
108     * Alias for {@link #delete(int)}.
109     */
110    @Implementation
111    public void remove(int key) {
112        delete(key);
113    }
114
115    private void gc() {
116        // Log.e("SparseArray", "gc start with " + mSize);
117
118        int n = mSize;
119        int o = 0;
120        int[] keys = mKeys;
121        Object[] values = mValues;
122
123        for (int i = 0; i < n; i++) {
124            Object val = values[i];
125
126            if (val != DELETED) {
127                if (i != o) {
128                    keys[o] = keys[i];
129                    values[o] = val;
130                }
131
132                o++;
133            }
134        }
135
136        mGarbage = false;
137        mSize = o;
138
139        // Log.e("SparseArray", "gc end with " + mSize);
140    }
141
142    /**
143     * Adds a mapping from the specified key to the specified value,
144     * replacing the previous mapping from the specified key if there
145     * was one.
146     */
147    @Implementation
148    public void put(int key, E value) {
149        int i = binarySearch(mKeys, 0, mSize, key);
150
151        if (i >= 0) {
152            mValues[i] = value;
153        } else {
154            i = ~i;
155
156            if (i < mSize && mValues[i] == DELETED) {
157                mKeys[i] = key;
158                mValues[i] = value;
159                return;
160            }
161
162            if (mGarbage && mSize >= mKeys.length) {
163                gc();
164
165                // Search again because indices may have changed.
166                i = ~binarySearch(mKeys, 0, mSize, key);
167            }
168
169            if (mSize >= mKeys.length) {
170                int n = idealIntArraySize(mSize + 1);
171
172                int[] nkeys = new int[n];
173                Object[] nvalues = new Object[n];
174
175                // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
176                System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
177                System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
178
179                mKeys = nkeys;
180                mValues = nvalues;
181            }
182
183            if (mSize - i != 0) {
184                // Log.e("SparseArray", "move " + (mSize - i));
185                System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
186                System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
187            }
188
189            mKeys[i] = key;
190            mValues[i] = value;
191            mSize++;
192        }
193    }
194
195    /**
196     * Returns the number of key-value mappings that this SparseArray
197     * currently stores.
198     */
199    @Implementation
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    @Implementation
214    public int keyAt(int index) {
215        if (mGarbage) {
216            gc();
217        }
218
219        return mKeys[index];
220    }
221
222    /**
223     * Given an index in the range <code>0...size()-1</code>, returns
224     * the value from the <code>index</code>th key-value mapping that this
225     * SparseArray stores.
226     */
227    @Implementation
228    public E valueAt(int index) {
229        if (mGarbage) {
230            gc();
231        }
232
233        //noinspection unchecked
234        return (E) mValues[index];
235    }
236
237    /**
238     * Given an index in the range <code>0...size()-1</code>, sets a new
239     * value for the <code>index</code>th key-value mapping that this
240     * SparseArray stores.
241     */
242    @Implementation
243    public void setValueAt(int index, E value) {
244        if (mGarbage) {
245            gc();
246        }
247
248        mValues[index] = value;
249    }
250
251    /**
252     * Returns the index for which {@link #keyAt} would return the
253     * specified key, or a negative number if the specified
254     * key is not mapped.
255     */
256    @Implementation
257    public int indexOfKey(int key) {
258        if (mGarbage) {
259            gc();
260        }
261
262        return binarySearch(mKeys, 0, mSize, key);
263    }
264
265    /**
266     * Returns an index for which {@link #valueAt} would return the
267     * specified key, or a negative number if no keys map to the
268     * specified value.
269     * Beware that this is a linear search, unlike lookups by key,
270     * and that multiple keys can map to the same value and this will
271     * find only one of them.
272     */
273    @Implementation
274    public int indexOfValue(E value) {
275        if (mGarbage) {
276            gc();
277        }
278
279        for (int i = 0; i < mSize; i++)
280            if (mValues[i] == value)
281                return i;
282
283        return -1;
284    }
285
286    /**
287     * Removes all key-value mappings from this SparseArray.
288     */
289    @Implementation
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    @Implementation
307    public void append(int key, E value) {
308        if (mSize != 0 && key <= mKeys[mSize - 1]) {
309            put(key, value);
310            return;
311        }
312
313        if (mGarbage && mSize >= mKeys.length) {
314            gc();
315        }
316
317        int pos = mSize;
318        if (pos >= mKeys.length) {
319            int n = idealIntArraySize(pos + 1);
320
321            int[] nkeys = new int[n];
322            Object[] nvalues = new Object[n];
323
324            // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
325            System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
326            System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
327
328            mKeys = nkeys;
329            mValues = nvalues;
330        }
331
332        mKeys[pos] = key;
333        mValues[pos] = value;
334        mSize = pos + 1;
335    }
336
337    private static int binarySearch(int[] a, int start, int len, int key) {
338        int high = start + len, low = start - 1, guess;
339
340        while (high - low > 1) {
341            guess = (high + low) / 2;
342
343            if (a[guess] < key)
344                low = guess;
345            else
346                high = guess;
347        }
348
349        if (high == start + len)
350            return ~(start + len);
351        else if (a[high] == key)
352            return high;
353        else
354            return ~high;
355    }
356
357    private void checkIntegrity() {
358        for (int i = 1; i < mSize; i++) {
359            if (mKeys[i] <= mKeys[i - 1]) {
360                for (int j = 0; j < mSize; j++) {
361                    System.err.println(
362                            "FAIL: " + j + ": " + mKeys[j] + " -> " + mValues[j]);
363                }
364
365                throw new RuntimeException();
366            }
367        }
368    }
369
370    public static int idealIntArraySize(int need) {
371        return idealByteArraySize(need * 4) / 4;
372    }
373
374    public static int idealByteArraySize(int need) {
375        for (int i = 4; i < 32; i++)
376            if (need <= (1 << i) - 12)
377                return (1 << i) - 12;
378
379        return need;
380    }
381
382    @Implementation
383    @Override
384    public boolean equals(Object o) {
385        if (o == null || o.getClass() != realObject.getClass())
386            return false;
387
388        ShadowSparseArray<?> target = (ShadowSparseArray<?>) shadowOf((SparseArray<?>) o);
389        return Arrays.equals(mKeys, target.mKeys) && Arrays.deepEquals(mValues, target.mValues);
390    }
391
392    private int[] mKeys;
393    private Object[] mValues;
394    private int mSize;
395}
396