1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.support.v4.util;
18 
19 /**
20  * A copy of the current platform (currently {@link android.os.Build.VERSION_CODES#KITKAT}
21  * version of {@link android.util.SparseArray}; provides a removeAt() method and other things.
22  */
23 public class SparseArrayCompat<E> implements Cloneable {
24     private static final Object DELETED = new Object();
25     private boolean mGarbage = false;
26 
27     private int[] mKeys;
28     private Object[] mValues;
29     private int mSize;
30 
31     /**
32      * Creates a new SparseArray containing no mappings.
33      */
SparseArrayCompat()34     public SparseArrayCompat() {
35         this(10);
36     }
37 
38     /**
39      * Creates a new SparseArray containing no mappings that will not
40      * require any additional memory allocation to store the specified
41      * number of mappings.  If you supply an initial capacity of 0, the
42      * sparse array will be initialized with a light-weight representation
43      * not requiring any additional array allocations.
44      */
SparseArrayCompat(int initialCapacity)45     public SparseArrayCompat(int initialCapacity) {
46         if (initialCapacity == 0) {
47             mKeys =  ContainerHelpers.EMPTY_INTS;
48             mValues =  ContainerHelpers.EMPTY_OBJECTS;
49         } else {
50             initialCapacity =  ContainerHelpers.idealIntArraySize(initialCapacity);
51             mKeys = new int[initialCapacity];
52             mValues = new Object[initialCapacity];
53         }
54         mSize = 0;
55     }
56 
57     @Override
58     @SuppressWarnings("unchecked")
clone()59     public SparseArrayCompat<E> clone() {
60         SparseArrayCompat<E> clone = null;
61         try {
62             clone = (SparseArrayCompat<E>) super.clone();
63             clone.mKeys = mKeys.clone();
64             clone.mValues = mValues.clone();
65         } catch (CloneNotSupportedException cnse) {
66             /* ignore */
67         }
68         return clone;
69     }
70 
71     /**
72      * Gets the Object mapped from the specified key, or <code>null</code>
73      * if no such mapping has been made.
74      */
get(int key)75     public E get(int key) {
76         return get(key, null);
77     }
78 
79     /**
80      * Gets the Object mapped from the specified key, or the specified Object
81      * if no such mapping has been made.
82      */
83     @SuppressWarnings("unchecked")
get(int key, E valueIfKeyNotFound)84     public E get(int key, E valueIfKeyNotFound) {
85         int i =  ContainerHelpers.binarySearch(mKeys, mSize, key);
86 
87         if (i < 0 || mValues[i] == DELETED) {
88             return valueIfKeyNotFound;
89         } else {
90             return (E) mValues[i];
91         }
92     }
93 
94     /**
95      * Removes the mapping from the specified key, if there was any.
96      */
delete(int key)97     public void delete(int key) {
98         int i =  ContainerHelpers.binarySearch(mKeys, mSize, key);
99 
100         if (i >= 0) {
101             if (mValues[i] != DELETED) {
102                 mValues[i] = DELETED;
103                 mGarbage = true;
104             }
105         }
106     }
107 
108     /**
109      * Alias for {@link #delete(int)}.
110      */
remove(int key)111     public void remove(int key) {
112         delete(key);
113     }
114 
115     /**
116      * Removes the mapping at the specified index.
117      */
removeAt(int index)118     public void removeAt(int index) {
119         if (mValues[index] != DELETED) {
120             mValues[index] = DELETED;
121             mGarbage = true;
122         }
123     }
124 
125     /**
126      * Remove a range of mappings as a batch.
127      *
128      * @param index Index to begin at
129      * @param size Number of mappings to remove
130      */
removeAtRange(int index, int size)131     public void removeAtRange(int index, int size) {
132         final int end = Math.min(mSize, index + size);
133         for (int i = index; i < end; i++) {
134             removeAt(i);
135         }
136     }
137 
gc()138     private void gc() {
139         // Log.e("SparseArray", "gc start with " + mSize);
140 
141         int n = mSize;
142         int o = 0;
143         int[] keys = mKeys;
144         Object[] values = mValues;
145 
146         for (int i = 0; i < n; i++) {
147             Object val = values[i];
148 
149             if (val != DELETED) {
150                 if (i != o) {
151                     keys[o] = keys[i];
152                     values[o] = val;
153                     values[i] = null;
154                 }
155 
156                 o++;
157             }
158         }
159 
160         mGarbage = false;
161         mSize = o;
162 
163         // Log.e("SparseArray", "gc end with " + mSize);
164     }
165 
166     /**
167      * Adds a mapping from the specified key to the specified value,
168      * replacing the previous mapping from the specified key if there
169      * was one.
170      */
put(int key, E value)171     public void put(int key, E value) {
172         int i =  ContainerHelpers.binarySearch(mKeys, mSize, key);
173 
174         if (i >= 0) {
175             mValues[i] = value;
176         } else {
177             i = ~i;
178 
179             if (i < mSize && mValues[i] == DELETED) {
180                 mKeys[i] = key;
181                 mValues[i] = value;
182                 return;
183             }
184 
185             if (mGarbage && mSize >= mKeys.length) {
186                 gc();
187 
188                 // Search again because indices may have changed.
189                 i = ~ ContainerHelpers.binarySearch(mKeys, mSize, key);
190             }
191 
192             if (mSize >= mKeys.length) {
193                 int n =  ContainerHelpers.idealIntArraySize(mSize + 1);
194 
195                 int[] nkeys = new int[n];
196                 Object[] nvalues = new Object[n];
197 
198                 // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
199                 System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
200                 System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
201 
202                 mKeys = nkeys;
203                 mValues = nvalues;
204             }
205 
206             if (mSize - i != 0) {
207                 // Log.e("SparseArray", "move " + (mSize - i));
208                 System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
209                 System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
210             }
211 
212             mKeys[i] = key;
213             mValues[i] = value;
214             mSize++;
215         }
216     }
217 
218     /**
219      * Returns the number of key-value mappings that this SparseArray
220      * currently stores.
221      */
size()222     public int size() {
223         if (mGarbage) {
224             gc();
225         }
226 
227         return mSize;
228     }
229 
230     /**
231      * Given an index in the range <code>0...size()-1</code>, returns
232      * the key from the <code>index</code>th key-value mapping that this
233      * SparseArray stores.
234      */
keyAt(int index)235     public int keyAt(int index) {
236         if (mGarbage) {
237             gc();
238         }
239 
240         return mKeys[index];
241     }
242 
243     /**
244      * Given an index in the range <code>0...size()-1</code>, returns
245      * the value from the <code>index</code>th key-value mapping that this
246      * SparseArray stores.
247      */
248     @SuppressWarnings("unchecked")
valueAt(int index)249     public E valueAt(int index) {
250         if (mGarbage) {
251             gc();
252         }
253 
254         return (E) mValues[index];
255     }
256 
257     /**
258      * Given an index in the range <code>0...size()-1</code>, sets a new
259      * value for the <code>index</code>th key-value mapping that this
260      * SparseArray stores.
261      */
setValueAt(int index, E value)262     public void setValueAt(int index, E value) {
263         if (mGarbage) {
264             gc();
265         }
266 
267         mValues[index] = value;
268     }
269 
270     /**
271      * Returns the index for which {@link #keyAt} would return the
272      * specified key, or a negative number if the specified
273      * key is not mapped.
274      */
indexOfKey(int key)275     public int indexOfKey(int key) {
276         if (mGarbage) {
277             gc();
278         }
279 
280         return  ContainerHelpers.binarySearch(mKeys, mSize, key);
281     }
282 
283     /**
284      * Returns an index for which {@link #valueAt} would return the
285      * specified key, or a negative number if no keys map to the
286      * specified value.
287      * <p>Beware that this is a linear search, unlike lookups by key,
288      * and that multiple keys can map to the same value and this will
289      * find only one of them.
290      * <p>Note also that unlike most collections' {@code indexOf} methods,
291      * this method compares values using {@code ==} rather than {@code equals}.
292      */
indexOfValue(E value)293     public int indexOfValue(E value) {
294         if (mGarbage) {
295             gc();
296         }
297 
298         for (int i = 0; i < mSize; i++)
299             if (mValues[i] == value)
300                 return i;
301 
302         return -1;
303     }
304 
305     /**
306      * Removes all key-value mappings from this SparseArray.
307      */
clear()308     public void clear() {
309         int n = mSize;
310         Object[] values = mValues;
311 
312         for (int i = 0; i < n; i++) {
313             values[i] = null;
314         }
315 
316         mSize = 0;
317         mGarbage = false;
318     }
319 
320     /**
321      * Puts a key/value pair into the array, optimizing for the case where
322      * the key is greater than all existing keys in the array.
323      */
append(int key, E value)324     public void append(int key, E value) {
325         if (mSize != 0 && key <= mKeys[mSize - 1]) {
326             put(key, value);
327             return;
328         }
329 
330         if (mGarbage && mSize >= mKeys.length) {
331             gc();
332         }
333 
334         int pos = mSize;
335         if (pos >= mKeys.length) {
336             int n =  ContainerHelpers.idealIntArraySize(pos + 1);
337 
338             int[] nkeys = new int[n];
339             Object[] nvalues = new Object[n];
340 
341             // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
342             System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
343             System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
344 
345             mKeys = nkeys;
346             mValues = nvalues;
347         }
348 
349         mKeys[pos] = key;
350         mValues[pos] = value;
351         mSize = pos + 1;
352     }
353 
354     /**
355      * {@inheritDoc}
356      *
357      * <p>This implementation composes a string by iterating over its mappings. If
358      * this map contains itself as a value, the string "(this Map)"
359      * will appear in its place.
360      */
361     @Override
toString()362     public String toString() {
363         if (size() <= 0) {
364             return "{}";
365         }
366 
367         StringBuilder buffer = new StringBuilder(mSize * 28);
368         buffer.append('{');
369         for (int i=0; i<mSize; i++) {
370             if (i > 0) {
371                 buffer.append(", ");
372             }
373             int key = keyAt(i);
374             buffer.append(key);
375             buffer.append('=');
376             Object value = valueAt(i);
377             if (value != this) {
378                 buffer.append(value);
379             } else {
380                 buffer.append("(this Map)");
381             }
382         }
383         buffer.append('}');
384         return buffer.toString();
385     }
386 }
387