blob: 841829e502e16f4f375297085539a00df415d9fa [file] [log] [blame]
Xiao Ma6f95cef2020-09-08 19:16:15 +09001/*
2 * Copyright (C) 2020 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
17package android.net.util;
18
19import android.annotation.NonNull;
20
21import java.lang.annotation.ElementType;
22import java.lang.annotation.Retention;
23import java.lang.annotation.RetentionPolicy;
24import java.lang.annotation.Target;
25import java.lang.reflect.Constructor;
26import java.lang.reflect.InvocationTargetException;
27import java.lang.reflect.Modifier;
28import java.math.BigInteger;
29import java.nio.BufferUnderflowException;
30import java.nio.ByteBuffer;
31import java.nio.ByteOrder;
Xiao Maa76690a2020-11-04 10:41:04 +090032import java.util.concurrent.ConcurrentHashMap;
Xiao Ma6f95cef2020-09-08 19:16:15 +090033
34/**
35 * Define a generic class that helps to parse the structured message.
36 *
37 * Example usage:
38 *
39 * // C-style NduserOption message header definition in the kernel:
40 * struct nduseroptmsg {
41 * unsigned char nduseropt_family;
42 * unsigned char nduseropt_pad1;
43 * unsigned short nduseropt_opts_len;
44 * int nduseropt_ifindex;
45 * __u8 nduseropt_icmp_type;
46 * __u8 nduseropt_icmp_code;
47 * unsigned short nduseropt_pad2;
48 * unsigned int nduseropt_pad3;
49 * }
50 *
51 * - Declare a subclass with explicit constructor or not which extends from this class to parse
52 * NduserOption header from raw bytes array.
53 *
54 * - Option w/ explicit constructor:
55 * static class NduserOptHeaderMessage extends Struct {
56 * @Field(order = 0, type = Type.U8, padding = 1)
57 * final short family;
58 * @Field(order = 1, type = Type.U16)
59 * final int len;
60 * @Field(order = 2, type = Type.S32)
61 * final int ifindex;
62 * @Field(order = 3, type = Type.U8)
63 * final short type;
64 * @Field(order = 4, type = Type.U8, padding = 6)
65 * final short code;
66 *
67 * NduserOptHeaderMessage(final short family, final int len, final int ifindex,
68 * final short type, final short code) {
69 * this.family = family;
70 * this.len = len;
71 * this.ifindex = ifindex;
72 * this.type = type;
73 * this.code = code;
74 * }
75 * }
76 *
77 * - Option w/o explicit constructor:
78 * static class NduserOptHeaderMessage extends Struct {
79 * @Field(order = 0, type = Type.U8, padding = 1)
80 * short family;
81 * @Field(order = 1, type = Type.U16)
82 * int len;
83 * @Field(order = 2, type = Type.S32)
84 * int ifindex;
85 * @Field(order = 3, type = Type.U8)
86 * short type;
87 * @Field(order = 4, type = Type.U8, padding = 6)
88 * short code;
89 * }
90 *
91 * - Parse the target message and refer the members.
92 * final ByteBuffer buf = ByteBuffer.wrap(RAW_BYTES_ARRAY);
93 * buf.order(ByteOrder.nativeOrder());
94 * final NduserOptHeaderMessage nduserHdrMsg = Struct.parse(NduserOptHeaderMessage.class, buf);
95 * assertEquals(10, nduserHdrMsg.family);
96 */
97public class Struct {
98 public enum Type {
99 U8, // unsigned byte, size = 1 byte
100 U16, // unsigned short, size = 2 bytes
101 U32, // unsigned int, size = 4 bytes
Xiao Maf4cb6d62020-10-23 18:10:12 +0900102 U63, // unsigned long(MSB: 0), size = 8 bytes
Xiao Ma6f95cef2020-09-08 19:16:15 +0900103 U64, // unsigned long, size = 8 bytes
104 S8, // signed byte, size = 1 byte
105 S16, // signed short, size = 2 bytes
106 S32, // signed int, size = 4 bytes
107 S64, // signed long, size = 8 bytes
Xiao Maf4cb6d62020-10-23 18:10:12 +0900108 UBE16, // unsigned short in network order, size = 2 bytes
109 UBE32, // unsigned int in network order, size = 4 bytes
110 UBE63, // unsigned long(MSB: 0) in network order, size = 8 bytes
111 UBE64, // unsigned long in network order, size = 8 bytes
Xiao Ma6f95cef2020-09-08 19:16:15 +0900112 ByteArray, // byte array with predefined length
113 }
114
115 /**
116 * Indicate that the field marked with this annotation will automatically be managed by this
117 * class (e.g., will be parsed by #parse).
118 *
119 * order: The placeholder associated with each field, consecutive order starting from zero.
120 * type: The primitive data type listed in above Type enumeration.
121 * padding: Padding bytes appear after the field for alignment.
122 * arraysize: The length of byte array.
123 *
124 * Annotation associated with field MUST have order and type properties at least, padding
Xiao Maf4cb6d62020-10-23 18:10:12 +0900125 * and arraysize properties depend on the specific usage, if these properties are absent,
Xiao Ma6f95cef2020-09-08 19:16:15 +0900126 * then default value 0 will be applied.
127 */
128 @Retention(RetentionPolicy.RUNTIME)
129 @Target(ElementType.FIELD)
130 public @interface Field {
131 int order();
132 Type type();
133 int padding() default 0;
134 int arraysize() default 0;
135 }
136
137 private static class FieldInfo {
138 @NonNull
139 public final Field annotation;
140 @NonNull
141 public final java.lang.reflect.Field field;
142
143 FieldInfo(final Field annotation, final java.lang.reflect.Field field) {
144 this.annotation = annotation;
145 this.field = field;
146 }
147 }
Xiao Maa76690a2020-11-04 10:41:04 +0900148 private static ConcurrentHashMap<Class, FieldInfo[]> sFieldCache = new ConcurrentHashMap();
Xiao Ma6f95cef2020-09-08 19:16:15 +0900149
Xiao Maf4cb6d62020-10-23 18:10:12 +0900150 private static void checkAnnotationType(final Field annotation, final Class fieldType) {
151 switch (annotation.type()) {
Xiao Ma6f95cef2020-09-08 19:16:15 +0900152 case U8:
153 case S16:
154 if (fieldType == Short.TYPE) return;
155 break;
156 case U16:
157 case S32:
Xiao Maf4cb6d62020-10-23 18:10:12 +0900158 case UBE16:
Xiao Ma6f95cef2020-09-08 19:16:15 +0900159 if (fieldType == Integer.TYPE) return;
160 break;
161 case U32:
Xiao Maf4cb6d62020-10-23 18:10:12 +0900162 case U63:
Xiao Ma6f95cef2020-09-08 19:16:15 +0900163 case S64:
Xiao Maf4cb6d62020-10-23 18:10:12 +0900164 case UBE32:
165 case UBE63:
Xiao Ma6f95cef2020-09-08 19:16:15 +0900166 if (fieldType == Long.TYPE) return;
167 break;
168 case U64:
Xiao Maf4cb6d62020-10-23 18:10:12 +0900169 case UBE64:
170 if (fieldType == BigInteger.class) return;
Xiao Ma6f95cef2020-09-08 19:16:15 +0900171 break;
172 case S8:
173 if (fieldType == Byte.TYPE) return;
174 break;
175 case ByteArray:
Xiao Maf4cb6d62020-10-23 18:10:12 +0900176 if (fieldType != byte[].class) break;
177 if (annotation.arraysize() <= 0) {
178 throw new IllegalArgumentException("Invalid ByteArray size: "
179 + annotation.arraysize());
180 }
181 return;
Xiao Ma6f95cef2020-09-08 19:16:15 +0900182 default:
Xiao Maf4cb6d62020-10-23 18:10:12 +0900183 throw new IllegalArgumentException("Unknown type" + annotation.type());
Xiao Ma6f95cef2020-09-08 19:16:15 +0900184 }
185 throw new IllegalArgumentException("Invalid primitive data type: " + fieldType
Xiao Maf4cb6d62020-10-23 18:10:12 +0900186 + " for annotation type: " + annotation.type());
187 }
188
189 private static int getFieldLength(final Field annotation) {
190 int length = 0;
191 switch (annotation.type()) {
192 case U8:
193 case S8:
194 length = 1;
195 break;
196 case U16:
197 case S16:
198 case UBE16:
199 length = 2;
200 break;
201 case U32:
202 case S32:
203 case UBE32:
204 length = 4;
205 break;
206 case U63:
207 case U64:
208 case S64:
209 case UBE63:
210 case UBE64:
211 length = 8;
212 break;
213 case ByteArray:
214 length = annotation.arraysize();
215 break;
216 default:
217 throw new IllegalArgumentException("Unknown type" + annotation.type());
218 }
219 return length + annotation.padding();
Xiao Ma6f95cef2020-09-08 19:16:15 +0900220 }
221
222 private static boolean isStructSubclass(final Class clazz) {
223 return clazz != null && Struct.class.isAssignableFrom(clazz) && Struct.class != clazz;
224 }
225
226 private static int getAnnotationFieldCount(final Class clazz) {
227 int count = 0;
228 for (java.lang.reflect.Field field : clazz.getDeclaredFields()) {
229 if (field.isAnnotationPresent(Field.class)) count++;
230 }
231 return count;
232 }
233
Xiao Maf4cb6d62020-10-23 18:10:12 +0900234 private static boolean allFieldsFinal(final FieldInfo[] fields, boolean immutable) {
Xiao Ma6f95cef2020-09-08 19:16:15 +0900235 for (FieldInfo fi : fields) {
236 if (Modifier.isFinal(fi.field.getModifiers()) != immutable) return false;
237 }
238 return true;
239 }
240
241 private static boolean hasBothMutableAndImmutableFields(final FieldInfo[] fields) {
Xiao Maf4cb6d62020-10-23 18:10:12 +0900242 return !allFieldsFinal(fields, true /* immutable */)
243 && !allFieldsFinal(fields, false /* mutable */);
Xiao Ma6f95cef2020-09-08 19:16:15 +0900244 }
245
246 private static boolean matchConstructor(final Constructor cons, final FieldInfo[] fields) {
247 final Class[] paramTypes = cons.getParameterTypes();
248 if (paramTypes.length != fields.length) return false;
Xiao Maea531442020-10-10 23:23:42 +0900249 for (int i = 0; i < paramTypes.length; i++) {
Xiao Ma6f95cef2020-09-08 19:16:15 +0900250 if (!paramTypes[i].equals(fields[i].field.getType())) return false;
251 }
252 return true;
253 }
254
Xiao Maf4cb6d62020-10-23 18:10:12 +0900255 /**
256 * Read U64/UBE64 type data from ByteBuffer and output a BigInteger instance.
257 *
258 * @param buf The byte buffer to read.
259 * @param type The annotation type.
260 *
261 * The magnitude argument of BigInteger constructor is a byte array in big-endian order.
262 * If BigInteger data is read from the byte buffer in little-endian, reverse the order of
263 * the bytes is required; if BigInteger data is read from the byte buffer in big-endian,
264 * then just keep it as-is.
265 */
266 private static BigInteger readBigInteger(final ByteBuffer buf, final Type type) {
Xiao Ma6f95cef2020-09-08 19:16:15 +0900267 final byte[] input = new byte[8];
Xiao Maf4cb6d62020-10-23 18:10:12 +0900268 boolean reverseBytes = (type == Type.U64 && buf.order() == ByteOrder.LITTLE_ENDIAN);
Xiao Ma6f95cef2020-09-08 19:16:15 +0900269 for (int i = 0; i < 8; i++) {
Xiao Maf4cb6d62020-10-23 18:10:12 +0900270 input[reverseBytes ? input.length - 1 - i : i] = buf.get();
Xiao Ma6f95cef2020-09-08 19:16:15 +0900271 }
272 return new BigInteger(1, input);
273 }
274
Xiao Maf4cb6d62020-10-23 18:10:12 +0900275 /**
276 * Get the last 8 bytes of a byte array. If there are less than 8 bytes,
277 * the first bytes are replaced with zeroes.
278 */
279 private static byte[] getLast8Bytes(final byte[] input) {
280 final byte[] tmp = new byte[8];
281 System.arraycopy(
282 input,
283 Math.max(0, input.length - 8), // srcPos: read at most last 8 bytes
284 tmp,
285 Math.max(0, 8 - input.length), // dstPos: pad output with that many zeroes
286 Math.min(8, input.length)); // length
287 return tmp;
288 }
289
290 /**
291 * Convert U64/UBE64 type data interpreted by BigInteger class to bytes array, output are
292 * always 8 bytes.
293 *
294 * @param bigInteger The number to convert.
295 * @param order Indicate ByteBuffer is read as little-endian or big-endian.
296 * @param type The annotation type.
297 *
298 * BigInteger#toByteArray returns a byte array containing the 2's complement representation
299 * of this BigInteger, in big-endian. If annotation type is U64 and ByteBuffer is read as
300 * little-endian, then reversing the order of the bytes is required.
301 */
302 private static byte[] bigIntegerToU64Bytes(final BigInteger bigInteger, final ByteOrder order,
303 final Type type) {
304 final byte[] bigIntegerBytes = bigInteger.toByteArray();
305 final byte[] output = getLast8Bytes(bigIntegerBytes);
306
307 if (type == Type.U64 && order == ByteOrder.LITTLE_ENDIAN) {
308 for (int i = 0; i < 4; i++) {
309 byte tmp = output[i];
310 output[i] = output[7 - i];
311 output[7 - i] = tmp;
312 }
313 }
314 return output;
315 }
316
Xiao Ma6f95cef2020-09-08 19:16:15 +0900317 private static Object getFieldValue(final ByteBuffer buf, final FieldInfo fieldInfo)
318 throws BufferUnderflowException {
319 final Object value;
Xiao Maf4cb6d62020-10-23 18:10:12 +0900320 checkAnnotationType(fieldInfo.annotation, fieldInfo.field.getType());
Xiao Ma6f95cef2020-09-08 19:16:15 +0900321 switch (fieldInfo.annotation.type()) {
322 case U8:
323 value = (short) (buf.get() & 0xFF);
324 break;
325 case U16:
326 value = (int) (buf.getShort() & 0xFFFF);
327 break;
328 case U32:
329 value = (long) (buf.getInt() & 0xFFFFFFFFL);
330 break;
331 case U64:
Xiao Maf4cb6d62020-10-23 18:10:12 +0900332 value = readBigInteger(buf, Type.U64);
Xiao Ma6f95cef2020-09-08 19:16:15 +0900333 break;
334 case S8:
335 value = buf.get();
336 break;
337 case S16:
338 value = buf.getShort();
339 break;
340 case S32:
341 value = buf.getInt();
342 break;
Xiao Maf4cb6d62020-10-23 18:10:12 +0900343 case U63:
Xiao Ma6f95cef2020-09-08 19:16:15 +0900344 case S64:
345 value = buf.getLong();
346 break;
Xiao Maf4cb6d62020-10-23 18:10:12 +0900347 case UBE16:
Xiao Ma6f95cef2020-09-08 19:16:15 +0900348 if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
349 value = (int) (Short.reverseBytes(buf.getShort()) & 0xFFFF);
350 } else {
351 value = (int) (buf.getShort() & 0xFFFF);
352 }
353 break;
Xiao Maf4cb6d62020-10-23 18:10:12 +0900354 case UBE32:
Xiao Ma6f95cef2020-09-08 19:16:15 +0900355 if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
356 value = (long) (Integer.reverseBytes(buf.getInt()) & 0xFFFFFFFFL);
357 } else {
358 value = (long) (buf.getInt() & 0xFFFFFFFFL);
359 }
360 break;
Xiao Maf4cb6d62020-10-23 18:10:12 +0900361 case UBE63:
362 if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
363 value = Long.reverseBytes(buf.getLong());
Xiao Ma6f95cef2020-09-08 19:16:15 +0900364 } else {
Xiao Maf4cb6d62020-10-23 18:10:12 +0900365 value = buf.getLong();
Xiao Ma6f95cef2020-09-08 19:16:15 +0900366 }
367 break;
Xiao Maf4cb6d62020-10-23 18:10:12 +0900368 case UBE64:
369 value = readBigInteger(buf, Type.UBE64);
370 break;
Xiao Ma6f95cef2020-09-08 19:16:15 +0900371 case ByteArray:
372 final byte[] array = new byte[fieldInfo.annotation.arraysize()];
373 buf.get(array);
374 value = array;
375 break;
376 default:
377 throw new IllegalArgumentException("Unknown type:" + fieldInfo.annotation.type());
378 }
379
380 // Skip the padding data for alignment if any.
381 if (fieldInfo.annotation.padding() > 0) {
382 buf.position(buf.position() + fieldInfo.annotation.padding());
383 }
384 return value;
385 }
386
Xiao Maf4cb6d62020-10-23 18:10:12 +0900387 private static void putFieldValue(final ByteBuffer output, final FieldInfo fieldInfo,
388 final Object value) throws BufferUnderflowException {
389 switch (fieldInfo.annotation.type()) {
390 case U8:
391 output.put((byte) (((short) value) & 0xFF));
392 break;
393 case U16:
394 output.putShort((short) (((int) value) & 0xFFFF));
395 break;
396 case U32:
397 output.putInt((int) (((long) value) & 0xFFFFFFFFL));
398 break;
399 case U63:
400 output.putLong((long) value);
401 break;
402 case U64:
403 output.put(bigIntegerToU64Bytes((BigInteger) value, output.order(), Type.U64));
404 break;
405 case S8:
406 output.put((byte) value);
407 break;
408 case S16:
409 output.putShort((short) value);
410 break;
411 case S32:
412 output.putInt((int) value);
413 break;
414 case S64:
415 output.putLong((long) value);
416 break;
417 case UBE16:
418 if (output.order() == ByteOrder.LITTLE_ENDIAN) {
419 output.putShort(Short.reverseBytes((short) (((int) value) & 0xFFFF)));
420 } else {
421 output.putShort((short) (((int) value) & 0xFFFF));
422 }
423 break;
424 case UBE32:
425 if (output.order() == ByteOrder.LITTLE_ENDIAN) {
426 output.putInt(Integer.reverseBytes(
427 (int) (((long) value) & 0xFFFFFFFFL)));
428 } else {
429 output.putInt((int) (((long) value) & 0xFFFFFFFFL));
430 }
431 break;
432 case UBE63:
433 if (output.order() == ByteOrder.LITTLE_ENDIAN) {
434 output.putLong(Long.reverseBytes((long) value));
435 } else {
436 output.putLong((long) value);
437 }
438 break;
439 case UBE64:
440 output.put(bigIntegerToU64Bytes((BigInteger) value, output.order(), Type.UBE64));
441 break;
442 case ByteArray:
443 output.put((byte[]) value);
444 break;
445 default:
446 throw new IllegalArgumentException("Unknown type:" + fieldInfo.annotation.type());
447 }
448
449 // padding zero after field value for alignment.
450 for (int i = 0; i < fieldInfo.annotation.padding(); i++) output.put((byte) 0);
451 }
452
Xiao Ma6f95cef2020-09-08 19:16:15 +0900453 private static FieldInfo[] getClassFieldInfo(final Class clazz) {
Xiao Maea531442020-10-10 23:23:42 +0900454 if (!isStructSubclass(clazz)) {
455 throw new IllegalArgumentException(clazz.getName() + " is not a subclass of "
456 + Struct.class.getName());
457 }
458
Xiao Maa76690a2020-11-04 10:41:04 +0900459 final FieldInfo[] cachedAnnotationFields = sFieldCache.get(clazz);
460 if (cachedAnnotationFields != null) {
461 return cachedAnnotationFields;
462 }
Xiao Ma6f95cef2020-09-08 19:16:15 +0900463
464 // Since array returned from Class#getDeclaredFields doesn't guarantee the actual order
465 // of field appeared in the class, that is a problem when parsing raw data read from
466 // ByteBuffer. Store the fields appeared by the order() defined in the Field annotation.
Xiao Maa76690a2020-11-04 10:41:04 +0900467 final FieldInfo[] annotationFields = new FieldInfo[getAnnotationFieldCount(clazz)];
Xiao Ma6f95cef2020-09-08 19:16:15 +0900468 for (java.lang.reflect.Field field : clazz.getDeclaredFields()) {
469 if (Modifier.isStatic(field.getModifiers())) continue;
470
471 final Field annotation = field.getAnnotation(Field.class);
472 if (annotation == null) {
473 throw new IllegalArgumentException("Field " + field.getName()
474 + " is missing the " + Field.class.getSimpleName()
475 + " annotation");
476 }
477 if (annotation.order() < 0 || annotation.order() >= annotationFields.length) {
478 throw new IllegalArgumentException("Annotation order: " + annotation.order()
479 + " is negative or non-consecutive");
480 }
481 if (annotationFields[annotation.order()] != null) {
482 throw new IllegalArgumentException("Duplicated annotation order: "
483 + annotation.order());
484 }
485 annotationFields[annotation.order()] = new FieldInfo(annotation, field);
486 }
Xiao Maa76690a2020-11-04 10:41:04 +0900487 sFieldCache.putIfAbsent(clazz, annotationFields);
Xiao Ma6f95cef2020-09-08 19:16:15 +0900488 return annotationFields;
489 }
490
491 /**
492 * Parse raw data from ByteBuffer according to the pre-defined annotation rule and return
493 * the type-variable object which is subclass of Struct class.
494 *
495 * TODO:
496 * 1. Support subclass inheritance.
497 * 2. Introduce annotation processor to enforce the subclass naming schema.
498 */
499 public static <T> T parse(final Class<T> clazz, final ByteBuffer buf) {
500 try {
Xiao Ma6f95cef2020-09-08 19:16:15 +0900501 final FieldInfo[] foundFields = getClassFieldInfo(clazz);
502 if (hasBothMutableAndImmutableFields(foundFields)) {
Xiao Maf4cb6d62020-10-23 18:10:12 +0900503 throw new IllegalArgumentException("Class has both final and non-final fields");
Xiao Ma6f95cef2020-09-08 19:16:15 +0900504 }
505
506 Constructor<?> constructor = null;
507 Constructor<?> defaultConstructor = null;
508 final Constructor<?>[] constructors = clazz.getDeclaredConstructors();
509 for (Constructor cons : constructors) {
510 if (matchConstructor(cons, foundFields)) constructor = cons;
511 if (cons.getParameterTypes().length == 0) defaultConstructor = cons;
512 }
513
514 if (constructor == null && defaultConstructor == null) {
515 throw new IllegalArgumentException("Fail to find available constructor");
516 }
517 if (constructor != null) {
Xiao Maea531442020-10-10 23:23:42 +0900518 final Object[] args = new Object[foundFields.length];
Xiao Ma6f95cef2020-09-08 19:16:15 +0900519 for (int i = 0; i < args.length; i++) {
520 args[i] = getFieldValue(buf, foundFields[i]);
521 }
522 return (T) constructor.newInstance(args);
523 }
524
525 final Object instance = defaultConstructor.newInstance();
526 for (FieldInfo fi : foundFields) {
527 fi.field.set(instance, getFieldValue(buf, fi));
528 }
529 return (T) instance;
Xiao Maf4cb6d62020-10-23 18:10:12 +0900530 } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
Xiao Ma6f95cef2020-09-08 19:16:15 +0900531 throw new IllegalArgumentException("Fail to create a instance from constructor", e);
532 } catch (BufferUnderflowException e) {
533 throw new IllegalArgumentException("Fail to read raw data from ByteBuffer", e);
534 }
535 }
Xiao Maf4cb6d62020-10-23 18:10:12 +0900536
537 private static int getSizeInternal(final FieldInfo[] fieldInfos) {
538 int size = 0;
539 for (FieldInfo fi : fieldInfos) {
540 size += getFieldLength(fi.annotation);
541 }
542 return size;
543 }
544
545 private void writeToByteBufferInternal(final ByteBuffer output, final FieldInfo[] fieldInfos) {
546 for (FieldInfo fi : fieldInfos) {
547 try {
548 putFieldValue(output, fi, fi.field.get(this));
549 } catch (IllegalAccessException e) {
550 throw new IllegalArgumentException("Fail to get the field value from instance", e);
551 } catch (BufferUnderflowException e) {
552 throw new IllegalArgumentException("Fail to fill raw data to ByteBuffer", e);
553 }
554 }
555 }
556
557 /**
558 * Get the size of Struct subclass object.
559 */
560 public static <T extends Struct> int getSize(final Class<T> clazz) {
561 final FieldInfo[] fieldInfos = getClassFieldInfo(clazz);
562 return getSizeInternal(fieldInfos);
563 }
564
565 /**
566 * Convert the parsed Struct subclass object to ByteBuffer.
567 *
568 * @param output ByteBuffer passed-in from the caller.
569 */
570 public final void writeToByteBuffer(final ByteBuffer output) {
571 final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass());
572 writeToByteBufferInternal(output, fieldInfos);
573 }
574
575 /**
576 * Convert the parsed Struct subclass object to byte array.
577 *
578 * @param order indicate ByteBuffer is outputted as little-endian or big-endian.
579 */
580 public final byte[] writeToBytes(final ByteOrder order) {
581 final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass());
582 final byte[] output = new byte[getSizeInternal(fieldInfos)];
583 final ByteBuffer buffer = ByteBuffer.wrap(output);
584 buffer.order(order);
585 writeToByteBufferInternal(buffer, fieldInfos);
586 return output;
587 }
Xiao Ma6f95cef2020-09-08 19:16:15 +0900588}