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