From dd1b55106bb6125374be673b8fac22d29f26accc Mon Sep 17 00:00:00 2001 From: Ziver Koc Date: Thu, 14 Apr 2016 16:44:42 +0200 Subject: [PATCH] Implementation of Variable length BinaryStruct fields, one TC is failing --- src/zutil/ByteUtil.java | 4 +- src/zutil/ClassUtil.java | 28 ++++++++++ src/zutil/parser/binary/BinaryFieldData.java | 56 ++++++++++++++----- src/zutil/parser/binary/BinaryStruct.java | 27 ++++++++- .../binary/BinaryStructInputStream.java | 4 +- .../binary/BinaryStructOutputStream.java | 2 +- .../binary/BinaryStructInputStreamTest.java | 21 +++++++ .../binary/BinaryStructOutputStreamTest.java | 18 +++++- 8 files changed, 139 insertions(+), 21 deletions(-) diff --git a/src/zutil/ByteUtil.java b/src/zutil/ByteUtil.java index f307b44..82c063f 100755 --- a/src/zutil/ByteUtil.java +++ b/src/zutil/ByteUtil.java @@ -73,13 +73,13 @@ public class ByteUtil { * Returns a byte bitmask * * @param index start index of the mask, valid values 0-7 - * @param length length of mask from index, valid values 1-8 + * @param length length of mask from index, valid values 1-8 depending on index */ public static byte getBitMask(int index, int length) { --length; if(0 > index || index > 7) throw new IllegalArgumentException("Invalid index argument, allowed value is 0-7"); - if(length < 0 && index-length < 0) + if(length < 0 || 7-index-length < 0) throw new IllegalArgumentException("Invalid length argument: "+length+", allowed values 1-8 depending on index"); return (byte) BYTE_MASK[index][length]; } diff --git a/src/zutil/ClassUtil.java b/src/zutil/ClassUtil.java index 81f769e..9fbb1ab 100755 --- a/src/zutil/ClassUtil.java +++ b/src/zutil/ClassUtil.java @@ -81,6 +81,34 @@ public class ClassUtil { return primitives.contains( type ); } + /** + * @return true if the given class is a type representing a number without any decimals. + * E.g. long, int, short, char, byte and corresponding wrapper. + */ + public static boolean isNumber(Class type){ + return Long.class.isAssignableFrom(type) || + long.class.isAssignableFrom(type) || + Integer.class.isAssignableFrom(type) || + int.class.isAssignableFrom(type) || + Short.class.isAssignableFrom(type) || + short.class.isAssignableFrom(type) || + Character.class.isAssignableFrom(type) || + char.class.isAssignableFrom(type) || + Byte.class.isAssignableFrom(type) || + byte.class.isAssignableFrom(type); + } + + /** + * @return true if the given class is a type representing a number with decimals. + * E.g. double, float and corresponding wrapper. + */ + public static boolean isDecimal(Class type){ + return Double.class.isAssignableFrom(type) || + double.class.isAssignableFrom(type) || + Float.class.isAssignableFrom(type) || + float.class.isAssignableFrom(type); + } + /** * @param field is the field to return the generics from diff --git a/src/zutil/parser/binary/BinaryFieldData.java b/src/zutil/parser/binary/BinaryFieldData.java index 164c3d0..11b3440 100755 --- a/src/zutil/parser/binary/BinaryFieldData.java +++ b/src/zutil/parser/binary/BinaryFieldData.java @@ -4,6 +4,7 @@ package zutil.parser.binary; import java.lang.reflect.Field; import java.util.*; +import zutil.ClassUtil; import zutil.converter.Converter; import zutil.parser.binary.BinaryStruct.*; @@ -15,8 +16,12 @@ public class BinaryFieldData { private int index; private int length; - private BinaryFieldSerializer serializer; private Field field; + /* @VariableLengthBinaryField */ + private BinaryFieldData lengthField; + private int lengthMultiplier; + /* @CustomBinaryField */ + private BinaryFieldSerializer serializer; protected static List getStructFieldList(Class clazz){ @@ -25,10 +30,10 @@ public class BinaryFieldData { ArrayList list = new ArrayList<>(); for (Field field : clazz.getDeclaredFields()) { if (field.isAnnotationPresent(BinaryField.class) || - field.isAnnotationPresent(CustomBinaryField.class)) + field.isAnnotationPresent(CustomBinaryField.class) || + field.isAnnotationPresent(VariableLengthBinaryField.class)) list.add(new BinaryFieldData(field)); - } Collections.sort(list, new Comparator(){ @Override @@ -45,21 +50,42 @@ public class BinaryFieldData { } - private BinaryFieldData(Field f) throws IllegalAccessException, InstantiationException { + private BinaryFieldData(Field f) throws IllegalAccessException, InstantiationException, NoSuchFieldException { field = f; + this.length = -1; + this.lengthField = null; + this.lengthMultiplier = 1; + this.serializer = null; if (field.isAnnotationPresent(CustomBinaryField.class)){ CustomBinaryField fieldData = field.getAnnotation(CustomBinaryField.class); - index = fieldData.index(); - serializer = (BinaryFieldSerializer) fieldData.serializer().newInstance(); + this.index = fieldData.index(); + this.serializer = fieldData.serializer().newInstance(); + } + else if (field.isAnnotationPresent(VariableLengthBinaryField.class)) { + VariableLengthBinaryField fieldData = field.getAnnotation(VariableLengthBinaryField.class); + this.index = fieldData.index(); + this.lengthMultiplier = fieldData.multiplier(); + this.lengthField = new BinaryFieldData( + field.getDeclaringClass().getDeclaredField(fieldData.lengthField())); + if ( !ClassUtil.isNumber(lengthField.getType())) + throw new IllegalArgumentException("Length variable for VariableLengthBinaryStruct needs to be of a number type."); } else { BinaryField fieldData = field.getAnnotation(BinaryField.class); - index = fieldData.index(); - length = fieldData.length(); + this.index = fieldData.index(); + this.length = fieldData.length(); } } - protected void setByteValue(Object obj, byte[] data){ + + public String getName(){ + return field.getName(); + } + public Class getType(){ + return field.getType(); + } + + public void setByteValue(Object obj, byte[] data){ try { field.setAccessible(true); if (field.getType() == Boolean.class || field.getType() == boolean.class) @@ -74,7 +100,7 @@ public class BinaryFieldData { e.printStackTrace(); } } - protected void setValue(Object obj, Object value){ + public void setValue(Object obj, Object value){ try { field.setAccessible(true); field.set(obj, value); @@ -83,7 +109,7 @@ public class BinaryFieldData { } } - protected byte[] getByteValue(Object obj){ + public byte[] getByteValue(Object obj){ try { field.setAccessible(true); if (field.getType() == Boolean.class || field.getType() == boolean.class) @@ -93,13 +119,13 @@ public class BinaryFieldData { else if (field.getType() == String.class) return ((String)(field.get(obj))).getBytes(); else - throw new UnsupportedOperationException("Unsupported BinaryStruct field class: "+ field.getClass()); + throw new UnsupportedOperationException("Unsupported BinaryStruct field type: "+ getType()); } catch (IllegalAccessException e){ e.printStackTrace(); } return null; } - protected Object getValue(Object obj){ + public Object getValue(Object obj){ try { field.setAccessible(true); return field.get(obj); @@ -110,7 +136,9 @@ public class BinaryFieldData { } - public int getBitLength(){ + public int getBitLength(Object obj){ + if(lengthField != null) + return (int) lengthField.getValue(obj) * lengthMultiplier; return length; } diff --git a/src/zutil/parser/binary/BinaryStruct.java b/src/zutil/parser/binary/BinaryStruct.java index c1d9000..5f98c01 100755 --- a/src/zutil/parser/binary/BinaryStruct.java +++ b/src/zutil/parser/binary/BinaryStruct.java @@ -34,17 +34,42 @@ import java.lang.annotation.Target; */ public interface BinaryStruct { + /** + * Basic BinaryField with a constant length. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @interface BinaryField{ + /** Will be used to order the fields are read. Lowest index number field will be read first. */ int index(); + /** Defines the bit length of the data */ int length(); } + /** + * Can be used for fields that are of variable length. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface VariableLengthBinaryField{ + /** Will be used to order the fields are read. Lowest index number field will be read first. */ + int index(); + /** The name of the field that will contain the length of the data to read. */ + String lengthField(); + /** Defines the multiplier used on the lengthField to convert to length in bits which is used internally. + * Default value is 8. */ + int multiplier() default 8; + } + + /** + * Can be used with fields that need a custom serializer. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @interface CustomBinaryField{ + /** Will be used to order the fields are read. Lowest index number field will be read first. */ int index(); - Class serializer(); + /** Defines the serializer class that will be used. Class needs to be publicly visible. */ + Class serializer(); } } diff --git a/src/zutil/parser/binary/BinaryStructInputStream.java b/src/zutil/parser/binary/BinaryStructInputStream.java index 1234e90..798b482 100755 --- a/src/zutil/parser/binary/BinaryStructInputStream.java +++ b/src/zutil/parser/binary/BinaryStructInputStream.java @@ -76,7 +76,7 @@ public class BinaryStructInputStream { field.setValue(struct, value); } else { - byte[] valueData = new byte[(int) Math.ceil(field.getBitLength() / 8.0)]; + byte[] valueData = new byte[(int) Math.ceil(field.getBitLength(struct) / 8.0)]; int fieldReadLength = 0; // Parse value @@ -85,7 +85,7 @@ public class BinaryStructInputStream { data = (byte) in.read(); dataBitIndex = 7; } - int bitLength = Math.min(dataBitIndex + 1, field.getBitLength() - fieldReadLength); + int bitLength = Math.min(dataBitIndex + 1, field.getBitLength(struct) - fieldReadLength); valueData[valueDataIndex] = ByteUtil.getShiftedBits(data, dataBitIndex, bitLength); fieldReadLength += bitLength; dataBitIndex -= bitLength; diff --git a/src/zutil/parser/binary/BinaryStructOutputStream.java b/src/zutil/parser/binary/BinaryStructOutputStream.java index a3b3cc6..b40152c 100755 --- a/src/zutil/parser/binary/BinaryStructOutputStream.java +++ b/src/zutil/parser/binary/BinaryStructOutputStream.java @@ -80,7 +80,7 @@ public class BinaryStructOutputStream { else{ byte[] data = field.getByteValue(struct); - int fieldBitLength = field.getBitLength(); + int fieldBitLength = field.getBitLength(struct); for (int i = (int) Math.ceil(fieldBitLength / 8.0) - 1; fieldBitLength > 0; fieldBitLength -= 8, --i) { byte b = data[i]; if (restBitLength == 0 && fieldBitLength >= 8) diff --git a/test/zutil/parser/binary/BinaryStructInputStreamTest.java b/test/zutil/parser/binary/BinaryStructInputStreamTest.java index d4cc248..2e585da 100755 --- a/test/zutil/parser/binary/BinaryStructInputStreamTest.java +++ b/test/zutil/parser/binary/BinaryStructInputStreamTest.java @@ -154,4 +154,25 @@ public class BinaryStructInputStreamTest { } public void write(OutputStream out, String obj, BinaryFieldData field) throws IOException {} } + + + @Test + public void variableLengthField(){ + BinaryTestStruct struct = new BinaryTestStruct() { + @BinaryField(index=1, length=8) + public int i1; + @VariableLengthBinaryField(index=2, lengthField="i1") + public String s2; + + public void assertObj(){ + assertEquals(3, i1); + assertEquals("123", s2); + } + }; + + byte[] data = "0123456".getBytes(); + data[0] = 3; + BinaryStructInputStream.read(struct, data); + struct.assertObj(); + } } diff --git a/test/zutil/parser/binary/BinaryStructOutputStreamTest.java b/test/zutil/parser/binary/BinaryStructOutputStreamTest.java index 8020b83..da8db08 100755 --- a/test/zutil/parser/binary/BinaryStructOutputStreamTest.java +++ b/test/zutil/parser/binary/BinaryStructOutputStreamTest.java @@ -80,7 +80,7 @@ public class BinaryStructOutputStreamTest { @Test - public void customBinaryField() throws IOException { + public void customBinaryFieldTest() throws IOException { BinaryStruct struct = new BinaryStruct() { @BinaryStruct.CustomBinaryField(index=1, serializer=ByteStringSerializer.class) public String s1 = "1234"; @@ -98,4 +98,20 @@ public class BinaryStructOutputStreamTest { out.write(Integer.parseInt(""+c)); } } + + + @Test + public void variableLengthFieldTest() throws IOException { + BinaryStruct struct = new BinaryStruct() { + @BinaryField(index=1, length=1) + private int s1 = 2; + @VariableLengthBinaryField(index=2, lengthField="s1") + private String s2 = "12345"; + }; + + byte[] data = BinaryStructOutputStream.serialize(struct); + byte[] expected = "012".getBytes(); + expected[0] = 2; + assertArrayEquals(expected, data); + } }