1/*
2 * Copyright (C) 2013 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
18package android.support.v7.widget;
19
20import java.util.ArrayList;
21
22/**
23 * Like a SparseArray, but with the ability to offset key ranges for bulk insertions/deletions.
24 */
25class PositionMap<E> implements Cloneable {
26    private static final Object DELETED = new Object();
27    private boolean mGarbage = false;
28
29    private int[] mKeys;
30    private Object[] mValues;
31    private int mSize;
32
33    /**
34     * Creates a new SparseArray containing no mappings.
35     */
36    public PositionMap() {
37        this(10);
38    }
39
40    /**
41     * Creates a new PositionMap containing no mappings that will not
42     * require any additional memory allocation to store the specified
43     * number of mappings.  If you supply an initial capacity of 0, the
44     * sparse array will be initialized with a light-weight representation
45     * not requiring any additional array allocations.
46     */
47    public PositionMap(int initialCapacity) {
48        if (initialCapacity == 0) {
49            mKeys = ContainerHelpers.EMPTY_INTS;
50            mValues = ContainerHelpers.EMPTY_OBJECTS;
51        } else {
52            initialCapacity = idealIntArraySize(initialCapacity);
53            mKeys = new int[initialCapacity];
54            mValues = new Object[initialCapacity];
55        }
56        mSize = 0;
57    }
58
59    @Override
60    @SuppressWarnings("unchecked")
61    public PositionMap<E> clone() {
62        PositionMap<E> clone = null;
63        try {
64            clone = (PositionMap<E>) super.clone();
65            clone.mKeys = mKeys.clone();
66            clone.mValues = mValues.clone();
67        } catch (CloneNotSupportedException cnse) {
68            /* ignore */
69        }
70        return clone;
71    }
72
73    /**
74     * Gets the Object mapped from the specified key, or <code>null</code>
75     * if no such mapping has been made.
76     */
77    public E get(int key) {
78        return get(key, null);
79    }
80
81    /**
82     * Gets the Object mapped from the specified key, or the specified Object
83     * if no such mapping has been made.
84     */
85    @SuppressWarnings("unchecked")
86    public E get(int key, E valueIfKeyNotFound) {
87        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
88
89        if (i < 0 || mValues[i] == DELETED) {
90            return valueIfKeyNotFound;
91        } else {
92            return (E) mValues[i];
93        }
94    }
95
96    /**
97     * Removes the mapping from the specified key, if there was any.
98     */
99    public void delete(int key) {
100        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
101
102        if (i >= 0) {
103            if (mValues[i] != DELETED) {
104                mValues[i] = DELETED;
105                mGarbage = true;
106            }
107        }
108    }
109
110    /**
111     * Alias for {@link #delete(int)}.
112     */
113    public void remove(int key) {
114        delete(key);
115    }
116
117    /**
118     * Removes the mapping at the specified index.
119     */
120    public void removeAt(int index) {
121        if (mValues[index] != DELETED) {
122            mValues[index] = DELETED;
123            mGarbage = true;
124        }
125    }
126
127    /**
128     * Remove a range of mappings as a batch.
129     *
130     * @param index Index to begin at
131     * @param size Number of mappings to remove
132     */
133    public void removeAtRange(int index, int size) {
134        final int end = Math.min(mSize, index + size);
135        for (int i = index; i < end; i++) {
136            removeAt(i);
137        }
138    }
139
140    public void insertKeyRange(int keyStart, int count) {
141
142    }
143
144    public void removeKeyRange(ArrayList<E> removedItems, int keyStart, int count) {
145
146    }
147
148    private void gc() {
149        // Log.e("SparseArray", "gc start with " + mSize);
150
151        int n = mSize;
152        int o = 0;
153        int[] keys = mKeys;
154        Object[] values = mValues;
155
156        for (int i = 0; i < n; i++) {
157            Object val = values[i];
158
159            if (val != DELETED) {
160                if (i != o) {
161                    keys[o] = keys[i];
162                    values[o] = val;
163                    values[i] = null;
164                }
165
166                o++;
167            }
168        }
169
170        mGarbage = false;
171        mSize = o;
172
173        // Log.e("SparseArray", "gc end with " + mSize);
174    }
175
176    /**
177     * Adds a mapping from the specified key to the specified value,
178     * replacing the previous mapping from the specified key if there
179     * was one.
180     */
181    public void put(int key, E value) {
182        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
183
184        if (i >= 0) {
185            mValues[i] = value;
186        } else {
187            i = ~i;
188
189            if (i < mSize && mValues[i] == DELETED) {
190                mKeys[i] = key;
191                mValues[i] = value;
192                return;
193            }
194
195            if (mGarbage && mSize >= mKeys.length) {
196                gc();
197
198                // Search again because indices may have changed.
199                i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
200            }
201
202            if (mSize >= mKeys.length) {
203                int n = idealIntArraySize(mSize + 1);
204
205                int[] nkeys = new int[n];
206                Object[] nvalues = new Object[n];
207
208                // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
209                System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
210                System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
211
212                mKeys = nkeys;
213                mValues = nvalues;
214            }
215
216            if (mSize - i != 0) {
217                // Log.e("SparseArray", "move " + (mSize - i));
218                System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
219                System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
220            }
221
222            mKeys[i] = key;
223            mValues[i] = value;
224            mSize++;
225        }
226    }
227
228    /**
229     * Returns the number of key-value mappings that this SparseArray
230     * currently stores.
231     */
232    public int size() {
233        if (mGarbage) {
234            gc();
235        }
236
237        return mSize;
238    }
239
240    /**
241     * Given an index in the range <code>0...size()-1</code>, returns
242     * the key from the <code>index</code>th key-value mapping that this
243     * SparseArray stores.
244     */
245    public int keyAt(int index) {
246        if (mGarbage) {
247            gc();
248        }
249
250        return mKeys[index];
251    }
252
253    /**
254     * Given an index in the range <code>0...size()-1</code>, returns
255     * the value from the <code>index</code>th key-value mapping that this
256     * SparseArray stores.
257     */
258    @SuppressWarnings("unchecked")
259    public E valueAt(int index) {
260        if (mGarbage) {
261            gc();
262        }
263
264        return (E) mValues[index];
265    }
266
267    /**
268     * Given an index in the range <code>0...size()-1</code>, sets a new
269     * value for the <code>index</code>th key-value mapping that this
270     * SparseArray stores.
271     */
272    public void setValueAt(int index, E value) {
273        if (mGarbage) {
274            gc();
275        }
276
277        mValues[index] = value;
278    }
279
280    /**
281     * Returns the index for which {@link #keyAt} would return the
282     * specified key, or a negative number if the specified
283     * key is not mapped.
284     */
285    public int indexOfKey(int key) {
286        if (mGarbage) {
287            gc();
288        }
289
290        return ContainerHelpers.binarySearch(mKeys, mSize, key);
291    }
292
293    /**
294     * Returns an index for which {@link #valueAt} would return the
295     * specified key, or a negative number if no keys map to the
296     * specified value.
297     * <p>Beware that this is a linear search, unlike lookups by key,
298     * and that multiple keys can map to the same value and this will
299     * find only one of them.
300     * <p>Note also that unlike most collections' {@code indexOf} methods,
301     * this method compares values using {@code ==} rather than {@code equals}.
302     */
303    public int indexOfValue(E value) {
304        if (mGarbage) {
305            gc();
306        }
307
308        for (int i = 0; i < mSize; i++)
309            if (mValues[i] == value)
310                return i;
311
312        return -1;
313    }
314
315    /**
316     * Removes all key-value mappings from this SparseArray.
317     */
318    public void clear() {
319        int n = mSize;
320        Object[] values = mValues;
321
322        for (int i = 0; i < n; i++) {
323            values[i] = null;
324        }
325
326        mSize = 0;
327        mGarbage = false;
328    }
329
330    /**
331     * Puts a key/value pair into the array, optimizing for the case where
332     * the key is greater than all existing keys in the array.
333     */
334    public void append(int key, E value) {
335        if (mSize != 0 && key <= mKeys[mSize - 1]) {
336            put(key, value);
337            return;
338        }
339
340        if (mGarbage && mSize >= mKeys.length) {
341            gc();
342        }
343
344        int pos = mSize;
345        if (pos >= mKeys.length) {
346            int n = idealIntArraySize(pos + 1);
347
348            int[] nkeys = new int[n];
349            Object[] nvalues = new Object[n];
350
351            // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
352            System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
353            System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
354
355            mKeys = nkeys;
356            mValues = nvalues;
357        }
358
359        mKeys[pos] = key;
360        mValues[pos] = value;
361        mSize = pos + 1;
362    }
363
364    /**
365     * {@inheritDoc}
366     *
367     * <p>This implementation composes a string by iterating over its mappings. If
368     * this map contains itself as a value, the string "(this Map)"
369     * will appear in its place.
370     */
371    @Override
372    public String toString() {
373        if (size() <= 0) {
374            return "{}";
375        }
376
377        StringBuilder buffer = new StringBuilder(mSize * 28);
378        buffer.append('{');
379        for (int i=0; i<mSize; i++) {
380            if (i > 0) {
381                buffer.append(", ");
382            }
383            int key = keyAt(i);
384            buffer.append(key);
385            buffer.append('=');
386            Object value = valueAt(i);
387            if (value != this) {
388                buffer.append(value);
389            } else {
390                buffer.append("(this Map)");
391            }
392        }
393        buffer.append('}');
394        return buffer.toString();
395    }
396
397    static int idealByteArraySize(int need) {
398        for (int i = 4; i < 32; i++)
399            if (need <= (1 << i) - 12)
400                return (1 << i) - 12;
401
402        return need;
403    }
404
405    static int idealBooleanArraySize(int need) {
406        return idealByteArraySize(need);
407    }
408
409    static int idealShortArraySize(int need) {
410        return idealByteArraySize(need * 2) / 2;
411    }
412
413    static int idealCharArraySize(int need) {
414        return idealByteArraySize(need * 2) / 2;
415    }
416
417    static int idealIntArraySize(int need) {
418        return idealByteArraySize(need * 4) / 4;
419    }
420
421    static int idealFloatArraySize(int need) {
422        return idealByteArraySize(need * 4) / 4;
423    }
424
425    static int idealObjectArraySize(int need) {
426        return idealByteArraySize(need * 4) / 4;
427    }
428
429    static int idealLongArraySize(int need) {
430        return idealByteArraySize(need * 8) / 8;
431    }
432
433    static class ContainerHelpers {
434        static final boolean[] EMPTY_BOOLEANS = new boolean[0];
435        static final int[] EMPTY_INTS = new int[0];
436        static final long[] EMPTY_LONGS = new long[0];
437        static final Object[] EMPTY_OBJECTS = new Object[0];
438
439        // This is Arrays.binarySearch(), but doesn't do any argument validation.
440        static int binarySearch(int[] array, int size, int value) {
441            int lo = 0;
442            int hi = size - 1;
443
444            while (lo <= hi) {
445                final int mid = (lo + hi) >>> 1;
446                final int midVal = array[mid];
447
448                if (midVal < value) {
449                    lo = mid + 1;
450                } else if (midVal > value) {
451                    hi = mid - 1;
452                } else {
453                    return mid;  // value found
454                }
455            }
456            return ~lo;  // value not present
457        }
458    }
459
460}
461