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;
21import com.android.internal.util.GrowingArrayUtils;
22
23import android.util.SparseArray;
24
25import java.lang.ref.WeakReference;
26
27/**
28 * This is a custom {@link SparseArray} that uses {@link WeakReference} around the objects added
29 * to it. When the array is compacted, not only deleted indices but also empty references
30 * are removed, making the array efficient at removing references that were reclaimed.
31 *
32 * The code is taken from {@link SparseArray} directly and adapted to use weak references.
33 *
34 * Because our usage means that we never actually call {@link #remove(long)} or
35 * {@link #delete(long)}, we must manually check if there are reclaimed references to
36 * trigger an internal compact step (which is normally only triggered when an item is manually
37 * removed).
38 *
39 * SparseArrays map integral values to Objects.  Unlike a normal array of Objects,
40 * there can be gaps in the indices.  It is intended to be more efficient
41 * than using a HashMap to map Integers (or Longs) to Objects.
42 */
43@SuppressWarnings("unchecked")
44public class SparseWeakArray<E> {
45
46    private static final Object DELETED_REF = new Object();
47    private static final WeakReference<?> DELETED = new WeakReference(DELETED_REF);
48    private boolean mGarbage = false;
49
50    /**
51     * Creates a new SparseArray containing no mappings.
52     */
53    public SparseWeakArray() {
54        this(10);
55    }
56
57    /**
58     * Creates a new SparseArray containing no mappings that will not
59     * require any additional memory allocation to store the specified
60     * number of mappings.
61     */
62    public SparseWeakArray(int initialCapacity) {
63        mKeys = ArrayUtils.newUnpaddedLongArray(initialCapacity);
64        mValues = new WeakReference[mKeys.length];
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(long 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(long 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(long 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(long)}.
106     */
107    public void remove(long 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        long[] 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
146    /**
147     * Adds a mapping from the specified key to the specified value,
148     * replacing the previous mapping from the specified key if there
149     * was one.
150     */
151    public void put(long key, E value) {
152        int i = binarySearch(mKeys, 0, mSize, key);
153
154        if (i >= 0) {
155            mValues[i] = new WeakReference(value);
156        } else {
157            i = ~i;
158
159            if (i < mSize && (mValues[i] == DELETED || mValues[i].get() == null)) {
160                mKeys[i] = key;
161                mValues[i] = new WeakReference(value);
162                return;
163            }
164
165            if (mSize >= mKeys.length && (mGarbage || hasReclaimedRefs())) {
166                gc();
167
168                // Search again because indices may have changed.
169                i = ~binarySearch(mKeys, 0, mSize, key);
170            }
171
172            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
173            mValues = GrowingArrayUtils.insert(mValues, mSize, i, new WeakReference(value));
174            mSize++;
175        }
176    }
177
178    /**
179     * Returns the number of key-value mappings that this SparseArray
180     * currently stores.
181     */
182    public int size() {
183        if (mGarbage) {
184            gc();
185        }
186
187        return mSize;
188    }
189
190    /**
191     * Given an index in the range <code>0...size()-1</code>, returns
192     * the key from the <code>index</code>th key-value mapping that this
193     * SparseArray stores.
194     */
195    public long keyAt(int index) {
196        if (mGarbage) {
197            gc();
198        }
199
200        return mKeys[index];
201    }
202
203    /**
204     * Given an index in the range <code>0...size()-1</code>, returns
205     * the value from the <code>index</code>th key-value mapping that this
206     * SparseArray stores.
207     */
208    public E valueAt(int index) {
209        if (mGarbage) {
210            gc();
211        }
212
213        return (E) mValues[index].get();
214    }
215
216    /**
217     * Given an index in the range <code>0...size()-1</code>, sets a new
218     * value for the <code>index</code>th key-value mapping that this
219     * SparseArray stores.
220     */
221    public void setValueAt(int index, E value) {
222        if (mGarbage) {
223            gc();
224        }
225
226        mValues[index] = new WeakReference(value);
227    }
228
229    /**
230     * Returns the index for which {@link #keyAt} would return the
231     * specified key, or a negative number if the specified
232     * key is not mapped.
233     */
234    public int indexOfKey(long key) {
235        if (mGarbage) {
236            gc();
237        }
238
239        return binarySearch(mKeys, 0, mSize, key);
240    }
241
242    /**
243     * Returns an index for which {@link #valueAt} would return the
244     * specified key, or a negative number if no keys map to the
245     * specified value.
246     * Beware that this is a linear search, unlike lookups by key,
247     * and that multiple keys can map to the same value and this will
248     * find only one of them.
249     */
250    public int indexOfValue(E value) {
251        if (mGarbage) {
252            gc();
253        }
254
255        for (int i = 0; i < mSize; i++)
256            if (mValues[i].get() == value)
257                return i;
258
259        return -1;
260    }
261
262    /**
263     * Removes all key-value mappings from this SparseArray.
264     */
265    public void clear() {
266        int n = mSize;
267        WeakReference<?>[] values = mValues;
268
269        for (int i = 0; i < n; i++) {
270            values[i] = null;
271        }
272
273        mSize = 0;
274        mGarbage = false;
275    }
276
277    /**
278     * Puts a key/value pair into the array, optimizing for the case where
279     * the key is greater than all existing keys in the array.
280     */
281    public void append(long key, E value) {
282        if (mSize != 0 && key <= mKeys[mSize - 1]) {
283            put(key, value);
284            return;
285        }
286
287        if (mSize >= mKeys.length && (mGarbage || hasReclaimedRefs())) {
288            gc();
289        }
290
291        mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
292        mValues = GrowingArrayUtils.append(mValues, mSize, new WeakReference(value));
293        mSize++;
294    }
295
296    private boolean hasReclaimedRefs() {
297        for (int i = 0 ; i < mSize ; i++) {
298            if (mValues[i].get() == null) { // DELETED.get() never returns null.
299                return true;
300            }
301        }
302
303        return false;
304    }
305
306    private static int binarySearch(long[] a, int start, int len, long key) {
307        int high = start + len, low = start - 1, guess;
308
309        while (high - low > 1) {
310            guess = (high + low) / 2;
311
312            if (a[guess] < key)
313                low = guess;
314            else
315                high = guess;
316        }
317
318        if (high == start + len)
319            return ~(start + len);
320        else if (a[high] == key)
321            return high;
322        else
323            return ~high;
324    }
325
326    private long[] mKeys;
327    private WeakReference<?>[] mValues;
328    private int mSize;
329}
330