• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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