View Javadoc
1   package mockit.asm.util;
2   
3   import edu.umd.cs.findbugs.annotations.NonNull;
4   
5   import org.checkerframework.checker.index.qual.NonNegative;
6   
7   /**
8    * A dynamically extensible vector of bytes. This class is roughly equivalent to a DataOutputStream on top of a
9    * ByteArrayOutputStream, but is more efficient.
10   */
11  @SuppressWarnings({ "NumericCastThatLosesPrecision", "CharUsedInArithmeticContext" })
12  public final class ByteVector {
13      /**
14       * The content of this vector.
15       */
16      @NonNull
17      private byte[] data;
18  
19      /**
20       * Actual number of bytes in this vector.
21       */
22      @NonNegative
23      private int length;
24  
25      /**
26       * Constructs a new ByteVector with a default initial size.
27       */
28      public ByteVector() {
29          data = new byte[64];
30      }
31  
32      /**
33       * Constructs a new ByteVector with the given initial size.
34       */
35      public ByteVector(@NonNegative int initialSize) {
36          data = new byte[initialSize];
37      }
38  
39      /**
40       * Returns the byte {@link #data}.
41       */
42      @NonNull
43      public byte[] getData() {
44          return data;
45      }
46  
47      /**
48       * Returns the {@link #length} of this vector, in bytes.
49       */
50      @NonNegative
51      public int getLength() {
52          return length;
53      }
54  
55      /**
56       * Sets the {@link #length} of this vector, in bytes.
57       */
58      public void setLength(@NonNegative int length) {
59          this.length = length;
60      }
61  
62      /**
63       * Puts a byte into this byte vector. The byte vector is automatically enlarged if necessary.
64       *
65       * @return this byte vector
66       */
67      @NonNull
68      public ByteVector putByte(int b) {
69          int len = getLengthEnlargingIfNeeded(1);
70          data[len] = (byte) b;
71          len++;
72          length = len;
73          return this;
74      }
75  
76      @NonNegative
77      private int getLengthEnlargingIfNeeded(@NonNegative int bytesToAdd) {
78          int len = length;
79  
80          if (len + bytesToAdd > data.length) {
81              enlarge(bytesToAdd);
82          }
83  
84          return len;
85      }
86  
87      /**
88       * Enlarge this byte vector so that it can receive n more bytes.
89       *
90       * @param size
91       *            number of additional bytes that this byte vector should be able to receive
92       */
93      private void enlarge(@NonNegative int size) {
94          int length1 = 2 * data.length;
95          int length2 = length + size;
96          byte[] newData = new byte[length1 > length2 ? length1 : length2];
97          System.arraycopy(data, 0, newData, 0, length);
98          data = newData;
99      }
100 
101     /**
102      * Puts two bytes into this byte vector. The byte vector is automatically enlarged if necessary.
103      *
104      * @return this byte vector
105      */
106     @NonNull
107     public ByteVector put11(int b1, int b2) {
108         int len = getLengthEnlargingIfNeeded(2);
109         byte[] bytes = data;
110         bytes[len] = (byte) b1;
111         len++;
112         bytes[len] = (byte) b2;
113         len++;
114         length = len;
115         return this;
116     }
117 
118     /**
119      * Puts a short into this byte vector. The byte vector is automatically enlarged if necessary.
120      *
121      * @return this byte vector
122      */
123     @NonNull
124     public ByteVector putShort(int s) {
125         return put11(s >>> 8, s);
126     }
127 
128     /**
129      * Puts a byte and a short into this byte vector. The byte vector is automatically enlarged if necessary.
130      *
131      * @return this byte vector
132      */
133     @NonNull
134     public ByteVector put12(int b, int s) {
135         int len = getLengthEnlargingIfNeeded(3);
136         byte[] bytes = data;
137         bytes[len] = (byte) b;
138         len++;
139         bytes[len] = (byte) (s >>> 8);
140         len++;
141         bytes[len] = (byte) s;
142         len++;
143         length = len;
144         return this;
145     }
146 
147     /**
148      * Puts an int into this byte vector. The byte vector is automatically enlarged if necessary.
149      *
150      * @return this byte vector
151      */
152     @NonNull
153     public ByteVector putInt(int i) {
154         int len = getLengthEnlargingIfNeeded(4);
155         byte[] bytes = data;
156         bytes[len] = (byte) (i >>> 24);
157         len++;
158         bytes[len] = (byte) (i >>> 16);
159         len++;
160         bytes[len] = (byte) (i >>> 8);
161         len++;
162         bytes[len] = (byte) i;
163         len++;
164         length = len;
165         return this;
166     }
167 
168     /**
169      * Puts a long into this byte vector. The byte vector is automatically enlarged if necessary.
170      */
171     public void putLong(long l) {
172         int i1 = (int) (l >>> 32);
173         int i2 = (int) l;
174         putInt(i1);
175         putInt(i2);
176     }
177 
178     /**
179      * Puts an UTF8 string into this byte vector. The byte vector is automatically enlarged if necessary.
180      *
181      * @param utf8String
182      *            a String whose UTF8 encoded length must be less than 65536
183      */
184     public void putUTF8(@NonNull String utf8String) {
185         int charLength = utf8String.length();
186 
187         if (charLength > 65535) {
188             throw new IllegalArgumentException("String too long: " + charLength);
189         }
190 
191         int len = getLengthEnlargingIfNeeded(2 + charLength);
192         byte[] characters = data;
193 
194         // Optimistic algorithm: instead of computing the byte length and then serializing the string (which requires
195         // two loops), we assume the byte length is equal to char length (which is the most frequent case), and we start
196         // serializing the string right away.
197         // During the serialization, if we find that this assumption is wrong, we continue with the general method.
198         characters[len] = (byte) (charLength >>> 8);
199         len++;
200         characters[len] = (byte) charLength;
201         len++;
202 
203         for (int i = 0; i < charLength; i++) {
204             char c = utf8String.charAt(i);
205 
206             if (c >= '\001' && c <= '\177') {
207                 characters[len] = (byte) c;
208                 len++;
209             } else {
210                 length = len;
211                 encodeUTF8(utf8String, i);
212             }
213         }
214 
215         length = len;
216     }
217 
218     /**
219      * Puts an UTF8 string into this byte vector. The byte vector is automatically enlarged if necessary. The string
220      * length is encoded in two bytes before the encoded characters, if there is space for that (i.e. if this.length - i
221      * - 2 >= 0).
222      *
223      * @param utf8String
224      *            the String to encode
225      * @param startIndex
226      *            the index of the first character to encode. The previous characters are supposed to have already been
227      *            encoded, using only one byte per character.
228      */
229     private void encodeUTF8(@NonNull String utf8String, @NonNegative int startIndex) {
230         int byteLength = computeByteLength(utf8String, startIndex);
231 
232         if (byteLength > 65535) {
233             throw new IllegalArgumentException("String too long for UTF8 encoding: " + byteLength);
234         }
235 
236         int start = length - startIndex - 2;
237 
238         if (start >= 0) {
239             data[start] = (byte) (byteLength >>> 8);
240             data[start + 1] = (byte) byteLength;
241         }
242 
243         if (length + byteLength - startIndex > data.length) {
244             enlarge(byteLength - startIndex);
245         }
246 
247         putEncodedCharacters(utf8String, startIndex);
248     }
249 
250     @NonNegative
251     private static int computeByteLength(@NonNull String utf8String, @NonNegative int startIndex) {
252         int byteLength = startIndex;
253 
254         for (int i = startIndex, n = utf8String.length(); i < n; i++) {
255             char c = utf8String.charAt(i);
256 
257             if (c >= '\001' && c <= '\177') {
258                 byteLength++;
259             } else if (c > '\u07FF') {
260                 byteLength += 3;
261             } else {
262                 byteLength += 2;
263             }
264         }
265 
266         return byteLength;
267     }
268 
269     private void putEncodedCharacters(@NonNull String utf8String, @NonNegative int startIndex) {
270         byte[] characters = data;
271         int len = length;
272 
273         for (int i = startIndex, n = utf8String.length(); i < n; i++) {
274             char c = utf8String.charAt(i);
275 
276             if (c >= '\001' && c <= '\177') {
277                 characters[len] = (byte) c;
278             } else {
279                 if (c > '\u07FF') {
280                     characters[len] = (byte) (0xE0 | c >> 12 & 0xF);
281                     len++;
282                     characters[len] = (byte) (0x80 | c >> 6 & 0x3F);
283                 } else {
284                     characters[len] = (byte) (0xC0 | c >> 6 & 0x1F);
285                 }
286                 len++;
287 
288                 characters[len] = (byte) (0x80 | c & 0x3F);
289             }
290             len++;
291         }
292 
293         length = len;
294     }
295 
296     /**
297      * Puts an array of bytes into this byte vector. The byte vector is automatically enlarged if necessary.
298      *
299      * @param bytes
300      *            an array of bytes
301      * @param offset
302      *            index of the first byte of code that must be copied
303      * @param numBytes
304      *            number of bytes of code that must be copied
305      */
306     public void putByteArray(@NonNull byte[] bytes, @NonNegative int offset, @NonNegative int numBytes) {
307         int len = getLengthEnlargingIfNeeded(numBytes);
308         System.arraycopy(bytes, offset, data, len, numBytes);
309         length += numBytes;
310     }
311 
312     public void putByteVector(@NonNull ByteVector another) {
313         putByteArray(another.data, 0, another.length);
314     }
315 
316     public void roundUpLength() {
317         int newLength = (4 - length % 4) % 4;
318         getLengthEnlargingIfNeeded(newLength);
319         length += newLength;
320     }
321 }