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