1 // Protocol Buffers - Google's data interchange format 2 // Copyright 2014 Google Inc. All rights reserved. 3 // https://developers.google.com/protocol-buffers/ 4 // 5 // Redistribution and use in source and binary forms, with or without 6 // modification, are permitted provided that the following conditions are 7 // met: 8 // 9 // * Redistributions of source code must retain the above copyright 10 // notice, this list of conditions and the following disclaimer. 11 // * Redistributions in binary form must reproduce the above 12 // copyright notice, this list of conditions and the following disclaimer 13 // in the documentation and/or other materials provided with the 14 // distribution. 15 // * Neither the name of Google Inc. nor the names of its 16 // contributors may be used to endorse or promote products derived from 17 // this software without specific prior written permission. 18 // 19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 31 package com.google.protobuf.nano; 32 33 34 /** 35 * A custom version of {@code android.util.SparseArray} with the minimal API 36 * for storing {@link FieldData} objects. 37 * 38 * <p>This class is an internal implementation detail of nano and should not 39 * be called directly by clients. 40 * 41 * Based on {@code android.support.v4.util.SpareArrayCompat}. 42 */ 43 public final class FieldArray implements Cloneable { 44 private static final FieldData DELETED = new FieldData(); 45 private boolean mGarbage = false; 46 47 private int[] mFieldNumbers; 48 private FieldData[] mData; 49 private int mSize; 50 51 /** 52 * Creates a new FieldArray containing no fields. 53 */ FieldArray()54 FieldArray() { 55 this(10); 56 } 57 58 /** 59 * Creates a new FieldArray containing no mappings that will not 60 * require any additional memory allocation to store the specified 61 * number of mappings. 62 */ FieldArray(int initialCapacity)63 FieldArray(int initialCapacity) { 64 initialCapacity = idealIntArraySize(initialCapacity); 65 mFieldNumbers = new int[initialCapacity]; 66 mData = new FieldData[initialCapacity]; 67 mSize = 0; 68 } 69 70 /** 71 * Gets the FieldData mapped from the specified fieldNumber, or <code>null</code> 72 * if no such mapping has been made. 73 */ get(int fieldNumber)74 FieldData get(int fieldNumber) { 75 int i = binarySearch(fieldNumber); 76 77 if (i < 0 || mData[i] == DELETED) { 78 return null; 79 } else { 80 return mData[i]; 81 } 82 } 83 84 /** 85 * Removes the data from the specified fieldNumber, if there was any. 86 */ remove(int fieldNumber)87 void remove(int fieldNumber) { 88 int i = binarySearch(fieldNumber); 89 90 if (i >= 0 && mData[i] != DELETED) { 91 mData[i] = DELETED; 92 mGarbage = true; 93 } 94 } 95 gc()96 private void gc() { 97 int n = mSize; 98 int o = 0; 99 int[] keys = mFieldNumbers; 100 FieldData[] values = mData; 101 102 for (int i = 0; i < n; i++) { 103 FieldData val = values[i]; 104 105 if (val != DELETED) { 106 if (i != o) { 107 keys[o] = keys[i]; 108 values[o] = val; 109 values[i] = null; 110 } 111 112 o++; 113 } 114 } 115 116 mGarbage = false; 117 mSize = o; 118 } 119 120 /** 121 * Adds a mapping from the specified fieldNumber to the specified data, 122 * replacing the previous mapping if there was one. 123 */ put(int fieldNumber, FieldData data)124 void put(int fieldNumber, FieldData data) { 125 int i = binarySearch(fieldNumber); 126 127 if (i >= 0) { 128 mData[i] = data; 129 } else { 130 i = ~i; 131 132 if (i < mSize && mData[i] == DELETED) { 133 mFieldNumbers[i] = fieldNumber; 134 mData[i] = data; 135 return; 136 } 137 138 if (mGarbage && mSize >= mFieldNumbers.length) { 139 gc(); 140 141 // Search again because indices may have changed. 142 i = ~ binarySearch(fieldNumber); 143 } 144 145 if (mSize >= mFieldNumbers.length) { 146 int n = idealIntArraySize(mSize + 1); 147 148 int[] nkeys = new int[n]; 149 FieldData[] nvalues = new FieldData[n]; 150 151 System.arraycopy(mFieldNumbers, 0, nkeys, 0, mFieldNumbers.length); 152 System.arraycopy(mData, 0, nvalues, 0, mData.length); 153 154 mFieldNumbers = nkeys; 155 mData = nvalues; 156 } 157 158 if (mSize - i != 0) { 159 System.arraycopy(mFieldNumbers, i, mFieldNumbers, i + 1, mSize - i); 160 System.arraycopy(mData, i, mData, i + 1, mSize - i); 161 } 162 163 mFieldNumbers[i] = fieldNumber; 164 mData[i] = data; 165 mSize++; 166 } 167 } 168 169 /** 170 * Returns the number of key-value mappings that this FieldArray 171 * currently stores. 172 */ size()173 int size() { 174 if (mGarbage) { 175 gc(); 176 } 177 178 return mSize; 179 } 180 isEmpty()181 public boolean isEmpty() { 182 return size() == 0; 183 } 184 185 /** 186 * Given an index in the range <code>0...size()-1</code>, returns 187 * the value from the <code>index</code>th key-value mapping that this 188 * FieldArray stores. 189 */ dataAt(int index)190 FieldData dataAt(int index) { 191 if (mGarbage) { 192 gc(); 193 } 194 195 return mData[index]; 196 } 197 198 @Override equals(Object o)199 public boolean equals(Object o) { 200 if (o == this) { 201 return true; 202 } 203 if (!(o instanceof FieldArray)) { 204 return false; 205 } 206 207 FieldArray other = (FieldArray) o; 208 if (size() != other.size()) { // size() will call gc() if necessary. 209 return false; 210 } 211 return arrayEquals(mFieldNumbers, other.mFieldNumbers, mSize) && 212 arrayEquals(mData, other.mData, mSize); 213 } 214 215 @Override hashCode()216 public int hashCode() { 217 if (mGarbage) { 218 gc(); 219 } 220 int result = 17; 221 for (int i = 0; i < mSize; i++) { 222 result = 31 * result + mFieldNumbers[i]; 223 result = 31 * result + mData[i].hashCode(); 224 } 225 return result; 226 } 227 idealIntArraySize(int need)228 private int idealIntArraySize(int need) { 229 return idealByteArraySize(need * 4) / 4; 230 } 231 idealByteArraySize(int need)232 private int idealByteArraySize(int need) { 233 for (int i = 4; i < 32; i++) 234 if (need <= (1 << i) - 12) 235 return (1 << i) - 12; 236 237 return need; 238 } 239 binarySearch(int value)240 private int binarySearch(int value) { 241 int lo = 0; 242 int hi = mSize - 1; 243 244 while (lo <= hi) { 245 int mid = (lo + hi) >>> 1; 246 int midVal = mFieldNumbers[mid]; 247 248 if (midVal < value) { 249 lo = mid + 1; 250 } else if (midVal > value) { 251 hi = mid - 1; 252 } else { 253 return mid; // value found 254 } 255 } 256 return ~lo; // value not present 257 } 258 arrayEquals(int[] a, int[] b, int size)259 private boolean arrayEquals(int[] a, int[] b, int size) { 260 for (int i = 0; i < size; i++) { 261 if (a[i] != b[i]) { 262 return false; 263 } 264 } 265 return true; 266 } 267 arrayEquals(FieldData[] a, FieldData[] b, int size)268 private boolean arrayEquals(FieldData[] a, FieldData[] b, int size) { 269 for (int i = 0; i < size; i++) { 270 if (!a[i].equals(b[i])) { 271 return false; 272 } 273 } 274 return true; 275 } 276 277 @Override clone()278 public final FieldArray clone() { 279 // Trigger GC so we compact and don't copy DELETED elements. 280 int size = size(); 281 FieldArray clone = new FieldArray(size); 282 System.arraycopy(mFieldNumbers, 0, clone.mFieldNumbers, 0, size); 283 for (int i = 0; i < size; i++) { 284 if (mData[i] != null) { 285 clone.mData[i] = mData[i].clone(); 286 } 287 } 288 clone.mSize = size; 289 return clone; 290 } 291 } 292