Home | History | Annotate | Download | only in shadows
      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 (at) 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 
     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