|
18 | 18 |
|
19 | 19 | import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
20 | 20 | import static org.junit.jupiter.api.Assertions.assertEquals;
|
| 21 | +import static org.junit.jupiter.api.Assertions.assertNull; |
21 | 22 |
|
22 | 23 |
|
23 | 24 | import com.igormaznitsa.jbbp.JBBPCustomFieldTypeProcessor;
|
|
27 | 28 | import com.igormaznitsa.jbbp.io.JBBPBitInputStream;
|
28 | 29 | import com.igormaznitsa.jbbp.io.JBBPBitNumber;
|
29 | 30 | import com.igormaznitsa.jbbp.io.JBBPBitOrder;
|
| 31 | +import com.igormaznitsa.jbbp.io.JBBPBitOutputStream; |
30 | 32 | import com.igormaznitsa.jbbp.io.JBBPByteOrder;
|
| 33 | +import com.igormaznitsa.jbbp.io.JBBPCustomFieldWriter; |
31 | 34 | import com.igormaznitsa.jbbp.io.JBBPOut;
|
32 | 35 | import com.igormaznitsa.jbbp.mapper.Bin;
|
33 | 36 | import com.igormaznitsa.jbbp.mapper.BinType;
|
| 37 | +import com.igormaznitsa.jbbp.mapper.JBBPMapperCustomFieldProcessor; |
34 | 38 | import com.igormaznitsa.jbbp.model.JBBPAbstractField;
|
35 | 39 | import com.igormaznitsa.jbbp.model.JBBPFieldArrayByte;
|
36 | 40 | import com.igormaznitsa.jbbp.model.JBBPFieldArrayLong;
|
37 | 41 | import com.igormaznitsa.jbbp.model.JBBPFieldArrayString;
|
| 42 | +import com.igormaznitsa.jbbp.model.JBBPFieldArrayStruct; |
38 | 43 | import com.igormaznitsa.jbbp.model.JBBPFieldInt;
|
39 | 44 | import com.igormaznitsa.jbbp.model.JBBPFieldLong;
|
40 | 45 | import com.igormaznitsa.jbbp.model.JBBPFieldString;
|
|
45 | 50 | import java.io.EOFException;
|
46 | 51 | import java.io.IOException;
|
47 | 52 | import java.io.InputStream;
|
| 53 | +import java.lang.reflect.Field; |
48 | 54 | import java.nio.charset.StandardCharsets;
|
49 | 55 | import java.util.ArrayList;
|
50 | 56 | import java.util.Iterator;
|
51 | 57 | import java.util.List;
|
| 58 | +import java.util.Locale; |
52 | 59 | import java.util.concurrent.atomic.AtomicInteger;
|
53 | 60 | import java.util.concurrent.atomic.AtomicLong;
|
54 | 61 | import org.junit.jupiter.api.Test;
|
@@ -415,4 +422,174 @@ public JBBPAbstractField readCustomFieldType(final JBBPBitInputStream in, final
|
415 | 422 | result = sasParser.parse(new byte[] {0, 0, 0, 2, 1, 2, 3, 4});
|
416 | 423 | assertEquals(2, ((JBBPNumericField) result.findFieldForName("keycount")).getAsInt());
|
417 | 424 | }
|
| 425 | + |
| 426 | + /** |
| 427 | + * Case 09-jun-2020 |
| 428 | + * Example how to write custom field type read-write-mapping processor for nullable byte array. |
| 429 | + * |
| 430 | + * @throws Exception for any error |
| 431 | + */ |
| 432 | + @Test |
| 433 | + public void testNullableByteArrayField() throws Exception { |
| 434 | + class NullableByteArrayProcessor |
| 435 | + implements JBBPCustomFieldWriter, JBBPMapperCustomFieldProcessor, |
| 436 | + JBBPCustomFieldTypeProcessor { |
| 437 | + |
| 438 | + private final String TYPE = "nullableByteArray"; |
| 439 | + private final String[] CUSTOM_TYPE = new String[] {TYPE.toLowerCase(Locale.ENGLISH)}; |
| 440 | + |
| 441 | + @Override |
| 442 | + public String[] getCustomFieldTypes() { |
| 443 | + return CUSTOM_TYPE; |
| 444 | + } |
| 445 | + |
| 446 | + @Override |
| 447 | + public boolean isAllowed(JBBPFieldTypeParameterContainer fieldType, String fieldName, |
| 448 | + int extraData, boolean isArray) { |
| 449 | + return !isArray; |
| 450 | + } |
| 451 | + |
| 452 | + private byte[] readFromStream(JBBPByteOrder byteOrder, JBBPBitInputStream in) |
| 453 | + throws IOException { |
| 454 | + final int len = in.readInt(byteOrder); |
| 455 | + return len < 0 ? null : in.readByteArray(len); |
| 456 | + } |
| 457 | + |
| 458 | + @Override |
| 459 | + public JBBPAbstractField readCustomFieldType(JBBPBitInputStream in, JBBPBitOrder bitOrder, |
| 460 | + int parserFlags, |
| 461 | + JBBPFieldTypeParameterContainer customTypeFieldInfo, |
| 462 | + JBBPNamedFieldInfo fieldName, int extraData, |
| 463 | + boolean readWholeStream, int arrayLength) |
| 464 | + throws IOException { |
| 465 | + if (arrayLength < 0) { |
| 466 | + return toStruct(fieldName, readFromStream(customTypeFieldInfo.getByteOrder(), in)); |
| 467 | + } else { |
| 468 | + throw new IllegalArgumentException("Array of nullable byte arrays is unsupported"); |
| 469 | + } |
| 470 | + } |
| 471 | + |
| 472 | + private void writeTo(final JBBPBitOutputStream outStream, final JBBPByteOrder order, |
| 473 | + final byte[] data) throws IOException { |
| 474 | + if (data == null) { |
| 475 | + outStream.writeInt(-1, order); |
| 476 | + } else { |
| 477 | + outStream.writeInt(data.length, order); |
| 478 | + outStream.write(data); |
| 479 | + } |
| 480 | + } |
| 481 | + |
| 482 | + @Override |
| 483 | + public void writeCustomField(JBBPOut context, JBBPBitOutputStream outStream, |
| 484 | + Object instanceToSave, Field instanceCustomField, |
| 485 | + Bin fieldAnnotation, Object value) throws IOException { |
| 486 | + if (fieldAnnotation.customType().equals(TYPE)) { |
| 487 | + writeTo(outStream, fieldAnnotation.byteOrder(), (byte[]) value); |
| 488 | + } else { |
| 489 | + throw new IllegalArgumentException( |
| 490 | + "Unsupported custom type: " + fieldAnnotation.customType()); |
| 491 | + } |
| 492 | + } |
| 493 | + |
| 494 | + private JBBPFieldStruct toStruct(JBBPNamedFieldInfo fieldName, final byte[] array) { |
| 495 | + if (array == null) { |
| 496 | + return new JBBPFieldStruct(fieldName, new JBBPAbstractField[0]); |
| 497 | + } else { |
| 498 | + return new JBBPFieldStruct(fieldName, |
| 499 | + new JBBPAbstractField[] {new JBBPFieldArrayByte(null, array)}); |
| 500 | + } |
| 501 | + } |
| 502 | + |
| 503 | + private byte[] fromStruct(final JBBPFieldStruct struct) { |
| 504 | + final JBBPAbstractField[] fields = struct.getArray(); |
| 505 | + return fields.length == 0 ? null : ((JBBPFieldArrayByte) struct.getArray()[0]).getArray(); |
| 506 | + } |
| 507 | + |
| 508 | + @Override |
| 509 | + public Object prepareObjectForMapping(JBBPFieldStruct parsedBlock, |
| 510 | + Bin annotation, |
| 511 | + Field field) { |
| 512 | + if (annotation.customType().equals(TYPE)) { |
| 513 | + if (field.getType() == byte[][].class) { |
| 514 | + final JBBPFieldArrayStruct structs = |
| 515 | + parsedBlock.findFieldForNameAndType(field.getName(), JBBPFieldArrayStruct.class); |
| 516 | + final byte[][] result = new byte[structs.size()][]; |
| 517 | + for (int i = 0; i < structs.size(); i++) { |
| 518 | + result[i] = fromStruct(structs.getElementAt(i)); |
| 519 | + } |
| 520 | + return result; |
| 521 | + } else { |
| 522 | + return fromStruct( |
| 523 | + parsedBlock.findFieldForNameAndType(field.getName(), JBBPFieldStruct.class)); |
| 524 | + } |
| 525 | + } else { |
| 526 | + throw new IllegalArgumentException("Unexpected custom type: " + annotation.customType()); |
| 527 | + } |
| 528 | + } |
| 529 | + } |
| 530 | + ; |
| 531 | + |
| 532 | + final NullableByteArrayProcessor nullableByteArrayProcessor = new NullableByteArrayProcessor(); |
| 533 | + |
| 534 | + class Klazz { |
| 535 | + @Bin |
| 536 | + int a; |
| 537 | + @Bin(custom = true, customType = "nullableByteArray") |
| 538 | + byte[] b; |
| 539 | + @Bin |
| 540 | + int c; |
| 541 | + } |
| 542 | + ; |
| 543 | + |
| 544 | + Klazz object = new Klazz(); |
| 545 | + object.a = 12345; |
| 546 | + object.b = null; |
| 547 | + object.c = 7890; |
| 548 | + |
| 549 | + final byte[] withNullField = |
| 550 | + JBBPOut.BeginBin().Bin(object, nullableByteArrayProcessor).End().toByteArray(); |
| 551 | + |
| 552 | + assertArrayEquals( |
| 553 | + new byte[] {0, 0, 48, 57, (byte) -1, (byte) -1, (byte) -1, (byte) -1, 0, 0, 30, (byte) -46}, |
| 554 | + withNullField); |
| 555 | + |
| 556 | + object = new Klazz(); |
| 557 | + object.a = 12345; |
| 558 | + object.b = new byte[] {1, 2, 3}; |
| 559 | + object.c = 7890; |
| 560 | + |
| 561 | + final byte[] withContent = |
| 562 | + JBBPOut.BeginBin().Bin(object, nullableByteArrayProcessor).End().toByteArray(); |
| 563 | + assertArrayEquals(new byte[] {0, 0, 48, 57, 0, 0, 0, 3, 1, 2, 3, 0, 0, 30, (byte) -46}, |
| 564 | + withContent); |
| 565 | + |
| 566 | + object = new Klazz(); |
| 567 | + object.a = 12345; |
| 568 | + object.b = new byte[0]; |
| 569 | + object.c = 7890; |
| 570 | + |
| 571 | + final byte[] withZeroLength = |
| 572 | + JBBPOut.BeginBin().Bin(object, nullableByteArrayProcessor).End().toByteArray(); |
| 573 | + assertArrayEquals(new byte[] {0, 0, 48, 57, 0, 0, 0, 0, 0, 0, 30, (byte) -46}, withZeroLength); |
| 574 | + |
| 575 | + JBBPParser parser = |
| 576 | + JBBPParser.prepare("int a; nullableByteArray b; int c;", nullableByteArrayProcessor); |
| 577 | + |
| 578 | + Klazz parsed = parser.parse(withNullField).mapTo(new Klazz(), nullableByteArrayProcessor); |
| 579 | + |
| 580 | + assertEquals(12345, parsed.a); |
| 581 | + assertNull(parsed.b); |
| 582 | + assertEquals(7890, parsed.c); |
| 583 | + |
| 584 | + parsed = parser.parse(withZeroLength).mapTo(new Klazz(), nullableByteArrayProcessor); |
| 585 | + assertEquals(12345, parsed.a); |
| 586 | + assertArrayEquals(new byte[0], parsed.b); |
| 587 | + assertEquals(7890, parsed.c); |
| 588 | + |
| 589 | + parsed = parser.parse(withContent).mapTo(new Klazz(), nullableByteArrayProcessor); |
| 590 | + assertEquals(12345, parsed.a); |
| 591 | + assertArrayEquals(new byte[] {1, 2, 3}, parsed.b); |
| 592 | + assertEquals(7890, parsed.c); |
| 593 | + } |
| 594 | + |
418 | 595 | }
|
0 commit comments