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
17package com.android.layoutlib.bridge.util;
18
19
20import com.android.internal.util.ArrayUtils;
21
22import android.util.SparseArray;
23
24import java.lang.ref.WeakReference;
25
26/**
27 * This is a custom {@link SparseArray} that uses {@link WeakReference} around the objects added
28 * to it. When the array is compacted, not only deleted indices but also empty references
29 * are removed, making the array efficient at removing references that were reclaimed.
30 *
31 * The code is taken from {@link SparseArray} directly and adapted to use weak references.
32 *
33 * Because our usage means that we never actually call {@link #remove(int)} or {@link #delete(int)},
34 * we must manually check if there are reclaimed references to trigger an internal compact step
35 * (which is normally only triggered when an item is manually removed).
36 *
37 * SparseArrays map integers to Objects.  Unlike a normal array of Objects,
38 * there can be gaps in the indices.  It is intended to be more efficient
39 * than using a HashMap to map Integers to Objects.
40 */
41@SuppressWarnings("unchecked")
42public class SparseWeakArray<E> {
43
44    private static final Object DELETED_REF = new Object();
45    private static final WeakReference<?> DELETED = new WeakReference(DELETED_REF);
46    private boolean mGarbage = false;
47
48    /**
49     * Creates a new SparseArray containing no mappings.
50     */
51    public SparseWeakArray() {
52        this(10);
53    }
54
55    /**
56     * Creates a new SparseArray containing no mappings that will not
57     * require any additional memory allocation to store the specified
58     * number of mappings.
59     */
60    public SparseWeakArray(int initialCapacity) {
61        initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
62
63        mKeys = new int[initialCapacity];
64        mValues = new WeakReference[initialCapacity];
65        mSize = 0;
66    }
67
68    /**
69     * Gets the Object mapped from the specified key, or <code>null</code>
70     * if no such mapping has been made.
71     */
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    public E get(int key, E valueIfKeyNotFound) {
81        int i = binarySearch(mKeys, 0, mSize, key);
82
83        if (i < 0 || mValues[i] == DELETED || mValues[i].get() == null) {
84            return valueIfKeyNotFound;
85        } else {
86            return (E) mValues[i].get();
87        }
88    }
89
90    /**
91     * Removes the mapping from the specified key, if there was any.
92     */
93    public void delete(int key) {
94        int i = binarySearch(mKeys, 0, mSize, key);
95
96        if (i >= 0) {
97            if (mValues[i] != DELETED) {
98                mValues[i] = DELETED;
99                mGarbage = true;
100            }
101        }
102    }
103
104    /**
105     * Alias for {@link #delete(int)}.
106     */
107    public void remove(int key) {
108        delete(key);
109    }
110
111    /**
112     * Removes the mapping at the specified index.
113     */
114    public void removeAt(int index) {
115        if (mValues[index] != DELETED) {
116            mValues[index] = DELETED;
117            mGarbage = true;
118        }
119    }
120
121    private void gc() {
122        int n = mSize;
123        int o = 0;
124        int[] keys = mKeys;
125        WeakReference<?>[] values = mValues;
126
127        for (int i = 0; i < n; i++) {
128            WeakReference<?> val = values[i];
129
130            // Don't keep any non DELETED values, but only the one that still have a valid
131            // reference.
132            if (val != DELETED && val.get() != null) {
133                if (i != o) {
134                    keys[o] = keys[i];
135                    values[o] = val;
136                }
137
138                o++;
139            }
140        }
141
142        mGarbage = false;
143        mSize = o;
144
145        int newSize = ArrayUtils.idealIntArraySize(mSize);
146        if (newSize < mKeys.length) {
147            int[] nkeys = new int[newSize];
148            WeakReference<?>[] nvalues = new WeakReference[newSize];
149
150            System.arraycopy(mKeys, 0, nkeys, 0, newSize);
151            System.arraycopy(mValues, 0, nvalues, 0, newSize);
152
153            mKeys = nkeys;
154            mValues = nvalues;
155        }
156    }
157
158    /**
159     * Adds a mapping from the specified key to the specified value,
160     * replacing the previous mapping from the specified key if there
161     * was one.
162     */
163    public void put(int key, E value) {
164        int i = binarySearch(mKeys, 0, mSize, key);
165
166        if (i >= 0) {
167            mValues[i] = new WeakReference(value);
168        } else {
169            i = ~i;
170
171            if (i < mSize && (mValues[i] == DELETED || mValues[i].get() == null)) {
172                mKeys[i] = key;
173                mValues[i] = new WeakReference(value);
174                return;
175            }
176
177            if (mSize >= mKeys.length && (mGarbage || hasReclaimedRefs())) {
178                gc();
179
180                // Search again because indices may have changed.
181                i = ~binarySearch(mKeys, 0, mSize, key);
182            }
183
184            if (mSize >= mKeys.length) {
185                int n = ArrayUtils.idealIntArraySize(mSize + 1);
186
187                int[] nkeys = new int[n];
188                WeakReference<?>[] nvalues = new WeakReference[n];
189
190                // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
191                System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
192                System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
193
194                mKeys = nkeys;
195                mValues = nvalues;
196            }
197
198            if (mSize - i != 0) {
199                // Log.e("SparseArray", "move " + (mSize - i));
200                System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
201                System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
202            }
203
204            mKeys[i] = key;
205            mValues[i] = new WeakReference(value);
206            mSize++;
207        }
208    }
209
210    /**
211     * Returns the number of key-value mappings that this SparseArray
212     * currently stores.
213     */
214    public int size() {
215        if (mGarbage) {
216            gc();
217        }
218
219        return mSize;
220    }
221
222    /**
223     * Given an index in the range <code>0...size()-1</code>, returns
224     * the key from the <code>index</code>th key-value mapping that this
225     * SparseArray stores.
226     */
227    public int keyAt(int index) {
228        if (mGarbage) {
229            gc();
230        }
231
232        return mKeys[index];
233    }
234
235    /**
236     * Given an index in the range <code>0...size()-1</code>, returns
237     * the value from the <code>index</code>th key-value mapping that this
238     * SparseArray stores.
239     */
240    public E valueAt(int index) {
241        if (mGarbage) {
242            gc();
243        }
244
245        return (E) mValues[index].get();
246    }
247
248    /**
249     * Given an index in the range <code>0...size()-1</code>, sets a new
250     * value for the <code>index</code>th key-value mapping that this
251     * SparseArray stores.
252     */
253    public void setValueAt(int index, E value) {
254        if (mGarbage) {
255            gc();
256        }
257
258        mValues[index] = new WeakReference(value);
259    }
260
261    /**
262     * Returns the index for which {@link #keyAt} would return the
263     * specified key, or a negative number if the specified
264     * key is not mapped.
265     */
266    public int indexOfKey(int key) {
267        if (mGarbage) {
268            gc();
269        }
270
271        return binarySearch(mKeys, 0, mSize, key);
272    }
273
274    /**
275     * Returns an index for which {@link #valueAt} would return the
276     * specified key, or a negative number if no keys map to the
277     * specified value.
278     * Beware that this is a linear search, unlike lookups by key,
279     * and that multiple keys can map to the same value and this will
280     * find only one of them.
281     */
282    public int indexOfValue(E value) {
283        if (mGarbage) {
284            gc();
285        }
286
287        for (int i = 0; i < mSize; i++)
288            if (mValues[i].get() == value)
289                return i;
290
291        return -1;
292    }
293
294    /**
295     * Removes all key-value mappings from this SparseArray.
296     */
297    public void clear() {
298        int n = mSize;
299        WeakReference<?>[] values = mValues;
300
301        for (int i = 0; i < n; i++) {
302            values[i] = null;
303        }
304
305        mSize = 0;
306        mGarbage = false;
307    }
308
309    /**
310     * Puts a key/value pair into the array, optimizing for the case where
311     * the key is greater than all existing keys in the array.
312     */
313    public void append(int key, E value) {
314        if (mSize != 0 && key <= mKeys[mSize - 1]) {
315            put(key, value);
316            return;
317        }
318
319        if (mSize >= mKeys.length && (mGarbage || hasReclaimedRefs())) {
320            gc();
321        }
322
323        int pos = mSize;
324        if (pos >= mKeys.length) {
325            int n = ArrayUtils.idealIntArraySize(pos + 1);
326
327            int[] nkeys = new int[n];
328            WeakReference<?>[] nvalues = new WeakReference[n];
329
330            // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
331            System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
332            System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
333
334            mKeys = nkeys;
335            mValues = nvalues;
336        }
337
338        mKeys[pos] = key;
339        mValues[pos] = new WeakReference(value);
340        mSize = pos + 1;
341    }
342
343    private boolean hasReclaimedRefs() {
344        for (int i = 0 ; i < mSize ; i++) {
345            if (mValues[i].get() == null) { // DELETED.get() never returns null.
346                return true;
347            }
348        }
349
350        return false;
351    }
352
353    private static int binarySearch(int[] a, int start, int len, int key) {
354        int high = start + len, low = start - 1, guess;
355
356        while (high - low > 1) {
357            guess = (high + low) / 2;
358
359            if (a[guess] < key)
360                low = guess;
361            else
362                high = guess;
363        }
364
365        if (high == start + len)
366            return ~(start + len);
367        else if (a[high] == key)
368            return high;
369        else
370            return ~high;
371    }
372
373    private int[] mKeys;
374    private WeakReference<?>[] mValues;
375    private int mSize;
376}
377