1 /* 2 * Copyright 2013, Google LLC 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google LLC nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 package com.android.tools.smali.util; 32 33 import java.util.Arrays; 34 import java.util.Collections; 35 import java.util.List; 36 37 /** 38 * SparseArrays map integers to Objects. Unlike a normal array of Objects, 39 * there can be gaps in the indices. It is intended to be more efficient 40 * than using a HashMap to map Integers to Objects. 41 */ 42 public class SparseArray<E> { 43 private static final Object DELETED = new Object(); 44 private boolean mGarbage = false; 45 46 /** 47 * Creates a new SparseArray containing no mappings. 48 */ SparseArray()49 public SparseArray() { 50 this(10); 51 } 52 53 /** 54 * Creates a new SparseArray containing no mappings that will not 55 * require any additional memory allocation to store the specified 56 * number of mappings. 57 */ SparseArray(int initialCapacity)58 public SparseArray(int initialCapacity) { 59 mKeys = new int[initialCapacity]; 60 mValues = new Object[initialCapacity]; 61 mSize = 0; 62 } 63 64 /** 65 * Gets the Object mapped from the specified key, or <code>null</code> 66 * if no such mapping has been made. 67 */ get(int key)68 public E get(int key) { 69 return get(key, null); 70 } 71 72 /** 73 * Gets the Object mapped from the specified key, or the specified Object 74 * if no such mapping has been made. 75 */ get(int key, E valueIfKeyNotFound)76 public E get(int key, E valueIfKeyNotFound) { 77 int i = binarySearch(mKeys, 0, mSize, key); 78 79 if (i < 0 || mValues[i] == DELETED) { 80 return valueIfKeyNotFound; 81 } else { 82 return (E) mValues[i]; 83 } 84 } 85 86 /** 87 * Removes the mapping from the specified key, if there was any. 88 */ delete(int key)89 public void delete(int key) { 90 int i = binarySearch(mKeys, 0, mSize, key); 91 92 if (i >= 0) { 93 if (mValues[i] != DELETED) { 94 mValues[i] = DELETED; 95 mGarbage = true; 96 } 97 } 98 } 99 100 /** 101 * Alias for {@link #delete(int)}. 102 */ remove(int key)103 public void remove(int key) { 104 delete(key); 105 } 106 gc()107 private void gc() { 108 // Log.e("SparseArray", "gc start with " + mSize); 109 110 int n = mSize; 111 int o = 0; 112 int[] keys = mKeys; 113 Object[] values = mValues; 114 115 for (int i = 0; i < n; i++) { 116 Object val = values[i]; 117 118 if (val != DELETED) { 119 if (i != o) { 120 keys[o] = keys[i]; 121 values[o] = val; 122 } 123 124 o++; 125 } 126 } 127 128 mGarbage = false; 129 mSize = o; 130 131 // Log.e("SparseArray", "gc end with " + mSize); 132 } 133 134 /** 135 * Adds a mapping from the specified key to the specified value, 136 * replacing the previous mapping from the specified key if there 137 * was one. 138 */ put(int key, E value)139 public void put(int key, E value) { 140 int i = binarySearch(mKeys, 0, mSize, key); 141 142 if (i >= 0) { 143 mValues[i] = value; 144 } else { 145 i = ~i; 146 147 if (i < mSize && mValues[i] == DELETED) { 148 mKeys[i] = key; 149 mValues[i] = value; 150 return; 151 } 152 153 if (mGarbage && mSize >= mKeys.length) { 154 gc(); 155 156 // Search again because indices may have changed. 157 i = ~binarySearch(mKeys, 0, mSize, key); 158 } 159 160 if (mSize >= mKeys.length) { 161 int n = Math.max(mSize + 1, mKeys.length * 2); 162 163 int[] nkeys = new int[n]; 164 Object[] nvalues = new Object[n]; 165 166 // Log.e("SparseArray", "grow " + mKeys.length + " to " + n); 167 System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); 168 System.arraycopy(mValues, 0, nvalues, 0, mValues.length); 169 170 mKeys = nkeys; 171 mValues = nvalues; 172 } 173 174 if (mSize - i != 0) { 175 // Log.e("SparseArray", "move " + (mSize - i)); 176 System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i); 177 System.arraycopy(mValues, i, mValues, i + 1, mSize - i); 178 } 179 180 mKeys[i] = key; 181 mValues[i] = value; 182 mSize++; 183 } 184 } 185 186 /** 187 * Returns the number of key-value mappings that this SparseArray 188 * currently stores. 189 */ size()190 public int size() { 191 if (mGarbage) { 192 gc(); 193 } 194 195 return mSize; 196 } 197 198 /** 199 * Given an index in the range <code>0...size()-1</code>, returns 200 * the key from the <code>index</code>th key-value mapping that this 201 * SparseArray stores. 202 */ keyAt(int index)203 public int keyAt(int index) { 204 if (mGarbage) { 205 gc(); 206 } 207 208 return mKeys[index]; 209 } 210 211 /** 212 * Given an index in the range <code>0...size()-1</code>, returns 213 * the value from the <code>index</code>th key-value mapping that this 214 * SparseArray stores. 215 */ valueAt(int index)216 public E valueAt(int index) { 217 if (mGarbage) { 218 gc(); 219 } 220 221 return (E) mValues[index]; 222 } 223 224 /** 225 * Given an index in the range <code>0...size()-1</code>, sets a new 226 * value for the <code>index</code>th key-value mapping that this 227 * SparseArray stores. 228 */ setValueAt(int index, E value)229 public void setValueAt(int index, E value) { 230 if (mGarbage) { 231 gc(); 232 } 233 234 mValues[index] = value; 235 } 236 237 /** 238 * Returns the index for which {@link #keyAt} would return the 239 * specified key, or a negative number if the specified 240 * key is not mapped. 241 */ indexOfKey(int key)242 public int indexOfKey(int key) { 243 if (mGarbage) { 244 gc(); 245 } 246 247 return binarySearch(mKeys, 0, mSize, key); 248 } 249 250 /** 251 * Returns an index for which {@link #valueAt} would return the 252 * specified key, or a negative number if no keys map to the 253 * specified value. 254 * Beware that this is a linear search, unlike lookups by key, 255 * and that multiple keys can map to the same value and this will 256 * find only one of them. 257 */ indexOfValue(E value)258 public int indexOfValue(E value) { 259 if (mGarbage) { 260 gc(); 261 } 262 263 for (int i = 0; i < mSize; i++) 264 if (mValues[i] == value) 265 return i; 266 267 return -1; 268 } 269 270 /** 271 * Removes all key-value mappings from this SparseArray. 272 */ clear()273 public void clear() { 274 int n = mSize; 275 Object[] values = mValues; 276 277 for (int i = 0; i < n; i++) { 278 values[i] = null; 279 } 280 281 mSize = 0; 282 mGarbage = false; 283 } 284 285 /** 286 * Puts a key/value pair into the array, optimizing for the case where 287 * the key is greater than all existing keys in the array. 288 */ append(int key, E value)289 public void append(int key, E value) { 290 if (mSize != 0 && key <= mKeys[mSize - 1]) { 291 put(key, value); 292 return; 293 } 294 295 if (mGarbage && mSize >= mKeys.length) { 296 gc(); 297 } 298 299 int pos = mSize; 300 if (pos >= mKeys.length) { 301 int n = Math.max(pos + 1, mKeys.length * 2); 302 303 int[] nkeys = new int[n]; 304 Object[] nvalues = new Object[n]; 305 306 // Log.e("SparseArray", "grow " + mKeys.length + " to " + n); 307 System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); 308 System.arraycopy(mValues, 0, nvalues, 0, mValues.length); 309 310 mKeys = nkeys; 311 mValues = nvalues; 312 } 313 314 mKeys[pos] = key; 315 mValues[pos] = value; 316 mSize = pos + 1; 317 } 318 319 /** 320 * Increases the size of the underlying storage if needed, to ensure that it can 321 * hold the specified number of items without having to allocate additional memory 322 * @param capacity the number of items 323 */ ensureCapacity(int capacity)324 public void ensureCapacity(int capacity) { 325 if (mGarbage && mSize >= mKeys.length) { 326 gc(); 327 } 328 329 if (mKeys.length < capacity) { 330 int[] nkeys = new int[capacity]; 331 Object[] nvalues = new Object[capacity]; 332 333 System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); 334 System.arraycopy(mValues, 0, nvalues, 0, mValues.length); 335 336 mKeys = nkeys; 337 mValues = nvalues; 338 } 339 } 340 binarySearch(int[] a, int start, int len, int key)341 private static int binarySearch(int[] a, int start, int len, int key) { 342 int high = start + len, low = start - 1, guess; 343 344 while (high - low > 1) { 345 guess = (high + low) / 2; 346 347 if (a[guess] < key) 348 low = guess; 349 else 350 high = guess; 351 } 352 353 if (high == start + len) 354 return ~(start + len); 355 else if (a[high] == key) 356 return high; 357 else 358 return ~high; 359 } 360 361 /** 362 * @return a read-only list of the values in this SparseArray which are in ascending order, based on their 363 * associated key 364 */ getValues()365 public List<E> getValues() { 366 return Collections.unmodifiableList(Arrays.asList((E[])mValues)); 367 } 368 369 private int[] mKeys; 370 private Object[] mValues; 371 private int mSize; 372 } 373