diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e0f15db --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "automatic" +} \ No newline at end of file diff --git a/README.md b/README.md index 1a093ab..21419a3 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,18 @@ RLP Encoding/Decoding - declaring your pojo ```java +package org.tdf.rlp; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + public class Node{ // RLP annotation specify the order of field in encoded list @RLP(0) public String name; - + @RLP(1) public List children; @@ -32,6 +39,15 @@ public class Node{ children.addAll(nodes); } + private static class Nested{ + @RLP + private List>> nested; + + public Nested() { + } + } + + public static void main(String[] args){ Node root = new Node("1"); root.addChildren(Arrays.asList(new Node("2"), new Node("3"))); @@ -40,15 +56,15 @@ public class Node{ root.children.get(1).addChildren(Arrays.asList(new Node("6"), new Node("7"))); // encode to byte array - byte[] encoded = RLPElement.encode(root).getEncoded(); - // encode to rlp element - RLPElement el = RLPElement.encode(root); + byte[] encoded = RLPCodec.encode(root); + // read as rlp tree + RLPElement el = RLPElement.readRLPTree(root); // decode from byte array - Node root2 = RLPDeserializer.deserialize(encoded, Node.class); - assertTrue(root2.children.get(0).children.get(0).name.equals("4")); - assertTrue(root2.children.get(0).children.get(1).name.equals("5")); - assertTrue(root2.children.get(1).children.get(0).name.equals("6")); - assertTrue(root2.children.get(1).children.get(1).name.equals("7")); + Node root2 = RLPCodec.decode(encoded, Node.class); + el = RLPElement.fromEncoded(encoded); + // decode from rlp element + root2 = el.as(Node.class); + root2 = RLPCodec.decode(el, Node.class); } public static void assertTrue(boolean b){ @@ -60,14 +76,18 @@ public class Node{ - custom encoding/decoding ```java +package org.tdf.rlp; + +import java.util.HashMap; +import java.util.Map; + public class Main{ public static class MapEncoderDecoder implements RLPEncoder>, RLPDecoder> { @Override - public Map decode(RLPElement element) { - RLPList list = element.getAsList(); + public Map decode(RLPElement list) { Map map = new HashMap<>(list.size() / 2); for (int i = 0; i < list.size(); i += 2) { - map.put(list.get(i).getAsItem().getString(), list.get(i+1).getAsItem().getString()); + map.put(list.get(i).asString(), list.get(i+1).asString()); } return map; } @@ -101,8 +121,8 @@ public class Main{ Map m = new HashMap<>(); m.put("a", "1"); m.put("b", "2"); - byte[] encoded = RLPElement.encode(new MapWrapper(m)).getEncoded(); - MapWrapper decoded = RLPDeserializer.deserialize(encoded, MapWrapper.class); + byte[] encoded = RLPCodec.encode(new MapWrapper(m)); + MapWrapper decoded = RLPCodec.decode(encoded, MapWrapper.class); assertTrue(decoded.map.get("a").equals("1")); } @@ -110,6 +130,7 @@ public class Main{ if(!b) throw new RuntimeException("assertion failed"); } } + ``` - supports List and POJO only for rlp is ordered while set is no ordered, generic list could be nested to any deepth @@ -123,6 +144,7 @@ public class Nested{ } } ``` + ```java public class Main{ public static void main(String[] args){ @@ -131,10 +153,97 @@ public class Main{ nested.nested.add(new ArrayList<>()); nested.nested.get(0).add(new ArrayList<>()); nested.nested.get(0).get(0).addAll(Arrays.asList("aaa", "bbb")); - byte[] encoded = RLPElement.encode(nested).getEncoded(); - nested = RLPDeserializer.deserialize(encoded, Nested.class); - assertTrue(nested.nested.get(0).get(0).get(0).equals("aaa")); - assertTrue(nested.nested.get(0).get(0).get(1).equals("bbb")); + byte[] encoded = RLPCodec.encode(nested); + nested = RLPCodec.decode(encoded, Nested.class); + assert nested.nested.get(0).get(0).get(0).equals("aaa"); + assert nested.nested.get(0).get(0).get(1).equals("bbb"); + } +} +``` + +- encoding/decoding of Set/Map + +```java +public class Main{ + + private static class ByteArraySetWrapper { + @RLP + @RLPDecoding(as = TreeSet.class) + @RLPEncoding(contentOrdering = BytesComparator.class) + private Set bytesSet; + } + + private static class BytesComparator implements Comparator { + @Override + public int compare(byte[] o1, byte[] o2) { + return new BigInteger(1, o1).compareTo(new BigInteger(1, o2)); + } + } + + public static class MapWrapper2 { + @RLP + @RLPDecoding(as = TreeMap.class) + @RLPEncoding(keyOrdering = StringComparator.class) + public Map> map = new HashMap<>(); + } + + private static class StringComparator implements Comparator { + @Override + public int compare(String o1, String o2) { + return o1.length() - o2.length(); + } + } + + public static void main(String[] args){ + ByteArraySetWrapper wrapper = + RLPList.of(RLPList.of(RLPItem.fromLong(1), RLPItem.fromLong(2))).as(ByteArraySetWrapper.class); + assert wrapper.bytesSet instanceof TreeSet; + + wrapper = new ByteArraySetWrapper(); + wrapper.bytesSet = new HashSet<>(); + wrapper.bytesSet.add(new byte[]{1}); + wrapper.bytesSet.add(new byte[]{2}); + wrapper.bytesSet.add(new byte[]{3}); + wrapper.bytesSet.add(new byte[]{4}); + wrapper.bytesSet.add(new byte[]{5}); + boolean sorted = true; + int i = 0; + for (byte[] b : wrapper.bytesSet) { + if (new BigInteger(1, b).compareTo(BigInteger.valueOf(i + 1)) != 0) { + sorted = false; + break; + } + i++; + } + assert !sorted; + RLPElement el = RLPElement.readRLPTree(wrapper).get(0); + for (int j = 0; j < el.size(); j++) { + assert new BigInteger(1, el.get(j).asBytes()).compareTo(BigInteger.valueOf(j + 1)) == 0; + } + + MapWrapper2 wrapper2 = new MapWrapper2(); + wrapper2.map.put("1", new HashMap<>()); + wrapper2.map.put("22", new HashMap<>()); + wrapper2.map.put("sss", new HashMap<>()); + wrapper2.map.get("sss").put("aaa", "bbb"); + hasSorted = true; + i = 1; + for (String k : wrapper2.map.keySet()) { + if (k.length() != i) { + hasSorted = false; + break; + } + i++; + } + assert !hasSorted; + byte[] encoded = RLPCodec.encode(wrapper2); + el = RLPElement.readRLPTree(wrapper2); + for (int j = 0; j < 3; j++) { + assert el.get(0).get(j * 2).asString().length() == j + 1; + } + MapWrapper2 decoded = RLPCodec.decode(encoded, MapWrapper2.class); + assert decoded.map instanceof TreeMap; + assert decoded.map.get("sss").get("aaa").equals("bbb"); } } ``` diff --git a/build.gradle b/build.gradle index 702bec7..7a0a178 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { } group 'org.tdf' -version '1.0.7' +version '1.1.7' sourceCompatibility = 1.8 diff --git a/src/main/java/org/tdf/rlp/CollectionContainer.java b/src/main/java/org/tdf/rlp/CollectionContainer.java new file mode 100644 index 0000000..e5e0a9a --- /dev/null +++ b/src/main/java/org/tdf/rlp/CollectionContainer.java @@ -0,0 +1,28 @@ +package org.tdf.rlp; + +import java.util.Collection; + +class CollectionContainer implements Container { + Class collectionType; + + public ContainerType getContainerType() { + return ContainerType.COLLECTION; + } + + Container contentType; + + @Override + public Class asRawType() { + throw new RuntimeException("not a raw type"); + } + + @Override + public CollectionContainer asCollectionContainer() { + return this; + } + + @Override + public MapContainer asMapContainer() { + throw new RuntimeException("not a map container"); + } +} \ No newline at end of file diff --git a/src/main/java/org/tdf/rlp/Container.java b/src/main/java/org/tdf/rlp/Container.java new file mode 100644 index 0000000..b757fb5 --- /dev/null +++ b/src/main/java/org/tdf/rlp/Container.java @@ -0,0 +1,78 @@ +package org.tdf.rlp; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Map; + +interface Container { + ContainerType getContainerType(); + + Class asRawType(); + + CollectionContainer asCollectionContainer(); + + MapContainer asMapContainer(); + + static Container resolveField(Field field) { + Container container = resolveContainerofGeneric(field.getGenericType()); + Class clazz = null; + if (field.isAnnotationPresent(RLPDecoding.class)) { + clazz = field.getAnnotation(RLPDecoding.class).as(); + } + + if(clazz == null || clazz == Void.class) return container; + if(container.getContainerType() == ContainerType.RAW) + throw new RuntimeException("@RLPDecoding.as is used on collection or map type"); + if(!field.getType().isAssignableFrom(clazz)) + throw new RuntimeException("cannot assign " + clazz + " as " + field.getType()); + if(container.getContainerType() == ContainerType.COLLECTION){ + container.asCollectionContainer().collectionType = clazz; + } + if(container.getContainerType() == ContainerType.MAP){ + container.asMapContainer().mapType = clazz; + } + return container; + } + + static Container resolveContainerOfNoGeneric(Class clazz){ + if(Collection.class.isAssignableFrom(clazz)){ + CollectionContainer con = new CollectionContainer(); + con.contentType = new Raw(RLPElement.class); + con.collectionType = clazz; + return con; + } + if(Map.class.isAssignableFrom(clazz)){ + MapContainer con = new MapContainer(); + con.keyType = new Raw(RLPElement.class); + con.valueType = new Raw(RLPElement.class); + con.mapType = clazz; + return con; + } + return new Raw(clazz); + } + + static Container resolveContainerofGeneric(Type type) { + if (!(type instanceof ParameterizedType)) { + Class clazz = (Class) type; + return resolveContainerOfNoGeneric(clazz); + } + ParameterizedType parameterizedType = (ParameterizedType) type; + Class clazz = (Class) parameterizedType.getRawType(); + if (Collection.class.isAssignableFrom(clazz)) { + CollectionContainer con = new CollectionContainer(); + con.contentType = resolveContainerofGeneric(parameterizedType.getActualTypeArguments()[0]); + con.collectionType = clazz; + return con; + } + if (Map.class.isAssignableFrom(clazz)) { + MapContainer con = new MapContainer(); + con.keyType = resolveContainerofGeneric(parameterizedType.getActualTypeArguments()[0]); + con.valueType = resolveContainerofGeneric(parameterizedType.getActualTypeArguments()[1]); + con.mapType = clazz; + return con; + } + return resolveContainerOfNoGeneric(clazz); + } +} diff --git a/src/main/java/org/tdf/rlp/ContainerType.java b/src/main/java/org/tdf/rlp/ContainerType.java new file mode 100644 index 0000000..eb7d380 --- /dev/null +++ b/src/main/java/org/tdf/rlp/ContainerType.java @@ -0,0 +1,7 @@ +package org.tdf.rlp; + +public enum ContainerType { + RAW, + COLLECTION, + MAP +} diff --git a/src/main/java/org/tdf/rlp/LazyByteArray.java b/src/main/java/org/tdf/rlp/LazyByteArray.java index 87b4e2d..b37ebc3 100644 --- a/src/main/java/org/tdf/rlp/LazyByteArray.java +++ b/src/main/java/org/tdf/rlp/LazyByteArray.java @@ -3,7 +3,7 @@ import java.util.Arrays; // reduce byte array copy by lazy loading -class LazyByteArray { +final class LazyByteArray { private byte[] data; private int offset; private int limit; diff --git a/src/main/java/org/tdf/rlp/LazyElement.java b/src/main/java/org/tdf/rlp/LazyElement.java new file mode 100644 index 0000000..87ca165 --- /dev/null +++ b/src/main/java/org/tdf/rlp/LazyElement.java @@ -0,0 +1,132 @@ +package org.tdf.rlp; + +import java.math.BigInteger; + +public class LazyElement implements RLPElement{ + private RLPElement delegate; + + private RLPParser parser; + + public LazyElement(RLPParser parser) { + this.parser = parser; + } + + private void parse(){ + if (delegate != null) return; + delegate = parser.readElement(); + // release gc + parser = null; + } + + @Override + public boolean isRLPList() { + return parser.peekIsList(); + } + + @Override + public boolean isRLPItem() { + return !isRLPList(); + } + + @Override + public RLPList asRLPList() { + parse(); + return delegate.asRLPList(); + } + + @Override + public RLPItem asRLPItem() { + parse(); + return delegate.asRLPItem(); + } + + @Override + public boolean isNull() { + parse(); + return delegate.isNull(); + } + + @Override + public byte[] getEncoded() { + parse(); + return delegate.getEncoded(); + } + + @Override + public byte[] asBytes() { + parse(); + return delegate.asBytes(); + } + + @Override + public byte asByte() { + parse(); + return delegate.asByte(); + } + + @Override + public short asShort() { + parse(); + return delegate.asShort(); + } + + @Override + public int asInt() { + parse(); + return delegate.asInt(); + } + + @Override + public long asLong() { + parse(); + return delegate.asLong(); + } + + @Override + public BigInteger asBigInteger() { + parse(); + return delegate.asBigInteger(); + } + + @Override + public String asString() { + parse(); + return delegate.asString(); + } + + @Override + public boolean asBoolean() { + parse(); + return delegate.asBoolean(); + } + + @Override + public T as(Class clazz) { + parse(); + return delegate.as(clazz); + } + + @Override + public RLPElement get(int index) { + parse(); + return delegate.get(index); + } + + @Override + public boolean add(RLPElement element) { + parse(); + return delegate.add(element); + } + + @Override + public RLPElement set(int index, RLPElement element) { + parse(); + return delegate.set(index, element); + } + + @Override + public int size() { + parse(); + return delegate.size(); + } +} diff --git a/src/main/java/org/tdf/rlp/MapContainer.java b/src/main/java/org/tdf/rlp/MapContainer.java new file mode 100644 index 0000000..20d02f2 --- /dev/null +++ b/src/main/java/org/tdf/rlp/MapContainer.java @@ -0,0 +1,29 @@ +package org.tdf.rlp; + +import java.util.Map; + +public class MapContainer implements Container { + Class mapType; + + public ContainerType getContainerType() { + return ContainerType.MAP; + } + + Container keyType; + Container valueType; + + @Override + public Class asRawType() { + throw new RuntimeException("not a raw type"); + } + + @Override + public CollectionContainer asCollectionContainer() { + throw new RuntimeException("not a collection container"); + } + + @Override + public MapContainer asMapContainer() { + return this; + } +} diff --git a/src/main/java/org/tdf/rlp/RLPCodec.java b/src/main/java/org/tdf/rlp/RLPCodec.java new file mode 100644 index 0000000..e2c99c5 --- /dev/null +++ b/src/main/java/org/tdf/rlp/RLPCodec.java @@ -0,0 +1,341 @@ +package org.tdf.rlp; + +import lombok.NonNull; + +import java.lang.reflect.*; +import java.math.BigInteger; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.tdf.rlp.Container.resolveField; +import static org.tdf.rlp.RLPConstants.*; +import static org.tdf.rlp.RLPElement.readRLPTree; + +public final class RLPCodec { + public static T decode(byte[] data, Class clazz) { + RLPElement element = RLPElement.fromEncoded(data); + return decode(element, clazz); + } + + public static T decode(RLPElement element, Class clazz) { + if (clazz == RLPElement.class) return (T) element; + if (clazz == RLPList.class) return (T) element.asRLPList(); + if (clazz == RLPItem.class) return (T) element.asRLPItem(); + if (clazz == boolean.class || clazz == Boolean.class) return (T) Boolean.valueOf(element.asBoolean()); + RLPDecoder decoder = RLPUtils.getAnnotatedRLPDecoder(clazz); + if (decoder != null) return (T) decoder.decode(element); + // non null terminals + if (clazz == Byte.class || clazz == byte.class) { + return (T) Byte.valueOf(element.asByte()); + } + if (clazz == Short.class || clazz == short.class) { + return (T) Short.valueOf(element.asShort()); + } + if (clazz == Integer.class || clazz == int.class) { + return (T) Integer.valueOf(element.asInt()); + } + if (clazz == Long.class || clazz == long.class) { + return (T) Long.valueOf(element.asLong()); + } + if (clazz == byte[].class) { + return (T) element.asBytes(); + } + // String is non-null, since we cannot differ between null empty string and null + if (clazz == String.class) { + return (T) element.asString(); + } + // big integer is non-null, since we cannot differ between zero and null + if (clazz == BigInteger.class) { + return (T) element.asBigInteger(); + } + if (element.isNull()) return null; + if (clazz.isArray()) { + Class elementType = clazz.getComponentType(); + Object res = Array.newInstance(clazz.getComponentType(), element.asRLPList().size()); + for (int i = 0; i < element.asRLPList().size(); i++) { + Array.set(res, i, decode(element.asRLPList().get(i), elementType)); + } + return (T) res; + } + // cannot determine generic type at runtime + if (clazz == List.class || clazz == Collection.class) { + return (T) element.asRLPList(); + } + if (clazz == Set.class || clazz == HashSet.class) { + return (T) new HashSet(element.asRLPList()); + } + if (clazz == TreeSet.class) { + return (T) new TreeSet(element.asRLPList()); + } + if (clazz == Map.class || clazz == HashMap.class) { + HashMap m = new HashMap(element.size() / 2); + for (int i = 0; i < element.size(); i += 2) { + m.put(element.get(i), element.get(i + 1)); + } + return (T) m; + } + if (clazz == TreeMap.class) { + TreeMap m = new TreeMap(); + for (int i = 0; i < element.size(); i += 2) { + m.put(element.get(i), element.get(i + 1)); + } + return (T) m; + } + if(clazz == Queue.class || clazz == LinkedList.class){ + LinkedList list = new LinkedList(element.asRLPList()); + return (T) list; + } + if(clazz == Deque.class || clazz == ArrayDeque.class){ + return (T) new ArrayDeque<>(element.asRLPList()); + } + T o; + + try { + Constructor con = clazz.getDeclaredConstructor(); + con.setAccessible(true); + o = con.newInstance(); + } catch (Exception e) { + throw new RuntimeException(clazz + " should has a no arguments constructor"); + } + + List fields = RLPUtils.getRLPFields(clazz); + if (fields.size() == 0) throw new RuntimeException(clazz + " is not supported not RLP annotation found"); + for (int i = 0; i < fields.size(); i++) { + RLPElement el = element.asRLPList().get(i); + Field f = fields.get(i); + f.setAccessible(true); + RLPDecoder fieldDecoder = RLPUtils.getAnnotatedRLPDecoder(f); + if (fieldDecoder != null) { + try { + f.set(o, fieldDecoder.decode(el)); + } catch (Exception e) { + throw new RuntimeException(e); + } + continue; + } + + try { + if (el.isNull()) { + continue; + } + Container container = resolveField(f); + f.set(o, decodeContainer(el, container)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return o; + } + + // rlp primitives encoding/decoding + public static byte[] encodeBoolean(boolean b) { + return RLPItem.fromBoolean(b).getEncoded(); + } + + public static boolean decodeBoolean(byte[] encoded) { + return RLPElement.fromEncoded(encoded).asBoolean(); + } + + public static byte[] encodeByte(byte b) { + return RLPItem.fromByte(b).getEncoded(); + } + + public static byte[] encodeShort(short s) { + return RLPItem.fromShort(s).getEncoded(); + } + + public static byte[] encodeInt(int n) { + return RLPItem.fromInt(n).getEncoded(); + } + + public static byte[] encodeBigInteger(BigInteger bigInteger) { + return RLPItem.fromBigInteger(bigInteger).getEncoded(); + } + + public static byte[] encodeString(String s) { + return RLPItem.fromString(s).getEncoded(); + } + + public static int decodeInt(byte[] encoded) { + return RLPElement.fromEncoded(encoded).asInt(); + } + + public static short decodeShort(byte[] encoded) { + return RLPElement.fromEncoded(encoded).asShort(); + } + + public static long decodeLong(byte[] encoded) { + return RLPElement.fromEncoded(encoded).asLong(); + } + + public static String decodeString(byte[] encoded) { + return RLPElement.fromEncoded(encoded).asString(); + } + + public static byte[] encode(Object o) { + return readRLPTree(o).getEncoded(); + } + + // rlp list encode + public static byte[] encodeBytes(byte[] srcData) { + // [0x80] + if (srcData == null || srcData.length == 0) { + return new byte[]{(byte) OFFSET_SHORT_ITEM}; + // [0x00] + } + if (srcData.length == 1 && (srcData[0] & 0xFF) < OFFSET_SHORT_ITEM) { + return srcData; + // [0x80, 0xb7], 0 - 55 bytes + } + if (srcData.length < SIZE_THRESHOLD) { + // length = 8X + byte length = (byte) (OFFSET_SHORT_ITEM + srcData.length); + byte[] data = Arrays.copyOf(srcData, srcData.length + 1); + System.arraycopy(data, 0, data, 1, srcData.length); + data[0] = length; + + return data; + // [0xb8, 0xbf], 56+ bytes + } + // length of length = BX + // prefix = [BX, [length]] + int tmpLength = srcData.length; + byte lengthOfLength = 0; + while (tmpLength != 0) { + ++lengthOfLength; + tmpLength = tmpLength >> 8; + } + + // set length Of length at first byte + byte[] data = new byte[1 + lengthOfLength + srcData.length]; + data[0] = (byte) (OFFSET_LONG_ITEM + lengthOfLength); + + // copy length after first byte + tmpLength = srcData.length; + for (int i = lengthOfLength; i > 0; --i) { + data[i] = (byte) (tmpLength & 0xFF); + tmpLength = tmpLength >> 8; + } + + // at last copy the number bytes after its length + System.arraycopy(srcData, 0, data, 1 + lengthOfLength, srcData.length); + + return data; + } + + public static byte[] encodeElements(@NonNull Collection elements) { + int totalLength = 0; + for (byte[] element1 : elements) { + totalLength += element1.length; + } + + byte[] data; + int copyPos; + if (totalLength < SIZE_THRESHOLD) { + + data = new byte[1 + totalLength]; + data[0] = (byte) (OFFSET_SHORT_LIST + totalLength); + copyPos = 1; + } else { + // length of length = BX + // prefix = [BX, [length]] + int tmpLength = totalLength; + byte byteNum = 0; + while (tmpLength != 0) { + ++byteNum; + tmpLength = tmpLength >> 8; + } + tmpLength = totalLength; + byte[] lenBytes = new byte[byteNum]; + for (int i = 0; i < byteNum; ++i) { + lenBytes[byteNum - 1 - i] = (byte) ((tmpLength >> (8 * i)) & 0xFF); + } + // first byte = F7 + bytes.length + data = new byte[1 + lenBytes.length + totalLength]; + data[0] = (byte) (OFFSET_LONG_LIST + byteNum); + System.arraycopy(lenBytes, 0, data, 1, lenBytes.length); + + copyPos = lenBytes.length + 1; + } + for (byte[] element : elements) { + System.arraycopy(element, 0, data, copyPos, element.length); + copyPos += element.length; + } + return data; + } + + static RLPElement encodeCollection(Collection col, Comparator contentOrdering) { + Stream s = contentOrdering == null ? col.stream() : col.stream().sorted(contentOrdering); + return new RLPList(s.map(RLPElement::readRLPTree) + .collect(Collectors.toList())); + } + + static RLPElement encodeMap(Map m, Comparator keyOrdering) { + RLPList list = RLPList.createEmpty(m.size() * 2); + Stream keys = keyOrdering == null ? m.keySet().stream() : m.keySet().stream().sorted(keyOrdering); + keys.forEach(x -> { + list.add(readRLPTree(x)); + list.add(readRLPTree(m.get(x))); + }); + return list; + } + + static Object decodeContainer(RLPElement element, Container container) { + switch (container.getContainerType()) { + case RAW: + return decode(element, container.asRawType()); + case COLLECTION: { + try { + CollectionContainer collectionContainer = container.asCollectionContainer(); + Collection res = (Collection) getDefaultImpl(collectionContainer.collectionType).newInstance(); + for (int i = 0; i < element.size(); i++) { + res.add(decodeContainer(element.get(i), collectionContainer.contentType)); + } + return res; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + case MAP: { + try { + MapContainer mapContainer = container.asMapContainer(); + Map res = (Map) getDefaultImpl(mapContainer.mapType).newInstance(); + for (int i = 0; i < element.size(); i += 2) { + res.put( + decodeContainer(element.get(i), mapContainer.keyType), + decodeContainer(element.get(i + 1), mapContainer.valueType) + ); + } + return res; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + throw new RuntimeException("unreachable"); + } + + private static Class getDefaultImpl(Class clazz) { + if (clazz == Collection.class + || clazz == List.class + ) { + return ArrayList.class; + } + if (clazz == Set.class) { + return HashSet.class; + } + if (clazz == Queue.class) { + return LinkedList.class; + } + if (clazz == Deque.class) { + return ArrayDeque.class; + } + if (clazz == Map.class) { + return HashMap.class; + } + if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())) + throw new RuntimeException("cannot new instance of " + clazz); + return clazz; + } +} diff --git a/src/main/java/org/tdf/rlp/RLPConstants.java b/src/main/java/org/tdf/rlp/RLPConstants.java index c5b09b2..3e98a25 100644 --- a/src/main/java/org/tdf/rlp/RLPConstants.java +++ b/src/main/java/org/tdf/rlp/RLPConstants.java @@ -1,13 +1,13 @@ package org.tdf.rlp; -public class RLPConstants { +public final class RLPConstants { /** * [0x80] * If a string is 0-55 bytes long, the RLP encoding consists of a single * byte with value 0x80 plus the length of the string followed by the * string. The range of the first byte is thus [0x80, 0xb7]. */ - static final int OFFSET_SHORT_ITEM = 0x80; + public static final int OFFSET_SHORT_ITEM = 0x80; /** * Reason for threshold according to Vitalik Buterin: @@ -18,7 +18,7 @@ public class RLPConstants { * - so 56 and 2^64 space seems like the right place to put the cutoff * - also, that's where Bitcoin's varint does the cutof */ - static final int SIZE_THRESHOLD = 56; + public static final int SIZE_THRESHOLD = 56; /** * [0xb7] @@ -29,7 +29,7 @@ public class RLPConstants { * \xb9\x04\x00 followed by the string. The range of the first byte is thus * [0xb8, 0xbf]. */ - static final int OFFSET_LONG_ITEM = 0xb7; + public static final int OFFSET_LONG_ITEM = 0xb7; /** * [0xc0] @@ -39,7 +39,7 @@ public class RLPConstants { * of the RLP encodings of the items. The range of the first byte is thus * [0xc0, 0xf7]. */ - static final int OFFSET_SHORT_LIST = 0xc0; + public static final int OFFSET_SHORT_LIST = 0xc0; /** * [0xf7] @@ -49,5 +49,5 @@ public class RLPConstants { * followed by the concatenation of the RLP encodings of the items. The * range of the first byte is thus [0xf8, 0xff]. */ - static final int OFFSET_LONG_LIST = 0xf7; + public static final int OFFSET_LONG_LIST = 0xf7; } diff --git a/src/main/java/org/tdf/rlp/RLPDecoder.java b/src/main/java/org/tdf/rlp/RLPDecoder.java index 596f06e..c45c837 100644 --- a/src/main/java/org/tdf/rlp/RLPDecoder.java +++ b/src/main/java/org/tdf/rlp/RLPDecoder.java @@ -3,7 +3,7 @@ public interface RLPDecoder { T decode(RLPElement element); - class None implements RLPDecoder{ + class None implements RLPDecoder{ @Override public Object decode(RLPElement element) { return null; diff --git a/src/main/java/org/tdf/rlp/RLPDecoding.java b/src/main/java/org/tdf/rlp/RLPDecoding.java index e63b252..19ce1f4 100644 --- a/src/main/java/org/tdf/rlp/RLPDecoding.java +++ b/src/main/java/org/tdf/rlp/RLPDecoding.java @@ -4,9 +4,11 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.*; @Target({ElementType.FIELD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface RLPDecoding { Class value() default RLPDecoder.None.class; + Class as() default Void.class; } diff --git a/src/main/java/org/tdf/rlp/RLPDeserializer.java b/src/main/java/org/tdf/rlp/RLPDeserializer.java deleted file mode 100644 index a1bf115..0000000 --- a/src/main/java/org/tdf/rlp/RLPDeserializer.java +++ /dev/null @@ -1,133 +0,0 @@ -package org.tdf.rlp; - -import java.lang.reflect.Array; -import java.lang.reflect.Field; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -public class RLPDeserializer { - - public static T deserialize(byte[] data, Class clazz) { - RLPElement element = RLPElement.fromEncoded(data); - return deserialize(element, clazz); - } - - public static List deserializeList(RLPElement element, Class elementType){ - return deserializeList(element.getAsList(), 1, elementType); - } - - public static List deserializeList(byte[] data, Class elementType) { - RLPElement element = RLPElement.fromEncoded(data); - return deserializeList(element.getAsList(), 1, elementType); - } - - private static List deserializeList(RLPList list, int level, Class elementType) { - if (level == 0) throw new RuntimeException("level should be positive"); - if (level > 1) { - List res = new ArrayList(list.size()); - for (int i = 0; i < list.size(); i++) { - res.add(deserializeList(list.get(i).getAsList(), level - 1, elementType)); - } - return res; - } - if (elementType == RLPElement.class) return list; - if (elementType == RLPItem.class) { - return list.stream().map(x -> x.getAsItem()).collect(Collectors.toList()); - } - List res = new ArrayList<>(list.size()); - for (int i = 0; i < list.size(); i++) { - res.add(deserialize(list.get(i), elementType)); - } - return res; - } - - public static T deserialize(RLPElement element, Class clazz) { - if (clazz == RLPElement.class) return (T) element; - if (clazz == RLPList.class) return (T) element.getAsList(); - if (clazz == RLPItem.class) return (T) element.getAsItem(); - if(clazz == boolean.class || clazz == Boolean.class) return (T) Boolean.valueOf(element.getAsItem().getBoolean()); - RLPDecoder decoder = RLPUtils.getAnnotatedRLPDecoder(clazz); - if (decoder != null) return (T) decoder.decode(element); - // non null terminals - if (clazz == Byte.class || clazz == byte.class) { - return (T) Byte.valueOf(element.getAsItem().getByte()); - } - if (clazz == Short.class || clazz == short.class) { - return (T) Short.valueOf(element.getAsItem().getShort()); - } - if (clazz == Integer.class || clazz == int.class) { - return (T) Integer.valueOf(element.getAsItem().getInt()); - } - if (clazz == Long.class || clazz == long.class) { - return (T) Long.valueOf(element.getAsItem().getLong()); - } - if (clazz == byte[].class) { - return (T) element.getAsItem().get(); - } - // String is non-null, since we cannot differ between null empty string and null - if (clazz == String.class) { - return (T) element.getAsItem().getString(); - } - // big integer is non-null, since we cannot differ between zero and null - if (clazz == BigInteger.class) { - return (T) element.getAsItem().getBigInteger(); - } - if (element.isNull()) return null; - if (clazz.isArray()) { - Class elementType = clazz.getComponentType(); - Object res = Array.newInstance(clazz.getComponentType(), element.getAsList().size()); - for (int i = 0; i < element.getAsList().size(); i++) { - Array.set(res, i, deserialize(element.getAsList().get(i), elementType)); - } - return (T) res; - } - // cannot determine generic type at runtime - if (clazz == List.class) { - return (T) deserializeList(element.getAsList(), 1, RLPElement.class); - } - Object o; - try { - o = clazz.newInstance(); - } catch (Exception e) { - throw new RuntimeException(e); - } - List fields = RLPUtils.getRLPFields(clazz); - if (fields.size() == 0) throw new RuntimeException(clazz + " is not supported not RLP annotation found"); - for (int i = 0; i < fields.size(); i++) { - RLPElement el = element.getAsList().get(i); - Field f = fields.get(i); - f.setAccessible(true); - RLPDecoder fieldDecoder = RLPUtils.getAnnotatedRLPDecoder(f); - if (fieldDecoder != null) { - try { - f.set(o, fieldDecoder.decode(el)); - } catch (Exception e) { - throw new RuntimeException(e); - } - continue; - } - - if (!f.getType().equals(List.class)) { - try { - f.set(o, deserialize(el, f.getType())); - } catch (Exception e) { - throw new RuntimeException(e); - } - continue; - } - - try { - if (el.isNull()) { - continue; - } - RLPUtils.Resolved resolved = RLPUtils.resolveFieldType(f); - f.set(o, deserializeList(el.getAsList(), resolved.level, resolved.type)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - return (T) o; - } -} diff --git a/src/main/java/org/tdf/rlp/RLPElement.java b/src/main/java/org/tdf/rlp/RLPElement.java index bec1ef3..c38a542 100644 --- a/src/main/java/org/tdf/rlp/RLPElement.java +++ b/src/main/java/org/tdf/rlp/RLPElement.java @@ -3,9 +3,9 @@ import java.lang.reflect.Array; import java.lang.reflect.Field; import java.math.BigInteger; -import java.util.Collection; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.tdf.rlp.RLPItem.NULL; import static org.tdf.rlp.RLPItem.ONE; @@ -17,27 +17,61 @@ * A list of items is an item * RLP could encode tree-like object * RLP cannot determine difference between null reference and emtpy byte array - * RLP cannot determine whether a box type is null or zero, e.g. Byte, Short, Integer, Long + * RLP cannot determine whether a box type is null or zero, e.g. Byte, Short, Integer, Long, BigInteger */ public interface RLPElement { - boolean isList(); + boolean isRLPList(); - RLPList getAsList(); + boolean isRLPItem(); - RLPItem getAsItem(); + RLPList asRLPList(); + + RLPItem asRLPItem(); boolean isNull(); byte[] getEncoded(); + byte[] asBytes(); + + byte asByte(); + + short asShort(); + + int asInt(); + + long asLong(); + + int size(); + + RLPElement get(int index); + + boolean add(RLPElement element); + + RLPElement set(int index, RLPElement element); + + BigInteger asBigInteger(); + + String asString(); + + boolean asBoolean(); + + default T as(Class clazz) { + return RLPCodec.decode(this, clazz); + } + static RLPElement fromEncoded(byte[] data) { - return RLPReader.fromEncoded(data); + return fromEncoded(data, true); + } + + static RLPElement fromEncoded(byte[] data, boolean lazy) { + return RLPParser.fromEncoded(data, lazy); } - // encode any object as a rlp element - static RLPElement encode(Object t) { + // convert any object as a rlp tree + static RLPElement readRLPTree(Object t) { if (t == null) return NULL; - if(t instanceof Boolean || t.getClass() == boolean.class){ + if (t instanceof Boolean || t.getClass() == boolean.class) { return ((Boolean) t) ? ONE : NULL; } if (t instanceof RLPElement) return (RLPElement) t; @@ -61,25 +95,30 @@ static RLPElement encode(Object t) { if (t instanceof Long || t.getClass().equals(long.class)) { return RLPItem.fromLong((long) t); } + if (t instanceof Map) { + return RLPCodec.encodeMap((Map) t, null); + } if (t.getClass().isArray()) { RLPList list = RLPList.createEmpty(Array.getLength(t)); for (int i = 0; i < Array.getLength(t); i++) { - list.add(encode(Array.get(t, i))); + list.add(readRLPTree(Array.get(t, i))); } return list; } if (t instanceof Collection) { - RLPList list = RLPList.createEmpty(((Collection) t).size()); - for (Object o : ((Collection) t)) { - list.add(encode(o)); - } - return list; + return RLPCodec.encodeCollection((Collection) t, null); } // peek fields reflection List fields = RLPUtils.getRLPFields(t.getClass()); - if (fields.size() == 0) throw new RuntimeException(t.getClass() + " is not supported, no @RLP annotation found"); + if (fields.size() == 0) + throw new RuntimeException(t.getClass() + " is not supported, no @RLP annotation found"); return new RLPList(fields.stream().map(f -> { f.setAccessible(true); + try { + if (f.get(t) == null) return NULL; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } RLPEncoder fieldEncoder = RLPUtils.getAnnotatedRLPEncoder(f); if (fieldEncoder != null) { try { @@ -88,11 +127,30 @@ static RLPElement encode(Object t) { throw new RuntimeException(e); } } + Comparator comparator = RLPUtils.getContentOrdering(f); + if (Collection.class.isAssignableFrom(f.getType())) { + try { + return RLPCodec.encodeCollection((Collection) f.get(t), comparator); + } catch (Exception e) { + throw new RuntimeException("get field " + f + " failed " + e.getCause()); + } + } + comparator = RLPUtils.getKeyOrdering(f); + if (Map.class.isAssignableFrom(f.getType())) { + try { + Map m = (Map) f.get(t); + return RLPCodec.encodeMap(m, comparator); + } catch (Exception e) { + throw new RuntimeException(e); + } + } try { - return encode(f.get(t)); + return readRLPTree(f.get(t)); } catch (Exception e) { throw new RuntimeException(e); } }).collect(Collectors.toList())); } + + } diff --git a/src/main/java/org/tdf/rlp/RLPEncoder.java b/src/main/java/org/tdf/rlp/RLPEncoder.java index 7cdb034..5952426 100644 --- a/src/main/java/org/tdf/rlp/RLPEncoder.java +++ b/src/main/java/org/tdf/rlp/RLPEncoder.java @@ -3,7 +3,7 @@ public interface RLPEncoder { RLPElement encode(T o); - class None implements RLPEncoder{ + class None implements RLPEncoder{ @Override public RLPElement encode(Object o) { return null; diff --git a/src/main/java/org/tdf/rlp/RLPEncoding.java b/src/main/java/org/tdf/rlp/RLPEncoding.java index ab9fcd1..3cbde93 100644 --- a/src/main/java/org/tdf/rlp/RLPEncoding.java +++ b/src/main/java/org/tdf/rlp/RLPEncoding.java @@ -4,9 +4,21 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.*; @Target({java.lang.annotation.ElementType.FIELD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface RLPEncoding { Class value() default RLPEncoder.None.class; + + // content ordering of set + Class contentOrdering() default None.class; + Class keyOrdering() default None.class; + + class None implements Comparator{ + @Override + public int compare(Object o1, Object o2) { + return 0; + } + } } diff --git a/src/main/java/org/tdf/rlp/RLPItem.java b/src/main/java/org/tdf/rlp/RLPItem.java index d9b4d89..d969912 100644 --- a/src/main/java/org/tdf/rlp/RLPItem.java +++ b/src/main/java/org/tdf/rlp/RLPItem.java @@ -6,13 +6,13 @@ import java.util.Arrays; import static org.tdf.rlp.LazyByteArray.EMPTY; -import static org.tdf.rlp.RLPConstants.*; +import static org.tdf.rlp.RLPCodec.encodeBytes; /** * immutable rlp item */ public final class RLPItem implements RLPElement { - private static byte[] NULL_ENCODED = encodeElement(null); + private static byte[] NULL_ENCODED = encodeBytes(null); public static final RLPItem ONE = new RLPItem(new LazyByteArray(new byte[]{1})); private LazyByteArray data; @@ -25,7 +25,7 @@ void setEncoded(LazyByteArray encoded) { this.encoded = encoded; } - public byte[] get() { + public byte[] asBytes() { return data.get(); } @@ -57,17 +57,21 @@ public static RLPItem fromLong(long l) { } public static RLPItem fromString(String s) { + if(s == null) return NULL; return fromBytes(s.getBytes(StandardCharsets.UTF_8)); } public static RLPItem fromBytes(byte[] data) { if (data == null || data.length == 0) return NULL; + if(data.length == 1 && (Byte.toUnsignedInt(data[0]) == 1)) return ONE; return new RLPItem(new LazyByteArray(data)); } public static RLPItem fromBigInteger(BigInteger bigInteger) { + if (bigInteger == null || bigInteger.equals(BigInteger.ZERO)) return NULL; + if (bigInteger.equals(BigInteger.ONE)) return ONE; if (bigInteger.compareTo(BigInteger.ZERO) < 0) throw new RuntimeException("negative numbers are not allowed"); - if (bigInteger.equals(BigInteger.ZERO)) return NULL; + return fromBytes(asUnsignedByteArray(bigInteger)); } @@ -92,17 +96,17 @@ private static byte[] asUnsignedByteArray( } @Override - public boolean isList() { + public boolean isRLPList() { return false; } @Override - public RLPList getAsList() { + public RLPList asRLPList() { throw new RuntimeException("not a rlp list"); } @Override - public RLPItem getAsItem() { + public RLPItem asRLPItem() { return this; } @@ -110,146 +114,58 @@ public boolean isNull() { return this == NULL || data.size() == 0; } - public byte getByte() { - if (Long.compareUnsigned(getLong(), 0xffL) > 0) throw new RuntimeException("invalid byte, overflow"); - return (byte) getLong(); + public byte asByte() { + if (Long.compareUnsigned(asLong(), 0xffL) > 0) throw new RuntimeException("invalid byte, overflow"); + return (byte) asLong(); } - public short getShort() { - if (Long.compareUnsigned(getLong(), 0xffff) > 0) throw new RuntimeException("invalid short, overflow"); - return (short) getLong(); + public short asShort() { + if (Long.compareUnsigned(asLong(), 0xffff) > 0) throw new RuntimeException("invalid short, overflow"); + return (short) asLong(); } - public int getInt() { - if (Long.compareUnsigned(getLong(), 0xffffffff) > 0) throw new RuntimeException("invalid int, overflow"); - return (int) getLong(); + public int asInt() { + if (Long.compareUnsigned(asLong(), 0xffffffff) > 0) throw new RuntimeException("invalid int, overflow"); + return (int) asLong(); } - public long getLong() { + public long asLong() { + if (isNull()) { + return 0; + } + if (this == ONE) return 1; if (longNumber != null) return longNumber; // numbers are ont starts with zero byte - byte[] data = get(); + byte[] data = asBytes(); if (data.length > 0 && data[0] == 0) throw new RuntimeException("not a number"); - if (isNull()) { - longNumber = 0L; - return longNumber; - } if (data.length > Long.BYTES) throw new RuntimeException("not a number"); longNumber = ByteBuffer.wrap(concat(new byte[Long.BYTES - data.length], data)).getLong(); return longNumber; } - public BigInteger getBigInteger() { - byte[] data = get(); - if (data[0] == 0) throw new RuntimeException("not a number"); + public BigInteger asBigInteger() { if (isNull()) return BigInteger.ZERO; + if (this == ONE) return BigInteger.ONE; + byte[] data = asBytes(); + if (data[0] == 0) throw new RuntimeException("not a number"); return new BigInteger(1, data); } - public String getString() { - return new String(get(), StandardCharsets.UTF_8); + public String asString() { + return new String(asBytes(), StandardCharsets.UTF_8); } - public boolean getBoolean() { - if (getLong() > 1) throw new RuntimeException("not a boolean"); - return getLong() == 1; + public boolean asBoolean() { + if (asLong() > 1) throw new RuntimeException("not a boolean"); + return asLong() == 1; } public byte[] getEncoded() { if (isNull()) return NULL_ENCODED; - if (encoded == null) encoded = new LazyByteArray(encodeElement(get())); + if (encoded == null) encoded = new LazyByteArray(encodeBytes(asBytes())); return encoded.get(); } - public static byte[] encodeElement(byte[] srcData) { - // [0x80] - if (srcData == null || srcData.length == 0) { - return new byte[]{(byte) OFFSET_SHORT_ITEM}; - // [0x00] - } - if (srcData.length == 1 && (srcData[0] & 0xFF) < OFFSET_SHORT_ITEM) { - return srcData; - // [0x80, 0xb7], 0 - 55 bytes - } - if (srcData.length < SIZE_THRESHOLD) { - // length = 8X - byte length = (byte) (OFFSET_SHORT_ITEM + srcData.length); - byte[] data = Arrays.copyOf(srcData, srcData.length + 1); - System.arraycopy(data, 0, data, 1, srcData.length); - data[0] = length; - - return data; - // [0xb8, 0xbf], 56+ bytes - } - // length of length = BX - // prefix = [BX, [length]] - int tmpLength = srcData.length; - byte lengthOfLength = 0; - while (tmpLength != 0) { - ++lengthOfLength; - tmpLength = tmpLength >> 8; - } - - // set length Of length at first byte - byte[] data = new byte[1 + lengthOfLength + srcData.length]; - data[0] = (byte) (OFFSET_LONG_ITEM + lengthOfLength); - - // copy length after first byte - tmpLength = srcData.length; - for (int i = lengthOfLength; i > 0; --i) { - data[i] = (byte) (tmpLength & 0xFF); - tmpLength = tmpLength >> 8; - } - - // at last copy the number bytes after its length - System.arraycopy(srcData, 0, data, 1 + lengthOfLength, srcData.length); - - return data; - } - - public static byte[] encodeBoolean(boolean b) { - return fromBoolean(b).getEncoded(); - } - - public static boolean decodeBoolean(byte[] encoded) { - return RLPElement.fromEncoded(encoded).getAsItem().getBoolean(); - } - - public static byte[] encodeByte(byte b) { - return fromByte(b).getEncoded(); - } - - public static byte[] encodeShort(short s) { - return fromShort(s).getEncoded(); - } - - public static byte[] encodeInt(int n) { - return fromInt(n).getEncoded(); - } - - public static byte[] encodeBigInteger(BigInteger bigInteger) { - return fromBigInteger(bigInteger).getEncoded(); - } - - public static byte[] encodeString(String s) { - return fromString(s).getEncoded(); - } - - public static int decodeInt(byte[] encoded) { - return RLPElement.fromEncoded(encoded).getAsItem().getInt(); - } - - public static short decodeShort(byte[] encoded) { - return RLPElement.fromEncoded(encoded).getAsItem().getShort(); - } - - public static long decodeLong(byte[] encoded) { - return RLPElement.fromEncoded(encoded).getAsItem().getLong(); - } - - public static String decodeString(byte[] encoded) { - return RLPElement.fromEncoded(encoded).getAsItem().getString(); - } /** * Returns the values from each provided array combined into a single array. For example, {@code @@ -271,4 +187,29 @@ private static byte[] concat(byte[]... arrays) { } return result; } + + @Override + public boolean isRLPItem() { + return true; + } + + @Override + public RLPElement get(int index) { + throw new RuntimeException("not a rlp list"); + } + + @Override + public boolean add(RLPElement element) { + throw new RuntimeException("not a rlp list"); + } + + @Override + public RLPElement set(int index, RLPElement element) { + throw new RuntimeException("not a rlp list"); + } + + @Override + public int size() { + throw new RuntimeException("not a rlp list"); + } } diff --git a/src/main/java/org/tdf/rlp/RLPList.java b/src/main/java/org/tdf/rlp/RLPList.java index 9810bb6..5648d19 100644 --- a/src/main/java/org/tdf/rlp/RLPList.java +++ b/src/main/java/org/tdf/rlp/RLPList.java @@ -1,22 +1,19 @@ package org.tdf.rlp; -import lombok.NonNull; - +import java.math.BigInteger; import java.util.*; import java.util.function.UnaryOperator; import java.util.stream.Collectors; -import static org.tdf.rlp.RLPConstants.*; - public final class RLPList implements RLPElement, List { - static byte[] EMPTY_ENCODED_LIST = encodeList(new ArrayList<>()); + static byte[] EMPTY_ENCODED_LIST = RLPCodec.encodeElements(new ArrayList<>()); public static RLPList of(RLPElement... elements) { return new RLPList(Arrays.asList(elements)); } public static RLPList fromElements(Collection elements) { - return new RLPList(elements.stream().collect(Collectors.toList())); + return new RLPList(new ArrayList<>(elements)); } public static RLPList createEmpty() { @@ -47,29 +44,29 @@ private RLPList() { } @Override - public boolean isList() { + public boolean isRLPList() { return true; } @Override - public RLPList getAsList() { + public RLPList asRLPList() { return this; } @Override - public RLPItem getAsItem() { + public RLPItem asRLPItem() { throw new RuntimeException("not a rlp item"); } @Override public byte[] getEncoded() { - if(size() == 0) return EMPTY_ENCODED_LIST; - if(encoded != null) return encoded.get(); + if (size() == 0) return EMPTY_ENCODED_LIST; + if (encoded != null) return encoded.get(); encoded = new LazyByteArray( - encodeList( - stream().map(RLPElement::getEncoded) - .collect(Collectors.toList()) - ) + RLPCodec.encodeElements( + stream().map(RLPElement::getEncoded) + .collect(Collectors.toList()) + ) ); return encoded.get(); } @@ -79,46 +76,6 @@ public boolean isNull() { return false; } - public static byte[] encodeList(@NonNull Collection elements) { - int totalLength = 0; - for (byte[] element1 : elements) { - totalLength += element1.length; - } - - byte[] data; - int copyPos; - if (totalLength < SIZE_THRESHOLD) { - - data = new byte[1 + totalLength]; - data[0] = (byte) (OFFSET_SHORT_LIST + totalLength); - copyPos = 1; - } else { - // length of length = BX - // prefix = [BX, [length]] - int tmpLength = totalLength; - byte byteNum = 0; - while (tmpLength != 0) { - ++byteNum; - tmpLength = tmpLength >> 8; - } - tmpLength = totalLength; - byte[] lenBytes = new byte[byteNum]; - for (int i = 0; i < byteNum; ++i) { - lenBytes[byteNum - 1 - i] = (byte) ((tmpLength >> (8 * i)) & 0xFF); - } - // first byte = F7 + bytes.length - data = new byte[1 + lenBytes.length + totalLength]; - data[0] = (byte) (OFFSET_LONG_LIST + byteNum); - System.arraycopy(lenBytes, 0, data, 1, lenBytes.length); - - copyPos = lenBytes.length + 1; - } - for (byte[] element : elements) { - System.arraycopy(element, 0, data, copyPos, element.length); - copyPos += element.length; - } - return data; - } @Override public int size() { @@ -277,4 +234,49 @@ public RLPList subList(int fromIndex, int toIndex) { public Spliterator spliterator() { return elements.spliterator(); } + + @Override + public boolean isRLPItem() { + return false; + } + + @Override + public byte[] asBytes() { + throw new RuntimeException("not a rlp item"); + } + + @Override + public byte asByte() { + throw new RuntimeException("not a rlp item"); + } + + @Override + public short asShort() { + throw new RuntimeException("not a rlp item"); + } + + @Override + public long asLong() { + throw new RuntimeException("not a rlp item"); + } + + @Override + public BigInteger asBigInteger() { + throw new RuntimeException("not a rlp item"); + } + + @Override + public String asString() { + throw new RuntimeException("not a rlp item"); + } + + @Override + public boolean asBoolean() { + throw new RuntimeException("not a rlp item"); + } + + @Override + public int asInt() { + throw new RuntimeException("not a rlp item"); + } } diff --git a/src/main/java/org/tdf/rlp/RLPReader.java b/src/main/java/org/tdf/rlp/RLPParser.java similarity index 71% rename from src/main/java/org/tdf/rlp/RLPReader.java rename to src/main/java/org/tdf/rlp/RLPParser.java index 08cb83a..7e06efb 100644 --- a/src/main/java/org/tdf/rlp/RLPReader.java +++ b/src/main/java/org/tdf/rlp/RLPParser.java @@ -8,7 +8,7 @@ import static org.tdf.rlp.RLPConstants.*; -class RLPReader { +final class RLPParser { private static int byteArrayToInt(byte[] b) { if (b == null || b.length == 0) return 0; @@ -21,30 +21,30 @@ private static int byteArrayToInt(byte[] b) { private int limit; - static RLPElement fromEncoded(@NonNull byte[] data) { - RLPReader reader = new RLPReader(data); - if (reader.estimateSize() != data.length) { + static RLPElement fromEncoded(@NonNull byte[] data, boolean lazy) { + RLPParser parser = new RLPParser(data); + if (parser.estimateSize() != data.length) { throw new RuntimeException("invalid encoding"); } - return reader.readElement(); + return lazy ? parser.readLazy() : parser.readElement(); } - private RLPReader(byte[] data) { + private RLPParser(byte[] data) { this.raw = data; this.limit = data.length; } - private RLPReader(byte[] data, int offset, int limit) { + private RLPParser(byte[] data, int offset, int limit) { this.raw = data; this.offset = offset; this.limit = limit; } - private RLPReader readAsReader(int length) { + private RLPParser readAsParser(int length) { if (offset + length > limit) throw new RuntimeException("read overflow"); - RLPReader reader = new RLPReader(raw, offset, offset + length); + RLPParser parser = new RLPParser(raw, offset, offset + length); offset += length; - return reader; + return parser; } private int estimateSize() { @@ -58,13 +58,13 @@ private int estimateSize() { if (prefix < OFFSET_SHORT_LIST) { // skip return byteArrayToInt( - Arrays.copyOfRange(raw, 1, 1 + prefix - OFFSET_LONG_ITEM) + Arrays.copyOfRange(raw, offset + 1, offset + 1 + prefix - OFFSET_LONG_ITEM) ) + 1 + prefix - OFFSET_LONG_ITEM; } if (prefix <= OFFSET_LONG_LIST) { return prefix - OFFSET_SHORT_LIST + 1; } - return byteArrayToInt(Arrays.copyOfRange(raw, 1, 1 + prefix - OFFSET_LONG_LIST)) + 1 + prefix - OFFSET_LONG_LIST; + return byteArrayToInt(Arrays.copyOfRange(raw, offset + 1, offset + 1 + prefix - OFFSET_LONG_LIST)) + 1 + prefix - OFFSET_LONG_LIST; } private int read() { @@ -88,39 +88,48 @@ private int peek() { } - private boolean peekIsList() { + boolean peekIsList() { return peek() >= OFFSET_SHORT_LIST; } - private RLPList readList() { + private RLPList readList(boolean lazy) { int offset = this.offset; int prefix = read(); RLPList list = RLPList.createEmpty(); - RLPReader reader; + RLPParser parser; if (prefix <= OFFSET_LONG_LIST) { int len = prefix - OFFSET_SHORT_LIST; // length of length the encoded bytes // skip preifx if (len == 0) return list; - reader = readAsReader(len); + parser = readAsParser(len); } else { int lenlen = prefix - OFFSET_LONG_LIST; // length of length the encoded list int lenlist = byteArrayToInt(read(lenlen)); // length of encoded bytes - reader = readAsReader(lenlist); + parser = readAsParser(lenlist); } - int limit = reader.limit; - while (reader.hasRemaining()) { - list.add(reader.readElement()); + int limit = parser.limit; + while (parser.hasRemaining()) { + list.add(lazy ? parser.readLazyElement() : parser.readElement()); } list.setEncoded(new LazyByteArray(raw, offset, limit)); return list; } - private RLPElement readElement() { - if (peekIsList()) return readList(); + RLPElement readElement() { + if (peekIsList()) return readList(false); return readItem(); } + RLPElement readLazy() { + if (peekIsList()) return readList(true); + return readItem(); + } + + LazyElement readLazyElement() { + return new LazyElement(readAsParser(estimateSize())); + } + private RLPItem readItem() { int initOffset = this.offset; int prefix = read(); diff --git a/src/main/java/org/tdf/rlp/RLPUtils.java b/src/main/java/org/tdf/rlp/RLPUtils.java index 44f4708..5420a03 100644 --- a/src/main/java/org/tdf/rlp/RLPUtils.java +++ b/src/main/java/org/tdf/rlp/RLPUtils.java @@ -1,15 +1,10 @@ package org.tdf.rlp; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Field; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; +import java.lang.reflect.*; +import java.util.*; import java.util.stream.Collectors; -class RLPUtils { +final class RLPUtils { static RLPEncoder getAnnotatedRLPEncoder(AnnotatedElement element) { if (!element.isAnnotationPresent(RLPEncoding.class)) { return null; @@ -51,41 +46,33 @@ static List getRLPFields(Class clazz) { return fields; } - - static Resolved resolveFieldType(Field f) { - if (f.getType() != List.class) { - return new Resolved(0, f.getType()); - } - Type generic = f.getGenericType(); - if (!(generic instanceof ParameterizedType)) { - return new Resolved(1, RLPElement.class); + static Comparator getContentOrdering(AnnotatedElement element) { + if (!element.isAnnotationPresent(RLPEncoding.class)) { + return null; } - return resolve((ParameterizedType) generic, new Resolved(1, null)); - } - - private static Resolved resolve(ParameterizedType type, Resolved resolved) { - Type[] types = type.getActualTypeArguments(); - Type t = types[0]; - if (t instanceof Class) { - resolved.type = (Class) t; - return resolved; + Class clazz = element.getAnnotation(RLPEncoding.class).contentOrdering(); + if (clazz == RLPEncoding.None.class) return null; + try { + Constructor con = clazz.getDeclaredConstructor(); + con.setAccessible(true); + return con.newInstance(); + } catch (Exception e) { + throw new RuntimeException("new instance of " + clazz + " failed " + e.getMessage()); } - // type is nested; - ParameterizedType nested = (ParameterizedType) t; - resolved.level += 1; - return resolve(nested, resolved); } - static class Resolved { - int level; - Class type; - - public Resolved() { + static Comparator getKeyOrdering(AnnotatedElement element) { + if (!element.isAnnotationPresent(RLPEncoding.class)) { + return null; } - - public Resolved(int level, Class type) { - this.level = level; - this.type = type; + Class clazz = element.getAnnotation(RLPEncoding.class).keyOrdering(); + if (clazz == RLPEncoding.None.class) return null; + try { + Constructor con = clazz.getDeclaredConstructor(); + con.setAccessible(true); + return con.newInstance(); + } catch (Exception e) { + throw new RuntimeException("new instance of " + clazz + " failed " + e.getCause()); } } } diff --git a/src/main/java/org/tdf/rlp/Raw.java b/src/main/java/org/tdf/rlp/Raw.java new file mode 100644 index 0000000..1c3e4ce --- /dev/null +++ b/src/main/java/org/tdf/rlp/Raw.java @@ -0,0 +1,31 @@ +package org.tdf.rlp; + +class Raw implements Container { + Class rawType; + + public ContainerType getContainerType() { + return ContainerType.RAW; + } + + @Override + public Class asRawType() { + return rawType; + } + + @Override + public CollectionContainer asCollectionContainer() { + throw new RuntimeException("not a collection container"); + } + + @Override + public MapContainer asMapContainer() { + throw new RuntimeException("not a map container"); + } + + public Raw() { + } + + Raw(Class rawType) { + this.rawType = rawType; + } +} diff --git a/src/test/java/org/tdf/rlp/ByteArrayMap.java b/src/test/java/org/tdf/rlp/ByteArrayMap.java new file mode 100644 index 0000000..c7fb5e2 --- /dev/null +++ b/src/test/java/org/tdf/rlp/ByteArrayMap.java @@ -0,0 +1,189 @@ +package org.tdf.rlp; + +import java.util.*; + + +/** + * wrap byte array wrapper hash map as byte array map + */ +public class ByteArrayMap implements Map { + private final Map delegate; + + public ByteArrayMap() { + this.delegate = new HashMap<>(); + } + + public ByteArrayMap(Map map) { + this(); + putAll(map); + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return delegate.containsKey(new ByteArrayWrapper((byte[]) key)); + } + + @Override + public boolean containsValue(Object value) { + return delegate.containsValue(value); + } + + @Override + public V get(Object key) { + return delegate.get(new ByteArrayWrapper((byte[]) key)); + } + + @Override + public V put(byte[] key, V value) { + return delegate.put(new ByteArrayWrapper(key), value); + } + + @Override + public V remove(Object key) { + return delegate.remove(new ByteArrayWrapper((byte[]) key)); + } + + @Override + public void putAll(Map m) { + for (Entry entry : m.entrySet()) { + delegate.put(new ByteArrayWrapper(entry.getKey()), entry.getValue()); + } + } + + @Override + public void clear() { + delegate.clear(); + } + + @Override + public Set keySet() { + return new ByteArraySet(new SetAdapter<>(delegate)); + } + + @Override + public Collection values() { + return delegate.values(); + } + + @Override + public Set> entrySet() { + return new MapEntrySet(delegate.entrySet()); + } + + @Override + public boolean equals(Object o) { + return delegate.equals(o); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public String toString() { + return delegate.toString(); + } + + private class MapEntrySet implements Set> { + private final Set> delegate; + + private MapEntrySet(Set> delegate) { + this.delegate = delegate; + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean contains(Object o) { + throw new RuntimeException("Not implemented"); + } + + @Override + public Iterator> iterator() { + final Iterator> it = delegate.iterator(); + return new Iterator>() { + + @Override + public boolean hasNext() { + return it.hasNext(); + } + + @Override + public Entry next() { + Entry next = it.next(); + return new AbstractMap.SimpleImmutableEntry(next.getKey().getData(), next.getValue()); + } + + @Override + public void remove() { + it.remove(); + } + }; + } + + @Override + public Object[] toArray() { + throw new RuntimeException("Not implemented"); + } + + @Override + public T[] toArray(T[] a) { + throw new RuntimeException("Not implemented"); + } + + @Override + public boolean add(Entry vEntry) { + throw new RuntimeException("Not implemented"); + } + + @Override + public boolean remove(Object o) { + throw new RuntimeException("Not implemented"); + } + + @Override + public boolean containsAll(Collection c) { + throw new RuntimeException("Not implemented"); + } + + @Override + public boolean addAll(Collection> c) { + throw new RuntimeException("Not implemented"); + } + + @Override + public boolean retainAll(Collection c) { + throw new RuntimeException("Not implemented"); + } + + @Override + public boolean removeAll(Collection c) { + throw new RuntimeException("Not implemented"); + } + + @Override + public void clear() { + throw new RuntimeException("Not implemented"); + + } + } +} diff --git a/src/test/java/org/tdf/rlp/ByteArraySet.java b/src/test/java/org/tdf/rlp/ByteArraySet.java new file mode 100644 index 0000000..128c42a --- /dev/null +++ b/src/test/java/org/tdf/rlp/ByteArraySet.java @@ -0,0 +1,133 @@ +package org.tdf.rlp; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + + +/** + * wrap byte array hash set as set + */ +public class ByteArraySet implements Set { + Set delegate; + + public ByteArraySet() { + this(new HashSet()); + } + + public ByteArraySet(Collection all){ + this(); + addAll(all); + } + + ByteArraySet(Set delegate) { + this.delegate = delegate; + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return delegate.contains(new ByteArrayWrapper((byte[]) o)); + } + + @Override + public Iterator iterator() { + return new Iterator() { + + Iterator it = delegate.iterator(); + @Override + public boolean hasNext() { + return it.hasNext(); + } + + @Override + public byte[] next() { + return it.next().getData(); + } + + @Override + public void remove() { + it.remove(); + } + }; + } + + @Override + public Object[] toArray() { + byte[][] ret = new byte[size()][]; + + ByteArrayWrapper[] arr = delegate.toArray(new ByteArrayWrapper[size()]); + for (int i = 0; i < arr.length; i++) { + ret[i] = arr[i].getData(); + } + return ret; + } + + @Override + public T[] toArray(T[] a) { + return (T[]) toArray(); + } + + @Override + public boolean add(byte[] bytes) { + return delegate.add(new ByteArrayWrapper(bytes)); + } + + @Override + public boolean remove(Object o) { + return delegate.remove(new ByteArrayWrapper((byte[]) o)); + } + + @Override + public boolean containsAll(Collection c) { + throw new RuntimeException("Not implemented"); + } + + @Override + public boolean addAll(Collection c) { + boolean ret = false; + for (byte[] bytes : c) { + ret |= add(bytes); + } + return ret; + } + + @Override + public boolean retainAll(Collection c) { + throw new RuntimeException("Not implemented"); + } + + @Override + public boolean removeAll(Collection c) { + boolean changed = false; + for (Object el : c) { + changed |= remove(el); + } + return changed; + } + + @Override + public void clear() { + delegate.clear(); + } + + @Override + public boolean equals(Object o) { + throw new RuntimeException("Not implemented"); + } + + @Override + public int hashCode() { + throw new RuntimeException("Not implemented"); + } +} diff --git a/src/test/java/org/tdf/rlp/ByteArrayWrapper.java b/src/test/java/org/tdf/rlp/ByteArrayWrapper.java new file mode 100644 index 0000000..3ddcbf2 --- /dev/null +++ b/src/test/java/org/tdf/rlp/ByteArrayWrapper.java @@ -0,0 +1,46 @@ +package org.tdf.rlp; + +import java.io.Serializable; +import java.util.Arrays; + +/** + * wrap byte array as immutable + */ +public class ByteArrayWrapper implements Comparable, Serializable { + + private static final long serialVersionUID = 7120319357455987329L; + private final byte[] data; + private int hashCode = 0; + + public ByteArrayWrapper(byte[] data) { + if (data == null) + throw new NullPointerException("Data must not be null"); + this.data = data; + this.hashCode = Arrays.hashCode(data); + } + + public boolean equals(Object other) { + if (!(other instanceof ByteArrayWrapper)) + return false; + byte[] otherData = ((ByteArrayWrapper) other).getData(); + return FastByteComparisons.compareTo( + data, 0, data.length, + otherData, 0, otherData.length) == 0; + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public int compareTo(ByteArrayWrapper o) { + return FastByteComparisons.compareTo( + data, 0, data.length, + o.getData(), 0, o.getData().length); + } + + public byte[] getData() { + return data; + } +} diff --git a/src/test/java/org/tdf/rlp/FastByteComparisons.java b/src/test/java/org/tdf/rlp/FastByteComparisons.java new file mode 100644 index 0000000..ef03a3a --- /dev/null +++ b/src/test/java/org/tdf/rlp/FastByteComparisons.java @@ -0,0 +1,89 @@ +package org.tdf.rlp; + +@SuppressWarnings("restriction") +public abstract class FastByteComparisons { + + public static boolean equal(byte[] b1, byte[] b2) { + return b1.length == b2.length && compareTo(b1, 0, b1.length, b2, 0, b2.length) == 0; + } + /** + * Lexicographically compare two byte arrays. + * + * @param b1 buffer1 + * @param s1 offset1 + * @param l1 length1 + * @param b2 buffer2 + * @param s2 offset2 + * @param l2 length2 + * @return int + */ + public static int compareTo(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) { + return LexicographicalComparerHolder.BEST_COMPARER.compareTo( + b1, s1, l1, b2, s2, l2); + } + + private interface Comparer { + int compareTo(T buffer1, int offset1, int length1, + T buffer2, int offset2, int length2); + } + + private static Comparer lexicographicalComparerJavaImpl() { + return LexicographicalComparerHolder.PureJavaComparer.INSTANCE; + } + + + /** + * + *

Uses reflection to gracefully fall back to the Java implementation if + * {@code Unsafe} isn't available. + */ + private static class LexicographicalComparerHolder { + static final String UNSAFE_COMPARER_NAME = + LexicographicalComparerHolder.class.getName() + "$UnsafeComparer"; + + static final Comparer BEST_COMPARER = getBestComparer(); + + /** + * Returns the Unsafe-using Comparer, or falls back to the pure-Java + * implementation if unable to do so. + */ + static Comparer getBestComparer() { + try { + Class theClass = Class.forName(UNSAFE_COMPARER_NAME); + + // yes, UnsafeComparer does implement Comparer + @SuppressWarnings("unchecked") + Comparer comparer = + (Comparer) theClass.getEnumConstants()[0]; + return comparer; + } catch (Throwable t) { // ensure we really catch *everything* + return lexicographicalComparerJavaImpl(); + } + } + + private enum PureJavaComparer implements Comparer { + INSTANCE; + + @Override + public int compareTo(byte[] buffer1, int offset1, int length1, + byte[] buffer2, int offset2, int length2) { + // Short circuit equal case + if (buffer1 == buffer2 && offset1 == offset2 && length1 == length2) { + return 0; + } + + int end1 = offset1 + length1; + int end2 = offset2 + length2; + for (int i = offset1, j = offset2; i < end1 && j < end2; i++, j++) { + int a = (buffer1[i] & 0xff); + int b = (buffer2[j] & 0xff); + if (a != b) { + return a - b; + } + } + + return length1 - length2; + } + } + } +} diff --git a/src/test/java/org/tdf/rlp/HashUtil.java b/src/test/java/org/tdf/rlp/HashUtil.java index 2c43158..9f6f3f2 100644 --- a/src/test/java/org/tdf/rlp/HashUtil.java +++ b/src/test/java/org/tdf/rlp/HashUtil.java @@ -5,6 +5,8 @@ import java.security.Provider; import java.security.Security; +import static org.tdf.rlp.RLPCodec.encodeBytes; + public class HashUtil { @@ -25,7 +27,7 @@ public class HashUtil { HASH_512_ALGORITHM_NAME = "ETH-KECCAK-512"; EMPTY_DATA_HASH = sha3(EMPTY_BYTE_ARRAY); EMPTY_LIST_HASH = sha3(RLPList.createEmpty().getEncoded()); - EMPTY_TRIE_HASH = sha3(RLPItem.encodeElement(EMPTY_BYTE_ARRAY)); + EMPTY_TRIE_HASH = sha3(encodeBytes(EMPTY_BYTE_ARRAY)); } /** diff --git a/src/test/java/org/tdf/rlp/Main.java b/src/test/java/org/tdf/rlp/Main.java index 6f5abaf..68163c2 100644 --- a/src/test/java/org/tdf/rlp/Main.java +++ b/src/test/java/org/tdf/rlp/Main.java @@ -6,11 +6,10 @@ public class Main{ public static class MapEncoderDecoder implements RLPEncoder>, RLPDecoder> { @Override - public Map decode(RLPElement element) { - RLPList list = element.getAsList(); + public Map decode(RLPElement list) { Map map = new HashMap<>(list.size() / 2); for (int i = 0; i < list.size(); i += 2) { - map.put(list.get(i).getAsItem().getString(), list.get(i+1).getAsItem().getString()); + map.put(list.get(i).asString(), list.get(i+1).asString()); } return map; } @@ -44,8 +43,8 @@ public static void main(String[] args){ Map m = new HashMap<>(); m.put("a", "1"); m.put("b", "2"); - byte[] encoded = RLPElement.encode(new MapWrapper(m)).getEncoded(); - MapWrapper decoded = RLPDeserializer.deserialize(encoded, MapWrapper.class); + byte[] encoded = RLPCodec.encode(new MapWrapper(m)); + MapWrapper decoded = RLPCodec.decode(encoded, MapWrapper.class); assertTrue(decoded.map.get("a").equals("1")); } diff --git a/src/test/java/org/tdf/rlp/Main2.java b/src/test/java/org/tdf/rlp/Main2.java new file mode 100644 index 0000000..0d3fa44 --- /dev/null +++ b/src/test/java/org/tdf/rlp/Main2.java @@ -0,0 +1,21 @@ +package org.tdf.rlp; + +public class Main2 { + public static void main(String[] args) { + int n = 1000000; + RLPList list = RLPList.createEmpty(n); + byte[] bytes = new byte[]{1}; + for (int i = 0; i < n; i++) { + list.add(RLPItem.fromBytes(bytes)); + } + long start = System.currentTimeMillis(); + byte[] encoded = list.getEncoded(); + long end = System.currentTimeMillis(); + System.out.println("encode " + (n * 32) + " bytes in " + (end - start) + " ms"); + + start = System.currentTimeMillis(); + RLPElement decoded = RLPElement.fromEncoded(encoded); + end = System.currentTimeMillis(); + System.out.println("decode " + (n * 32) + " bytes in " + (end - start) + " ms"); + } +} diff --git a/src/test/java/org/tdf/rlp/Node.java b/src/test/java/org/tdf/rlp/Node.java index 8a015b6..fb76ff9 100644 --- a/src/test/java/org/tdf/rlp/Node.java +++ b/src/test/java/org/tdf/rlp/Node.java @@ -47,25 +47,15 @@ public static void main(String[] args){ root.children.get(1).addChildren(Arrays.asList(new Node("6"), new Node("7"))); // encode to byte array - byte[] encoded = RLPElement.encode(root).getEncoded(); - // encode to rlp element - RLPElement el = RLPElement.encode(root); + byte[] encoded = RLPCodec.encode(root); + // read as rlp tree + RLPElement el = RLPElement.readRLPTree(root); // decode from byte array - Node root2 = RLPDeserializer.deserialize(encoded, Node.class); - assertTrue(root2.children.get(0).children.get(0).name.equals("4")); - assertTrue(root2.children.get(0).children.get(1).name.equals("5")); - assertTrue(root2.children.get(1).children.get(0).name.equals("6")); - assertTrue(root2.children.get(1).children.get(1).name.equals("7")); - - Nested nested = new Nested(); - nested.nested = new ArrayList<>(); - nested.nested.add(new ArrayList<>()); - nested.nested.get(0).add(new ArrayList<>()); - nested.nested.get(0).get(0).addAll(Arrays.asList("aaa", "bbb")); - encoded = RLPElement.encode(nested).getEncoded(); - nested = RLPDeserializer.deserialize(encoded, Nested.class); - assertTrue(nested.nested.get(0).get(0).get(0).equals("aaa")); - assertTrue(nested.nested.get(0).get(0).get(1).equals("bbb")); + Node root2 = RLPCodec.decode(encoded, Node.class); + el = RLPElement.fromEncoded(encoded); + // decode from rlp element + root2 = el.as(Node.class); + root2 = RLPCodec.decode(el, Node.class); } public static void assertTrue(boolean b){ diff --git a/src/test/java/org/tdf/rlp/RLPTest.java b/src/test/java/org/tdf/rlp/RLPTest.java index 6f38681..df6c004 100644 --- a/src/test/java/org/tdf/rlp/RLPTest.java +++ b/src/test/java/org/tdf/rlp/RLPTest.java @@ -1,21 +1,22 @@ package org.tdf.rlp; import org.apache.commons.codec.binary.Hex; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.math.BigInteger; import java.nio.charset.StandardCharsets; -import java.security.SecureRandom; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import static org.tdf.rlp.RLPItem.*; +import static org.tdf.rlp.Container.resolveContainerofGeneric; +import static org.tdf.rlp.RLPCodec.*; +import static org.tdf.rlp.RLPItem.NULL; +import static org.tdf.rlp.RLPItem.ONE; @RunWith(JUnit4.class) public class RLPTest { @@ -59,7 +60,7 @@ public static class RLPSerializer { public static RLPSerializer SERIALIZER = new RLPSerializer(); public byte[] serialize(Object o) { - return RLPElement.encode(o).getEncoded(); + return RLPElement.readRLPTree(o).getEncoded(); } } @@ -80,39 +81,39 @@ public static byte[] decode(String s) throws Exception { @Test public void test0() { byte[] data = RLPSerializer.SERIALIZER.serialize(-1L); - assert RLPDeserializer.deserialize(data, Long.class) == -1L; + assert RLPCodec.decode(data, Long.class) == -1L; data = RLPSerializer.SERIALIZER.serialize(0L); - assert RLPDeserializer.deserialize(data, Long.class) == 0; + assert RLPCodec.decode(data, Long.class) == 0; data = RLPSerializer.SERIALIZER.serialize(Long.MAX_VALUE); - assert RLPDeserializer.deserialize(data, Long.class) == Long.MAX_VALUE; + assert RLPCodec.decode(data, Long.class) == Long.MAX_VALUE; data = RLPSerializer.SERIALIZER.serialize(Long.MIN_VALUE); - assert RLPDeserializer.deserialize(data, Long.class) == Long.MIN_VALUE; + assert RLPCodec.decode(data, Long.class) == Long.MIN_VALUE; data = RLPSerializer.SERIALIZER.serialize(Integer.valueOf(0)); - assert RLPDeserializer.deserialize(data, Integer.class) == 0; + assert RLPCodec.decode(data, Integer.class) == 0; data = RLPSerializer.SERIALIZER.serialize(Integer.MIN_VALUE); - assert RLPDeserializer.deserialize(data, Integer.class) == Integer.MIN_VALUE; + assert RLPCodec.decode(data, Integer.class) == Integer.MIN_VALUE; data = RLPSerializer.SERIALIZER.serialize(Integer.MAX_VALUE); - assert RLPDeserializer.deserialize(data, Integer.class) == Integer.MAX_VALUE; + assert RLPCodec.decode(data, Integer.class) == Integer.MAX_VALUE; data = RLPSerializer.SERIALIZER.serialize(Integer.valueOf(-1)); - assert RLPDeserializer.deserialize(data, Integer.class) == -1; + assert RLPCodec.decode(data, Integer.class) == -1; data = RLPSerializer.SERIALIZER.serialize(Short.valueOf((short) 0)); - assert RLPDeserializer.deserialize(data, Short.class) == 0; + assert RLPCodec.decode(data, Short.class) == 0; data = RLPSerializer.SERIALIZER.serialize(Short.MIN_VALUE); - assert RLPDeserializer.deserialize(data, Short.class) == Short.MIN_VALUE; + assert RLPCodec.decode(data, Short.class) == Short.MIN_VALUE; data = RLPSerializer.SERIALIZER.serialize(Short.MAX_VALUE); - assert RLPDeserializer.deserialize(data, Short.class) == Short.MAX_VALUE; + assert RLPCodec.decode(data, Short.class) == Short.MAX_VALUE; } @Test public void test1() { byte[] data = RLPSerializer.SERIALIZER.serialize(new TestSerializer(Arrays.asList("1", "2", "3"))); - TestSerializer serializer = RLPDeserializer.deserialize(data, TestSerializer.class); + TestSerializer serializer = RLPCodec.decode(data, TestSerializer.class); assert serializer.strings.get(0).equals("1"); assert serializer.strings.get(1).equals("2"); assert serializer.strings.get(2).equals("3"); - RLPList list = RLPElement.fromEncoded(data).getAsList(); + RLPList list = RLPElement.fromEncoded(data).asRLPList(); list.setEncoded(null); assertArrayEquals(data, list.getEncoded()); } @@ -125,14 +126,14 @@ public void testEncodeList() { byte[] encoderesult = RLPSerializer.SERIALIZER.serialize(test); assertEquals(expected, HexBytes.encode(encoderesult)); - String[] decodedTest = RLPDeserializer.deserialize(encoderesult, String[].class); + String[] decodedTest = RLPCodec.decode(encoderesult, String[].class); assertArrayEquals(decodedTest, test); test = new String[]{"dog", "god", "cat"}; expected = "cc83646f6783676f6483636174"; encoderesult = RLPSerializer.SERIALIZER.serialize(test); assertEquals(expected, HexBytes.encode(encoderesult)); - assertArrayEquals(RLPElement.fromEncoded(encoderesult).getAsList().stream().map(x -> x.getAsItem().getString()).toArray(), test); + assertArrayEquals(RLPElement.fromEncoded(encoderesult).asRLPList().stream().map(x -> x.asRLPItem().asString()).toArray(), test); } @Test @@ -318,7 +319,7 @@ public void test8() throws Exception { String expected = "b840" + byteArr; - assertEquals(expected, HexBytes.encode(encodeElement(byteArray))); + assertEquals(expected, HexBytes.encode(encodeBytes(byteArray))); assertEquals(expected, HexBytes.encode(RLPItem.fromBytes(byteArray).getEncoded())); assertEquals(expected, HexBytes.encode( RLPElement.fromEncoded(HexBytes.decode(expected)).getEncoded() @@ -335,26 +336,26 @@ public void test9() { @Test /** encode null value */ - public void testEncodeElementNull() { + public void testencodeBytesNull() { - byte[] actuals = encodeElement(null); + byte[] actuals = encodeBytes(null); assertArrayEquals(new byte[]{(byte) 0x80}, actuals); } @Test /** encode single byte 0x00 */ - public void testEncodeElementZero() { + public void testencodeBytesZero() { - byte[] actuals = encodeElement(new byte[]{0x00}); + byte[] actuals = encodeBytes(new byte[]{0x00}); assertArrayEquals(new byte[]{0x00}, actuals); } @Test /** encode single byte 0x01 */ - public void testEncodeElementOne() { + public void testencodeBytesOne() { - byte[] actuals = encodeElement(new byte[]{0x01}); + byte[] actuals = encodeBytes(new byte[]{0x01}); assertArrayEquals(new byte[]{(byte) 0x01}, actuals); } @@ -369,7 +370,7 @@ public void test10() { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - prevHash = encodeElement(prevHash); + prevHash = encodeBytes(prevHash); /* 2 */ byte[] uncleList = HashUtil.sha3(RLPList.createEmpty().getEncoded()); @@ -379,9 +380,9 @@ public void test10() { {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - coinbase = encodeElement(coinbase); + coinbase = encodeBytes(coinbase); - byte[] header = RLPList.encodeList( + byte[] header = encodeElements( Arrays.asList(prevHash, uncleList, coinbase)); assertEquals("f856a000000000000000000000000000000000000000000000000000000000000000001dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000", @@ -405,9 +406,9 @@ public void testEncodeShortString() throws Exception { String expected = "83646f67"; byte[] encoderesult = encodeString(test); assertEquals(expected, HexBytes.encode(encoderesult)); - assert RLPElement.fromEncoded(HexBytes.decode(expected)).getAsItem().getString().equals(test); + assert RLPElement.fromEncoded(HexBytes.decode(expected)).asRLPItem().asString().equals(test); - byte[] decodeResult = RLPElement.fromEncoded(encoderesult).getAsItem().get(); + byte[] decodeResult = RLPElement.fromEncoded(encoderesult).asRLPItem().asBytes(); assertEquals(test, new String(decodeResult, StandardCharsets.US_ASCII)); } @@ -429,18 +430,18 @@ public void performanceDecode() throws Exception { long start1 = System.currentTimeMillis(); for (int i = 0; i < ITERATIONS; i++) { - list = RLPElement.fromEncoded(payload).getAsList(); + list = RLPElement.fromEncoded(payload, false).asRLPList(); } long end1 = System.currentTimeMillis(); long start2 = System.currentTimeMillis(); for (int i = 0; i < ITERATIONS; i++) { - list = RLPElement.fromEncoded(payload).getAsList(); + list = RLPElement.fromEncoded(payload, true).asRLPList(); } long end2 = System.currentTimeMillis(); - System.out.println("Result RLPElement.fromEncoded\t: " + (end1 - start1) + "ms and\t " + " bytes for each resulting object list"); - System.out.println("Result RLPElement.fromEncoded\t: " + (end2 - start2) + "ms and\t " + " bytes for each resulting object list"); + System.out.println("Result RLPElement.fromEncoded\t: " + (end1 - start1) + "ms and\t " + (payload.length * ITERATIONS) + " bytes for each resulting object list"); + System.out.println("Result RLPElement.fromEncoded lazy\t: " + (end2 - start2) + "ms and\t " + (payload.length * ITERATIONS) + " bytes for each resulting object list"); } else { System.out.println("Performance test for RLP.decode() disabled"); } @@ -454,13 +455,13 @@ public void encodeEdgeShortList() throws Exception { byte[] rlpKeysList = HexBytes.decode("c0"); byte[] rlpValuesList = HexBytes.decode("c0"); byte[] rlpCode = HexBytes.decode("b4600160003556601359506301000000600035040f6018590060005660805460016080530160005760003560805760203560003557"); - byte[] output = RLPList.encodeList(Arrays.asList(rlpKeysList, rlpValuesList, rlpCode)); + byte[] output = encodeElements(Arrays.asList(rlpKeysList, rlpValuesList, rlpCode)); assertEquals(expectedOutput, HexBytes.encode(output)); assertArrayEquals(RLPElement.fromEncoded(HexBytes.decode(expectedOutput)).getEncoded(), HexBytes.decode(expectedOutput)); assertArrayEquals( RLPElement.fromEncoded(HexBytes.decode(expectedOutput)) - .getAsList().stream().map(x -> x.getEncoded()).toArray(), + .asRLPList().stream().map(x -> x.getEncoded()).toArray(), new byte[][]{rlpKeysList, rlpValuesList, rlpCode} ); } @@ -470,7 +471,7 @@ public void encodeBigIntegerEdge_1() { BigInteger integer = new BigInteger("80", 10); byte[] encodedData = encodeBigInteger(integer); - assert RLPElement.fromEncoded(encodedData).getAsItem().getInt() == 80; + assert RLPElement.fromEncoded(encodedData).asRLPItem().asInt() == 80; } @Test @@ -478,7 +479,7 @@ public void testEncodeInt_7f() throws Exception { String result = HexBytes.encode(encodeInt(0x7f)); String expected = "7f"; assertEquals(expected, result); - assert RLPElement.fromEncoded(HexBytes.decode(expected)).getAsItem().getInt() == 0x7f; + assert RLPElement.fromEncoded(HexBytes.decode(expected)).asRLPItem().asInt() == 0x7f; } @Test @@ -486,7 +487,7 @@ public void testEncodeInt_80() throws Exception { String result = HexBytes.encode(encodeInt(0x80)); String expected = "8180"; assertEquals(expected, result); - assert RLPElement.fromEncoded(HexBytes.decode(expected)).getAsItem().getInt() == 0x80; + assert RLPElement.fromEncoded(HexBytes.decode(expected)).asRLPItem().asInt() == 0x80; } @@ -495,7 +496,7 @@ public void testEncode_ED() throws Exception { String result = HexBytes.encode(encodeInt(0xED)); String expected = "81ed"; assertEquals(expected, result); - assert RLPElement.fromEncoded(HexBytes.decode(result)).getAsItem().getInt() == 0xED; + assert RLPElement.fromEncoded(HexBytes.decode(result)).asRLPItem().asInt() == 0xED; } // encode a binary tree @@ -508,8 +509,8 @@ public void testTreeLike() { root.children.get(1).addChildren(Arrays.asList(new Node("6"), new Node("7"))); byte[] encoded = RLPSerializer.SERIALIZER.serialize(root); - RLPElement el = RLPElement.encode(root); - Node root2 = RLPDeserializer.deserialize(encoded, Node.class); + RLPElement el = RLPElement.readRLPTree(root); + Node root2 = RLPCodec.decode(encoded, Node.class); assert root2.children.get(0).children.get(0).name.equals("4"); assert root2.children.get(0).children.get(1).name.equals("5"); assert root2.children.get(1).children.get(0).name.equals("6"); @@ -522,10 +523,10 @@ public static class MapEncoderDecoder implements RLPEncoder> @Override public Map decode(RLPElement element) { - RLPList list = element.getAsList(); + RLPList list = element.asRLPList(); Map map = new HashMap<>(list.size() / 2); for (int i = 0; i < list.size(); i += 2) { - map.put(list.get(i).getAsItem().getString(), list.get(i + 1).getAsItem().getString()); + map.put(list.get(i).asRLPItem().asString(), list.get(i + 1).asRLPItem().asString()); } return map; } @@ -561,7 +562,7 @@ public void testMap() { m.put("a", "1"); m.put("b", "2"); byte[] encoded = RLPSerializer.SERIALIZER.serialize(new MapWrapper(m)); - MapWrapper decoded = RLPDeserializer.deserialize(encoded, MapWrapper.class); + MapWrapper decoded = RLPCodec.decode(encoded, MapWrapper.class); assert decoded.map.get("a").equals("1"); byte[] encoded2 = MapEncoderDecoder.CODEC.encode(m).getEncoded(); Map m2 = MapEncoderDecoder.CODEC.decode(RLPElement.fromEncoded(encoded2)); @@ -588,7 +589,7 @@ public void rlpEncodedLength() throws Exception { byte[] rlpBomb = HexBytes.decode(rlpBombStr); boolean isProtected = false; try { - RLPList list = RLPElement.fromEncoded(rlpBomb).getAsList(); + RLPList list = RLPElement.fromEncoded(rlpBomb).asRLPList(); } catch (Throwable cause) { // decode2 is protected! while (cause != null) { @@ -601,7 +602,7 @@ public void rlpEncodedLength() throws Exception { isProtected = false; try { - RLPList list = RLPElement.fromEncoded(rlpBomb).getAsList(); + RLPList list = RLPElement.fromEncoded(rlpBomb).asRLPList(); } catch (Throwable cause) { // decode is protected now too! // decode2 is protected! @@ -1150,7 +1151,7 @@ public void testNestedListsBomb() { @Test(expected = Exception.class) public void testEncodeFail() { - RLPElement.encode(new Foo()); + RLPElement.readRLPTree(new Foo()); } static class Foo { @@ -1161,7 +1162,7 @@ static class Foo { @Test public void testListCache() { byte[] encoded = RLPList.of(RLPItem.fromInt(1), RLPItem.fromInt(2)).getEncoded(); - RLPList list = RLPElement.fromEncoded(encoded).getAsList(); + RLPList list = RLPElement.fromEncoded(encoded).asRLPList(); byte[] encoded2 = list.getEncoded(); list.setEncoded(null); byte[] encoded3 = list.getEncoded(); @@ -1172,7 +1173,7 @@ public void testListCache() { @Test public void testItemCache() { byte[] encoded = RLPItem.fromString("hello world").getEncoded(); - RLPItem item = RLPElement.fromEncoded(encoded).getAsItem(); + RLPItem item = RLPElement.fromEncoded(encoded).asRLPItem(); byte[] encoded2 = item.getEncoded(); item.setEncoded(null); byte[] encoded3 = item.getEncoded(); @@ -1180,32 +1181,30 @@ public void testItemCache() { assertArrayEquals(encoded2, encoded3); } - @Ignore @Test // ethereumJ: encode 320000 bytes in 127 ms // ethereumJ: decode 320000 bytes in 159 ms // our: encode 320000 bytes in 109 ms // our: decode 320000 bytes in 4 ms public void testBomb() { - int n = 10000; - SecureRandom sr = new SecureRandom(); + int n = 1000000; RLPList list = RLPList.createEmpty(n); + byte[] bytes = new byte[]{1}; for (int i = 0; i < n; i++) { - byte[] bytes = new byte[10000]; list.add(RLPItem.fromBytes(bytes)); } long start = System.currentTimeMillis(); byte[] encoded = list.getEncoded(); long end = System.currentTimeMillis(); - System.out.println("encode " + (n * 32) + " bytes in " + (end - start) + " ms"); + System.out.println("encode " + (n) + " bytes in " + (end - start) + " ms"); start = System.currentTimeMillis(); - RLPElement decoded = RLPElement.fromEncoded(encoded); + RLPElement decoded = RLPElement.fromEncoded(encoded, false); end = System.currentTimeMillis(); - System.out.println("decode " + (n * 32) + " bytes in " + (end - start) + " ms"); + System.out.println("decode " + (n) + " bytes in " + (end - start) + " ms"); } - private static class Nested{ + private static class Nested { @RLP private List>> nested; @@ -1215,20 +1214,7 @@ public Nested() { } } - @Test - public void testNested() throws Exception{ - RLPUtils.Resolved resolved = RLPUtils.resolveFieldType(Nested.class.getDeclaredField("nested")); - assert resolved.level == 3; - assert resolved.type == String.class; - resolved = RLPUtils.resolveFieldType(Nested.class.getDeclaredField("hello")); - assert resolved.level == 0; - assert resolved.type == String.class; - resolved = RLPUtils.resolveFieldType(NoNested.class.getDeclaredField("nested")); - assert resolved.level == 1; - assert resolved.type == String.class; - } - - private static class NoNested{ + private static class NoNested { @RLP private List nested; @@ -1237,36 +1223,36 @@ public NoNested() { } @Test - public void testDecode2(){ + public void testDecode2() { NoNested nested = new NoNested(); nested.nested = new ArrayList<>(); nested.nested.addAll(Arrays.asList("aaa", "bbb")); byte[] encoded = RLPSerializer.SERIALIZER.serialize(nested); - NoNested noNested = RLPDeserializer.deserialize(encoded, NoNested.class); + NoNested noNested = RLPCodec.decode(encoded, NoNested.class); assert noNested.nested.get(0).equals("aaa"); assert noNested.nested.get(1).equals("bbb"); } @Test - public void testDecode3(){ + public void testDecode3() { Nested nested = new Nested(); nested.nested = new ArrayList<>(); nested.nested.add(new ArrayList<>()); nested.nested.get(0).add(new ArrayList<>()); nested.nested.get(0).get(0).addAll(Arrays.asList("aaa", "bbb")); - byte[] encoded = RLPSerializer.SERIALIZER.serialize(nested); - nested = RLPDeserializer.deserialize(encoded, Nested.class); + byte[] encoded = RLPCodec.encode(nested); + nested = RLPCodec.decode(encoded, Nested.class); assert nested.nested.get(0).get(0).get(0).equals("aaa"); assert nested.nested.get(0).get(0).get(1).equals("bbb"); } @Test - public void testNestedString(){ + public void testNestedString() { RLPList li1 = RLPList.of(RLPItem.fromString("aa"), RLPItem.fromString("bbb")); RLPList li2 = RLPList.of(RLPItem.fromString("aa"), RLPItem.fromString("bbb")); byte[] encoded = RLPList.of(li1, li2).getEncoded(); - String[][] strs = RLPDeserializer.deserialize(encoded, String[][].class); + String[][] strs = RLPCodec.decode(encoded, String[][].class); assert strs[0][0].equals("aa"); assert strs[0][1].equals("bbb"); assert strs[1][0].equals("aa"); @@ -1274,27 +1260,234 @@ public void testNestedString(){ } @Test - public void testBoolean(){ - assert !RLPDeserializer.deserialize(NULL, Boolean.class); - assert RLPDeserializer.deserialize(RLPItem.fromBoolean(true), Boolean.class); + public void testBoolean() { + assert !RLPCodec.decode(NULL, Boolean.class); + assert RLPCodec.decode(RLPItem.fromBoolean(true), Boolean.class); List elements = Stream.of(1, 1, 1).map(RLPItem::fromInt).collect(Collectors.toList()); RLPList list = RLPList.fromElements(elements); - assert RLPDeserializer.deserializeList( - list, Boolean.class - ).stream().allMatch(x -> x); + for (boolean b : RLPCodec.decode( + list, boolean[].class + )) + assert b; } @Test(expected = RuntimeException.class) - public void testBooleanFailed(){ - RLPDeserializer.deserialize(RLPItem.fromInt(2), Boolean.class); + public void testBooleanFailed() { + RLPCodec.decode(RLPItem.fromInt(2), Boolean.class); } @Test(expected = RuntimeException.class) - public void testBooleanFailed2(){ + public void testBooleanFailed2() { List elements = Stream.of(1, 2, 3).map(RLPItem::fromInt).collect(Collectors.toList()); RLPList list = RLPList.fromElements(elements); - RLPDeserializer.deserializeList( - list, Boolean.class + RLPCodec.decode( + list, boolean[].class ); } + + @Test + public void test() { + assert !NULL.as(boolean.class); + assert ONE.as(boolean.class); + assert NULL.asBigInteger().compareTo(BigInteger.ZERO) == 0; + assert RLPItem.fromBoolean(true) == ONE; + assert RLPItem.fromLong(1) == ONE; + assert RLPItem.fromInt(0) == NULL; + assert !NULL.isRLPList(); + assert NULL.isRLPItem(); + assert (NULL.getEncoded()[0] & 0xff) == RLPConstants.OFFSET_SHORT_ITEM; + assert RLPItem.fromInt(2).asBigInteger().compareTo(BigInteger.valueOf(2)) == 0; + } + + @Test(expected = RuntimeException.class) + public void test2() { + NULL.asRLPList(); + } + + @Test + public void testLazyParse() throws Exception { + String expected = "c88363617483646f67"; + + RLPElement el = RLPElement.fromEncoded(Hex.decodeHex(expected)).asRLPList(); + assert el.asRLPList().stream().allMatch(x -> x instanceof LazyElement); + el.get(0).asString(); + } + + @Test(expected = RuntimeException.class) + public void testByteOverFlow() { + RLPItem.fromLong(0xffL + 1).asByte(); + } + + @Test(expected = RuntimeException.class) + public void testShortOverFlow() { + RLPItem.fromLong(0xffffL + 1).asByte(); + } + + @Test(expected = RuntimeException.class) + public void testIntOverFlow() { + RLPItem.fromLong(0xffffffffL + 1).asByte(); + } + + @Test(expected = RuntimeException.class) + public void testItemAsList1() { + NULL.get(0); + } + + @Test(expected = RuntimeException.class) + public void testItemAsList2() { + NULL.add(NULL); + } + + @Test(expected = RuntimeException.class) + public void testItemAsList3() { + NULL.set(0, NULL); + } + + @Test(expected = RuntimeException.class) + public void testItemAsList4() { + NULL.size(); + } + + @Test + public void testAsByteSuccess() { + assert RLPItem.fromLong(0xffL).asByte() == (byte) 0xff; + } + + @Test + public void testInstanceOf() { + ArrayList li = new ArrayList<>(); + assert li instanceof Collection; + } + + private static class SetWrapper0 { + @RLP + private Set set = new HashSet<>(); + } + + private static class StringComparator implements Comparator { + @Override + public int compare(String o1, String o2) { + return o1.length() - o2.length(); + } + } + + private static class SetWrapper1 { + @RLP + @RLPEncoding(contentOrdering = StringComparator.class) + Set set = new HashSet<>(); + } + + + @Test + public void testEncodeSetSuccess() { + SetWrapper1 w1 = new SetWrapper1(); + List strings = Arrays.asList("1", "22", "333", "4444", "55555"); + w1.set.addAll(strings); + int i = 0; + boolean hasSorted = true; + for (String s : w1.set) { + if (!s.equals(strings.get(i))) { + hasSorted = false; + break; + } + i++; + } + assert !hasSorted; + RLPElement el = RLPElement.readRLPTree(w1); + for (int j = 0; j < strings.size(); j++) { + assert el.get(0).get(j).asString().equals(strings.get(j)); + } + } + + public static class Con { + public List>>> sss; + public Optional ccc; + public String vvv; + public List li; + } + + @Test + public void testContainer() throws Exception { + Container con = resolveContainerofGeneric(Con.class.getField("sss").getGenericType()); + Container con2 = resolveContainerofGeneric(Con.class.getField("ccc").getGenericType()); + Container con3 = resolveContainerofGeneric(Con.class.getField("vvv").getGenericType()); + Container con4 = resolveContainerofGeneric(Con.class.getField("li").getGenericType()); + } + + public static class MapWrapper2 { + @RLP + @RLPDecoding(as = TreeMap.class) + @RLPEncoding(keyOrdering = StringComparator.class) + public Map> map = new HashMap<>(); + } + + @Test + public void testMapWrapper2() { + MapWrapper2 wrapper2 = new MapWrapper2(); + wrapper2.map.put("1", new HashMap<>()); + wrapper2.map.put("22", new HashMap<>()); + wrapper2.map.put("sss", new HashMap<>()); + wrapper2.map.get("sss").put("aaa", "bbb"); + boolean hasSorted = true; + int i = 1; + for (String k : wrapper2.map.keySet()) { + if (k.length() != i) { + hasSorted = false; + break; + } + i++; + } + assert !hasSorted; + byte[] encoded = RLPCodec.encode(wrapper2); + RLPElement el = RLPElement.readRLPTree(wrapper2); + for (int j = 0; j < 3; j++) { + assert el.get(0).get(j * 2).asString().length() == j + 1; + } + MapWrapper2 decoded = RLPCodec.decode(encoded, MapWrapper2.class); + assert decoded.map instanceof TreeMap; + assert decoded.map.get("sss").get("aaa").equals("bbb"); + } + + private static class ByteArraySetWrapper { + @RLP + @RLPDecoding(as = ByteArraySet.class) + @RLPEncoding(contentOrdering = BytesComparator.class) + private Set bytesSet; + } + + private static class BytesComparator implements Comparator { + @Override + public int compare(byte[] o1, byte[] o2) { + return new BigInteger(1, o1).compareTo(new BigInteger(1, o2)); + } + } + + @Test + public void testByteArraySet() { + ByteArraySetWrapper wrapper = + RLPList.of(RLPList.of(RLPItem.fromLong(1), RLPItem.fromLong(2))).as(ByteArraySetWrapper.class); + assert wrapper.bytesSet instanceof ByteArraySet; + + wrapper = new ByteArraySetWrapper(); + wrapper.bytesSet = new HashSet<>(); + wrapper.bytesSet.add(new byte[]{1}); + wrapper.bytesSet.add(new byte[]{2}); + wrapper.bytesSet.add(new byte[]{3}); + wrapper.bytesSet.add(new byte[]{4}); + wrapper.bytesSet.add(new byte[]{5}); + boolean sorted = true; + int i = 0; + for (byte[] b : wrapper.bytesSet) { + if (new BigInteger(1, b).compareTo(BigInteger.valueOf(i + 1)) != 0) { + sorted = false; + break; + } + i++; + } + assert !sorted; + RLPElement el = RLPElement.readRLPTree(wrapper).get(0); + for (int j = 0; j < el.size(); j++) { + assert new BigInteger(1, el.get(j).asBytes()).compareTo(BigInteger.valueOf(j + 1)) == 0; + } + } } diff --git a/src/test/java/org/tdf/rlp/SetAdapter.java b/src/test/java/org/tdf/rlp/SetAdapter.java new file mode 100644 index 0000000..c95749f --- /dev/null +++ b/src/test/java/org/tdf/rlp/SetAdapter.java @@ -0,0 +1,92 @@ +package org.tdf.rlp; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * Map wrapper + */ +public class SetAdapter implements Set { + private static final Object DummyValue = new Object(); + Map delegate; + + public SetAdapter(Map delegate) { + this.delegate = (Map) delegate; + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return delegate.containsKey(o); + } + + @Override + public Iterator iterator() { + return delegate.keySet().iterator(); + } + + @Override + public Object[] toArray() { + return delegate.keySet().toArray(); + } + + @Override + public T[] toArray(T[] a) { + return delegate.keySet().toArray(a); + } + + @Override + public boolean add(E e) { + return delegate.put(e, DummyValue) == null; + } + + @Override + public boolean remove(Object o) { + return delegate.remove(o) != null; + } + + @Override + public boolean containsAll(Collection c) { + return delegate.keySet().containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + boolean ret = false; + for (E e : c) { + ret |= add(e); + } + return ret; + } + + @Override + public boolean retainAll(Collection c) { + throw new RuntimeException("Not implemented"); // TODO add later if required + } + + @Override + public boolean removeAll(Collection c) { + boolean ret = false; + for (Object e : c) { + ret |= remove(e); + } + return ret; + } + + @Override + public void clear() { + delegate.clear(); + } +} +