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.tradefed.util;
18 
19 import java.util.Arrays;
20 
21 /**
22  * A class to represent a lightweight byte array.  The goal of this class is to avoid the
23  * significant space overhead of using Java classes.  See, for instance:
24  * http://benjchristensen.com/2008/05/27/java-memory-usage-ints/
25  */
26 public class ByteArrayList {
27     private byte[] mStorage;
28     /** arbitrarily choose 128 bytes as the default size */
29     private int mMaxBytes = 128;
30     private int mCurBytes = 0;
31     private float mGrowthFactor = 2.0f;
32 
33     /**
34      * Constructs an empty list with an initial capacity of 128 bytes and growth factor of 2.0
35      */
ByteArrayList()36     public ByteArrayList() {
37         mStorage = new byte[mMaxBytes];
38     }
39 
40     /**
41      * Constructs an empty list with the specified initial capacity, and with a growth factor of 2.0
42      *
43      * @param defaultSize The initial capacity of the list, in bytes
44      */
ByteArrayList(int defaultSize)45     public ByteArrayList(int defaultSize) {
46         this();
47         setSize(defaultSize);
48     }
49 
50     /**
51      * Constructs an empty list with the specified initial capacity and growth factor
52      *
53      * @param defaultSize The initial capacity of the list, in bytes
54      * @param growthFactor The factor by which the capacity is multiplied when the list needs to
55      *        auto-resize.  Must be {@code >= 1.1f}.
56      */
ByteArrayList(int defaultSize, float growthFactor)57     public ByteArrayList(int defaultSize, float growthFactor) {
58         this(defaultSize);
59 
60         if (growthFactor < 1.1f) {
61             throw new IllegalArgumentException("Growth factor must be at least 1.1");
62         }
63         mGrowthFactor = growthFactor;
64     }
65 
66     // Methods involved in managing size
67     /**
68      * Trims the capacity of this {@code ByteArrayList} instance to be the list's current size.
69      */
trimToSize()70     public void trimToSize() {
71         setSize(size());
72     }
73 
74     /**
75      * Increases the capacity of this {@code ByteArrayList} instance, if necessary, to ensure that
76      * it can hold at least the number of bytes specified by the minimum capacity argument.
77      *
78      * @param minCapacity The minimum capacity to ensure storage for, in bytes
79      */
ensureCapacity(int minCapacity)80     public void ensureCapacity(int minCapacity) {
81         if (minCapacity < 0) {
82             throw new IllegalArgumentException("Minimum capacity must be non-negative");
83         } else if (minCapacity <= mMaxBytes) {
84             return;
85         }
86 
87         int curSize = mMaxBytes;
88         if (curSize == 0) {
89             curSize = 1;
90         }
91         /**
92          * Need to grow the array
93          * Want: size * (growthFactor * k) >= minCapacity
94          *   So: k >= minCapacity / (size * growthFactor)
95          *       k = ceil(minCapacity / size / growthFactor)
96          */
97         int growthFactorMultiples = (int)Math.ceil(minCapacity / mGrowthFactor / curSize);
98         // newSize = oldSize * (growthFactor * k) >= minCapacity, from above
99         float newSize = curSize * mGrowthFactor * growthFactorMultiples;
100         setSize((int)Math.ceil(newSize));
101     }
102 
103     /**
104      * Sets the storage capacity of the internal storage array to the size specified, truncating the
105      * internal byte array if needed.
106      *
107      * @param size The new capacity of the list, in bytes
108      */
setSize(int size)109     void setSize(int size) {
110         if (size < 0) {
111             throw new IllegalArgumentException("New size must be non-negative");
112         } else if (size < mCurBytes) {
113             mCurBytes = size;
114         }
115         mMaxBytes = size;
116 
117         byte[] newStorage = new byte[size];
118         System.arraycopy(mStorage, 0, newStorage, 0, mCurBytes);
119         mStorage = newStorage;
120     }
121 
122     // Extra methods specific to this class
123     /**
124      * Returns a copy of the contents of this {@code ByteArrayList} as a {@code byte[]}.
125      *
126      * @return A {@code byte[]} copy of the list contents
127      */
getContents()128     public byte[] getContents() {
129         byte[] contents = new byte[mCurBytes];
130         System.arraycopy(mStorage, 0, contents, 0, mCurBytes);
131         return contents;
132     }
133 
134     // Implement useful methods from the List interface
135     /**
136      * Appends the specified element to the end of this list
137      *
138      * @param b The {@code byte} to append to the list
139      * @return {@code true}
140      */
add(byte b)141     public boolean add(byte b) {
142         ensureCapacity(mCurBytes + 1);
143         mStorage[mCurBytes] = b;
144         mCurBytes++;
145         return true;
146     }
147 
148     /**
149      * Appends the full contents of the supplied {@code byte[]} to the list.
150      *
151      * @param src The {@code byte[]} to append contents from
152      * @return {@code true}
153      */
addAll(byte[] src)154     public boolean addAll(byte[] src) {
155         if (src == null) {
156             throw new NullPointerException();
157         }
158 
159         return addAll(src, 0, src.length);
160     }
161 
162     /**
163      * Appends the specified contents of the supplied {@code byte[]} to the list.
164      *
165      * @param src The {@code byte[]} to append contents from
166      * @param srcOffset The index of first element of {@code src} to append
167      * @param length The quantity of bytes to append to the list
168      * @return {@code true}
169      */
addAll(byte[] src, int srcOffset, int length)170     public boolean addAll(byte[] src, int srcOffset, int length) {
171         if (src == null) {
172             throw new NullPointerException();
173         }
174 
175         ensureCapacity(mCurBytes + length);
176         System.arraycopy(src, srcOffset, mStorage, mCurBytes, length);
177         mCurBytes += length;
178         return true;
179     }
180 
181     /**
182      * Appends the full contents of the supplied {@link ByteArrayList} to the list.
183      *
184      * @param src The {@link ByteArrayList} to append contents from
185      * @return {@code true}
186      */
addall(ByteArrayList src)187     public boolean addall(ByteArrayList src) {
188         if (src == null) {
189             throw new NullPointerException();
190         }
191 
192         return addAll(src.getContents());
193     }
194 
195     /**
196      * Removes all of the elements from this list.
197      */
clear()198     public void clear() {
199         mCurBytes = 0;
200     }
201 
202     /**
203      * {@inheritDoc}
204      */
205     @Override
equals(Object other)206     public boolean equals(Object other) {
207         if (!(other instanceof ByteArrayList)) {
208             return false;
209         }
210         ByteArrayList otherList = (ByteArrayList) other;
211 
212         if (otherList.size() != size()) {
213             return false;
214         }
215         for (int i = 0; i < size(); i++) {
216             if (otherList.get(i) != get(i)) {
217                 return false;
218             }
219         }
220         return true;
221     }
222 
223     /**
224      * {@inheritDoc}
225      */
226     @Override
hashCode()227     public int hashCode() {
228         return Arrays.hashCode(mStorage);
229     }
230 
231     /**
232      * Returns {@code true} if this list contains no bytes
233      */
isEmpty()234     public boolean isEmpty() {
235         return size() == 0;
236     }
237 
238     /**
239      * Returns the element at the specified position in this list
240      *
241      * @param idx The index to return
242      */
get(int idx)243     public byte get(int idx) {
244         if (idx < 0 || idx >= size()) {
245             throw new IndexOutOfBoundsException();
246         }
247         return mStorage[idx];
248     }
249 
250     /**
251      * Replaces the element at the specified position in this list with the specified element
252      *
253      * @param idx The index to replace
254      * @param b The {@code byte} to replace at that index
255      */
set(int idx, byte b)256     public byte set(int idx, byte b) {
257         if (idx < 0 || idx >= size()) {
258             throw new IndexOutOfBoundsException();
259         }
260         byte curVal = mStorage[idx];
261         mStorage[idx] = b;
262         return curVal;
263     }
264 
265     /**
266      * Returns the number of bytes in this list
267      */
size()268     public int size() {
269         return mCurBytes;
270     }
271 
272     // methods to facilitate testing
getMaxSize()273     int getMaxSize() {
274         return mMaxBytes;
275     }
276 }
277 
278