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 com.android.layoutlib.bridge.util; 18 19 20 import com.android.internal.util.ArrayUtils; 21 22 import android.util.SparseArray; 23 24 import 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") 42 public 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