• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
18 package com.xtremelabs.robolectric.shadows;
19 
20 import static com.xtremelabs.robolectric.Robolectric.shadowOf;
21 
22 import java.util.Arrays;
23 
24 import android.util.SparseArray;
25 
26 import com.xtremelabs.robolectric.Robolectric;
27 import com.xtremelabs.robolectric.internal.Implementation;
28 import com.xtremelabs.robolectric.internal.Implements;
29 import 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)
41 public 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 
__constructor__()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      */
__constructor__(int initialCapacity)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
get(int key)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
get(int key, E valueIfKeyNotFound)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
delete(int key)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
remove(int key)111     public void remove(int key) {
112         delete(key);
113     }
114 
gc()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
put(int key, E value)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
size()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
keyAt(int index)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
valueAt(int index)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
setValueAt(int index, E value)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
indexOfKey(int key)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
indexOfValue(E value)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
clear()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
append(int key, E value)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 
binarySearch(int[] a, int start, int len, int key)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 
checkIntegrity()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 
idealIntArraySize(int need)370     public static int idealIntArraySize(int need) {
371         return idealByteArraySize(need * 4) / 4;
372     }
373 
idealByteArraySize(int need)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
equals(Object o)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