diff --git a/api/build.gradle b/api/build.gradle index 5724e2c2..8fe92f04 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -1,18 +1,19 @@ dependencies { + implementation project(':common') + implementation project(':pallet') implementation project(':rpc') implementation project(':rpc:rpc-core') implementation project(':rpc:rpc-sections') - implementation project(':transport') - implementation project(':pallet') - implementation project(':scale') - implementation project(':types') implementation project(':rpc:rpc-types') + implementation project(':scale') implementation project(':storage') + implementation project(':transport') + implementation project(':types') testImplementation project(':tests') - testImplementation 'org.testcontainers:testcontainers:1.16.3' - testImplementation 'org.testcontainers:junit-jupiter:1.16.3' + testImplementation 'org.testcontainers:testcontainers:1.17.1' + testImplementation 'org.testcontainers:junit-jupiter:1.17.1' testAnnotationProcessor project(':pallet:pallet-codegen') } \ No newline at end of file diff --git a/build.gradle b/build.gradle index f7b84986..10ee0ade 100644 --- a/build.gradle +++ b/build.gradle @@ -26,8 +26,8 @@ subprojects { implementation 'com.google.guava:guava:30.1.1-jre' implementation 'org.slf4j:slf4j-api:1.7.32' - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2' - testImplementation 'org.junit.jupiter:junit-jupiter-params:5.7.2' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.2' testImplementation 'org.assertj:assertj-core:3.22.0' testImplementation 'org.mockito:mockito-core:3.12.4' testImplementation 'org.mockito:mockito-inline:3.12.4' diff --git a/common/build.gradle b/common/build.gradle index d989c557..edcf987c 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -1,4 +1,6 @@ dependencies { implementation 'org.reflections:reflections:0.10.2' implementation 'com.squareup:javapoet:1.13.0' + + testImplementation project(':tests') } \ No newline at end of file diff --git a/common/src/main/java/com/strategyobject/substrateclient/common/CommonType.java b/common/src/main/java/com/strategyobject/substrateclient/common/CommonType.java new file mode 100644 index 00000000..4d5aa968 --- /dev/null +++ b/common/src/main/java/com/strategyobject/substrateclient/common/CommonType.java @@ -0,0 +1,6 @@ +package com.strategyobject.substrateclient.common; + +public interface CommonType { + class Array implements CommonType { + } +} diff --git a/common/src/main/java/com/strategyobject/substrateclient/common/codegen/Constants.java b/common/src/main/java/com/strategyobject/substrateclient/common/codegen/Constants.java new file mode 100644 index 00000000..0b6e0068 --- /dev/null +++ b/common/src/main/java/com/strategyobject/substrateclient/common/codegen/Constants.java @@ -0,0 +1,10 @@ +package com.strategyobject.substrateclient.common.codegen; + +import com.strategyobject.substrateclient.common.CommonType; + +public class Constants { + public static final Class ARRAY_TYPE = CommonType.Array.class; + + private Constants() { + } +} diff --git a/common/src/main/java/com/strategyobject/substrateclient/common/codegen/ProcessorContext.java b/common/src/main/java/com/strategyobject/substrateclient/common/codegen/ProcessorContext.java index 64710fb1..301b7fa5 100644 --- a/common/src/main/java/com/strategyobject/substrateclient/common/codegen/ProcessorContext.java +++ b/common/src/main/java/com/strategyobject/substrateclient/common/codegen/ProcessorContext.java @@ -8,6 +8,7 @@ import javax.annotation.processing.Messager; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; +import javax.lang.model.type.ArrayType; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; @@ -33,11 +34,16 @@ public boolean isAssignable(@NonNull TypeMirror candidate, @NonNull TypeMirror s public boolean isSubtype(@NonNull TypeMirror candidate, @NonNull TypeMirror supertype) { return typeUtils.isSubtype(candidate, supertype); } - - public boolean isGeneric(@NonNull TypeMirror type) { - return ((TypeElement) typeUtils.asElement(type)) - .getTypeParameters() - .size() > 0; + + public boolean isNonGeneric(@NonNull TypeMirror type) { + if (type instanceof ArrayType) { + return isNonGeneric(((ArrayType) type).getComponentType()); + } + + return type.getKind().isPrimitive() || + ((TypeElement) typeUtils.asElement(type)) + .getTypeParameters() + .size() == 0; } public TypeMirror erasure(@NonNull TypeMirror type) { diff --git a/common/src/main/java/com/strategyobject/substrateclient/common/codegen/TypeNotSupportedException.java b/common/src/main/java/com/strategyobject/substrateclient/common/codegen/TypeNotSupportedException.java new file mode 100644 index 00000000..58f5561f --- /dev/null +++ b/common/src/main/java/com/strategyobject/substrateclient/common/codegen/TypeNotSupportedException.java @@ -0,0 +1,9 @@ +package com.strategyobject.substrateclient.common.codegen; + +import javax.lang.model.type.TypeMirror; + +public class TypeNotSupportedException extends IllegalArgumentException { + public TypeNotSupportedException(TypeMirror type) { + super("Type is not supported: " + type); + } +} diff --git a/common/src/main/java/com/strategyobject/substrateclient/common/codegen/TypeTraverser.java b/common/src/main/java/com/strategyobject/substrateclient/common/codegen/TypeTraverser.java index 3893622e..c97e7f8d 100644 --- a/common/src/main/java/com/strategyobject/substrateclient/common/codegen/TypeTraverser.java +++ b/common/src/main/java/com/strategyobject/substrateclient/common/codegen/TypeTraverser.java @@ -26,6 +26,10 @@ public TypeTraverser(Class clazz) { protected abstract T whenGenericType(@NonNull DeclaredType type, TypeMirror override, @NonNull T[] subtypes); + protected abstract T whenArrayPrimitiveType(@NonNull ArrayType type, TypeMirror override); + + protected abstract T whenArrayType(@NonNull ArrayType type, TypeMirror override, @NonNull T subtype); + protected boolean doTraverseArguments(@NonNull DeclaredType type, TypeMirror override) { return true; } @@ -40,20 +44,32 @@ public T traverse(@NonNull TypeMirror type) { return whenPrimitiveType((PrimitiveType) type, null); } + if (type instanceof ArrayType) { + val arrayType = (ArrayType) type; + return arrayType.getComponentType().getKind().isPrimitive() ? + whenArrayPrimitiveType(arrayType, null) : + whenArrayType( + arrayType, + null, + traverse(arrayType.getComponentType())); + } + if (!(type instanceof DeclaredType)) { - throw new IllegalArgumentException("Type is not supported: " + type); + throw new TypeNotSupportedException(type); } val declaredType = (DeclaredType) type; val typeArguments = getTypeArgumentsOrDefault(declaredType, null); - return typeArguments.size() == 0 ? - whenNonGenericType(declaredType, null) : - whenGenericType( - declaredType, - null, - typeArguments.stream() - .map(this::traverse) - .toArray(x -> (T[]) Array.newInstance(clazz, typeArguments.size()))); + if (typeArguments.size() == 0) { + return whenNonGenericType(declaredType, null); + } + + return whenGenericType( + declaredType, + null, + typeArguments.stream() + .map(this::traverse) + .toArray(x -> (T[]) Array.newInstance(clazz, typeArguments.size()))); } @SuppressWarnings({"unchecked", "UnstableApiUsage"}) @@ -66,8 +82,30 @@ public T traverse(@NonNull TypeMirror type, @NonNull TypeTraverser.TypeTreeNode return whenPrimitiveType((PrimitiveType) type, typeOverride.type); } + if (type instanceof ArrayType) { + val arrayType = (ArrayType) type; + if (arrayType.getComponentType().getKind().isPrimitive()) { + return whenArrayPrimitiveType(arrayType, typeOverride.type); + } + + switch (typeOverride.children.size()) { + case 0: + return whenArrayType( + arrayType, + typeOverride.type, + traverse(arrayType.getComponentType())); + case 1: + return whenArrayType( + arrayType, + typeOverride.type, + traverse(arrayType.getComponentType(), typeOverride.children.get(0))); + default: + throw new IllegalArgumentException("Array type cannot be overridden by a generic type with more than one parameter"); + } + } + if (!(type instanceof DeclaredType)) { - throw new IllegalArgumentException("Type is not supported: " + type); + throw new TypeNotSupportedException(type); } val declaredType = (DeclaredType) type; @@ -107,8 +145,18 @@ public T traverse(@NonNull TypeTraverser.TypeTreeNode typeOverride) { return whenPrimitiveType((PrimitiveType) typeOverride.type, typeOverride.type); } + if (typeOverride.type instanceof ArrayType) { + val arrayType = (ArrayType) typeOverride.type; + return arrayType.getComponentType().getKind().isPrimitive() ? + whenArrayPrimitiveType(arrayType, arrayType) : + whenArrayType( + arrayType, + arrayType, + traverse(arrayType.getComponentType())); + } + if (!(typeOverride.type instanceof DeclaredType)) { - throw new IllegalArgumentException("Type is not supported: " + typeOverride.type); + throw new TypeNotSupportedException(typeOverride.type); } val declaredType = (DeclaredType) typeOverride.type; diff --git a/common/src/main/java/com/strategyobject/substrateclient/common/codegen/TypeUtils.java b/common/src/main/java/com/strategyobject/substrateclient/common/codegen/TypeUtils.java index f7ff2e17..26b17de8 100644 --- a/common/src/main/java/com/strategyobject/substrateclient/common/codegen/TypeUtils.java +++ b/common/src/main/java/com/strategyobject/substrateclient/common/codegen/TypeUtils.java @@ -3,7 +3,10 @@ import lombok.val; import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import static com.strategyobject.substrateclient.common.utils.StringUtils.capitalize; @@ -29,4 +32,20 @@ public static String getGetterName(VariableElement field) { return prefix + capitalize(fieldName); } + + public static String getSimpleName(TypeMirror type) { + if (type.getKind().isPrimitive()) { + return type.toString(); + } + + if (type instanceof DeclaredType) { + return ((DeclaredType) type).asElement().getSimpleName().toString(); + } + + if (type instanceof ArrayType) { + return String.format("%s[]", getSimpleName(((ArrayType) type).getComponentType())); + } + + throw new IllegalArgumentException(String.format("Cannot populate the name of %s", type)); + } } diff --git a/common/src/test/java/com/strategyobject/substrateclient/common/utils/HexConverterTests.java b/common/src/test/java/com/strategyobject/substrateclient/common/utils/HexConverterTests.java index 37ad2f30..ab58dbf2 100644 --- a/common/src/test/java/com/strategyobject/substrateclient/common/utils/HexConverterTests.java +++ b/common/src/test/java/com/strategyobject/substrateclient/common/utils/HexConverterTests.java @@ -1,86 +1,98 @@ package com.strategyobject.substrateclient.common.utils; -import lombok.RequiredArgsConstructor; -import lombok.val; -import org.junit.jupiter.api.Test; +import com.strategyobject.substrateclient.tests.TestSuite; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.function.Executable; -import java.util.ArrayList; +import java.util.Arrays; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; public class HexConverterTests { - @Test - void toHex() { - val testCases = new ArrayList>(); - testCases.add(new TestCase<>(new byte[]{0}, "0x00")); - testCases.add(new TestCase<>(new byte[]{(byte) 255}, "0xff")); - testCases.add(new TestCase<>(new byte[]{(byte) 255, 0}, "0xff00")); - testCases.add(new TestCase<>(new byte[]{(byte) 0, (byte) 255}, "0x00ff")); - testCases.add(new TestCase<>(new byte[]{127, (byte) 128}, "0x7f80")); - testCases.add(new TestCase<>(new byte[]{(byte) 128, (byte) 127}, "0x807f")); + @TestFactory + Stream toHex() { + return TestSuite.of( + Test.toHex(new byte[]{0}, "0x00"), + Test.toHex(new byte[]{(byte) 255}, "0xff"), + Test.toHex(new byte[]{(byte) 255, 0}, "0xff00"), + Test.toHex(new byte[]{(byte) 0, (byte) 255}, "0x00ff"), + Test.toHex(new byte[]{127, (byte) 128}, "0x7f80"), + Test.toHex(new byte[]{(byte) 128, (byte) 127}, "0x807f") + ); + } - for (val test : testCases) { - assertEquals(test.expected, HexConverter.toHex(test.given)); - } + @TestFactory + Stream toHexThrows() { + return TestSuite.of( + Test.toHex(null, IllegalArgumentException.class), + Test.toHex(new byte[]{}, IllegalArgumentException.class) + ); } - @Test - void toHexThrows() { - val testCases = new ArrayList>>(); - testCases.add(new TestCase<>(null, IllegalArgumentException.class)); - testCases.add(new TestCase<>(new byte[]{}, IllegalArgumentException.class)); + @TestFactory + Stream toBytes() { + return TestSuite.of( + Test.toBytes("0x00", new byte[]{0}), + Test.toBytes("0X00", new byte[]{0}), + Test.toBytes("00", new byte[]{0}), + Test.toBytes("007f", new byte[]{0, 127}), + Test.toBytes("ff", new byte[]{(byte) 255}), + Test.toBytes("FF", new byte[]{(byte) 255}), + Test.toBytes("0x807f", new byte[]{(byte) 128, (byte) 127}), + Test.toBytes("0x000102030a0b0c0d0e0f", new byte[]{0, 1, 2, 3, 0x0a, 11, 12, 13, 14, 0x0f})); + } - for (val test : testCases) { - assertThrows(test.expected, () -> HexConverter.toHex(test.given)); - } + @TestFactory + Stream toBytesThrows() { + return TestSuite.of( + Test.toBytes(null, IllegalArgumentException.class), + Test.toBytes("0x0g", IllegalArgumentException.class), + Test.toBytes("0x0G", IllegalArgumentException.class), + Test.toBytes("0x!a", IllegalArgumentException.class), + Test.toBytes("0x!%", IllegalArgumentException.class)); } - @Test - void toBytes() { - val testCases = new ArrayList>(); - testCases.add(new TestCase<>("0x00", new byte[]{0})); - testCases.add(new TestCase<>("0X00", new byte[]{0})); - testCases.add(new TestCase<>("00", new byte[]{0})); - testCases.add(new TestCase<>("007f", new byte[]{0, 127})); - testCases.add(new TestCase<>("ff", new byte[]{(byte) 255})); - testCases.add(new TestCase<>("FF", new byte[]{(byte) 255})); - testCases.add(new TestCase<>("0x807f", new byte[]{(byte) 128, (byte) 127})); - testCases.add(new TestCase<>( - "0x000102030a0b0c0d0e0f", - new byte[]{ - 0, - 1, - 2, - 3, - 0x0a, - 11, - 12, - 13, - 14, - 0x0f})); + @AllArgsConstructor(access = AccessLevel.PRIVATE) + static class Test extends TestSuite.TestCase { + private final String displayName; + private final Executable executable; - for (val test : testCases) { - assertArrayEquals(test.expected, HexConverter.toBytes(test.given)); + @Override + public String getDisplayName() { + return displayName; + } + + @Override + public void execute() throws Throwable { + executable.execute(); } - } - @Test - void toBytesThrows() { - val testCases = new ArrayList>>(); - testCases.add(new TestCase<>(null, IllegalArgumentException.class)); - testCases.add(new TestCase<>("0x0g", IllegalArgumentException.class)); - testCases.add(new TestCase<>("0x0G", IllegalArgumentException.class)); - testCases.add(new TestCase<>("0x!a", IllegalArgumentException.class)); - testCases.add(new TestCase<>("0x!%", IllegalArgumentException.class)); + public static Test toHex(byte[] given, String expected) { + return new Test( + String.format("Convert %s to %s", Arrays.toString(given), expected), + () -> assertEquals(expected, HexConverter.toHex(given))); + } - for (val test : testCases) { - assertThrows(test.expected, () -> HexConverter.toBytes(test.given)); + public static Test toHex(byte[] given, Class expected) { + return new Test( + String.format("Convert of %s throws %s", Arrays.toString(given), expected), + () -> assertThrows(expected, () -> HexConverter.toHex(given))); } - } - @RequiredArgsConstructor - static class TestCase { - final T1 given; - final T2 expected; + public static Test toBytes(String given, byte[] expected) { + return new Test( + String.format("Convert %s to %s", given, Arrays.toString(expected)), + () -> assertArrayEquals(expected, HexConverter.toBytes(given))); + } + + public static Test toBytes(String given, Class expected) { + return new Test( + String.format("Convert %s throws %s", given, expected), + () -> assertThrows(expected, () -> HexConverter.toBytes(given))); + } } } diff --git a/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/Constants.java b/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/Constants.java index 1c9596b7..1e8b55ec 100644 --- a/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/Constants.java +++ b/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/Constants.java @@ -32,4 +32,7 @@ public class Constants { public static final String SCALE_WRITER_REGISTRY = "scaleWriterRegistry"; public static final String SCALE_READER_REGISTRY = "scaleReaderRegistry"; + + private Constants() { + } } diff --git a/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/DecoderCompositor.java b/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/DecoderCompositor.java index f373c0ce..e9404519 100644 --- a/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/DecoderCompositor.java +++ b/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/DecoderCompositor.java @@ -1,6 +1,8 @@ package com.strategyobject.substrateclient.rpc.codegen.decoder; import com.squareup.javapoet.CodeBlock; +import com.strategyobject.substrateclient.common.codegen.Constants; +import com.strategyobject.substrateclient.common.codegen.ProcessorContext; import com.strategyobject.substrateclient.common.codegen.TypeTraverser; import com.strategyobject.substrateclient.rpc.core.DecoderPair; import com.strategyobject.substrateclient.rpc.core.RpcDecoder; @@ -10,26 +12,23 @@ import lombok.NonNull; import lombok.var; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.PrimitiveType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.util.Types; +import javax.lang.model.type.*; import java.util.Map; import static com.strategyobject.substrateclient.rpc.codegen.Constants.PAIR_FACTORY_METHOD; import static com.strategyobject.substrateclient.rpc.codegen.Constants.RESOLVE_AND_INJECT_METHOD; public class DecoderCompositor extends TypeTraverser { - private final Types typeUtils; + private final ProcessorContext context; private final Map typeVarMap; private final String decoderAccessor; private final String readerAccessor; private final String readerMethod; private final String decoderRegistryVarName; private final String scaleRegistryVarName; + private final TypeMirror arrayType; - public DecoderCompositor(@NonNull Types typeUtils, + public DecoderCompositor(@NonNull ProcessorContext context, @NonNull Map typeVarMap, @NonNull String decoderAccessor, @NonNull String readerAccessor, @@ -37,17 +36,18 @@ public DecoderCompositor(@NonNull Types typeUtils, @NonNull String decoderRegistryVarName, @NonNull String scaleRegistryVarName) { super(CodeBlock.class); - this.typeUtils = typeUtils; + + this.context = context; this.typeVarMap = typeVarMap; this.decoderAccessor = decoderAccessor; this.readerAccessor = readerAccessor; this.readerMethod = readerMethod; this.decoderRegistryVarName = decoderRegistryVarName; this.scaleRegistryVarName = scaleRegistryVarName; + this.arrayType = context.erasure(context.getType(Constants.ARRAY_TYPE)); } - @Override - protected CodeBlock whenTypeVar(@NonNull TypeVariable type, TypeMirror _override) { + private CodeBlock getTypeVarCodeBlock(TypeVariable type) { return CodeBlock.builder() .add("$T.$L(($T) ", DecoderPair.class, PAIR_FACTORY_METHOD, RpcDecoder.class) .add(decoderAccessor, typeVarMap.get(type.toString())) @@ -57,16 +57,6 @@ protected CodeBlock whenTypeVar(@NonNull TypeVariable type, TypeMirror _override .build(); } - @Override - protected CodeBlock whenPrimitiveType(@NonNull PrimitiveType type, TypeMirror _override) { - return getNonGenericCodeBlock(type); - } - - @Override - protected CodeBlock whenNonGenericType(@NonNull DeclaredType type, TypeMirror _override) { - return getNonGenericCodeBlock(type); - } - private CodeBlock getNonGenericCodeBlock(TypeMirror type) { return CodeBlock.builder() .add("$T.$L(", DecoderPair.class, PAIR_FACTORY_METHOD) @@ -77,9 +67,8 @@ private CodeBlock getNonGenericCodeBlock(TypeMirror type) { .build(); } - @Override - protected CodeBlock whenGenericType(@NonNull DeclaredType type, TypeMirror _override, @NonNull CodeBlock[] subtypes) { - TypeMirror resolveType = typeUtils.erasure(type); + private CodeBlock getGenericCodeBlock(TypeMirror type, CodeBlock[] subtypes) { + TypeMirror resolveType = context.erasure(type); var builder = CodeBlock.builder() .add("$T.$L(", DecoderPair.class, PAIR_FACTORY_METHOD) @@ -100,4 +89,34 @@ protected CodeBlock whenGenericType(@NonNull DeclaredType type, TypeMirror _over return builder .add(")").build(); } + + @Override + protected CodeBlock whenTypeVar(@NonNull TypeVariable type, TypeMirror _override) { + return getTypeVarCodeBlock(type); + } + + @Override + protected CodeBlock whenPrimitiveType(@NonNull PrimitiveType type, TypeMirror _override) { + return getNonGenericCodeBlock(type); + } + + @Override + protected CodeBlock whenNonGenericType(@NonNull DeclaredType type, TypeMirror _override) { + return getNonGenericCodeBlock(type); + } + + @Override + protected CodeBlock whenGenericType(@NonNull DeclaredType type, TypeMirror _override, @NonNull CodeBlock[] subtypes) { + return getGenericCodeBlock(type, subtypes); + } + + @Override + protected CodeBlock whenArrayPrimitiveType(@NonNull ArrayType type, TypeMirror _override) { + return getNonGenericCodeBlock(type); + } + + @Override + protected CodeBlock whenArrayType(@NonNull ArrayType type, TypeMirror _override, @NonNull CodeBlock subtype) { + return getGenericCodeBlock(arrayType, new CodeBlock[]{subtype}); + } } diff --git a/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/RpcDecoderAnnotatedClass.java b/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/RpcDecoderAnnotatedClass.java index 4ddf40fe..a1df6fec 100644 --- a/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/RpcDecoderAnnotatedClass.java +++ b/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/RpcDecoderAnnotatedClass.java @@ -118,7 +118,8 @@ private void addMethodBody(MethodSpec.Builder methodSpec, ProcessorContext conte } private void setFields(MethodSpec.Builder methodSpec, ProcessorContext context) throws ProcessingException { - val decoderCompositor = new DecoderCompositor(context.getTypeUtils(), + val decoderCompositor = new DecoderCompositor( + context, typeVarMap, String.format("%s[$L].%s", DECODERS_ARG, DECODER_UNSAFE_ACCESSOR), String.format("%s[$L].%s", DECODERS_ARG, READER_UNSAFE_ACCESSOR), @@ -126,7 +127,8 @@ private void setFields(MethodSpec.Builder methodSpec, ProcessorContext context) DECODER_REGISTRY, SCALE_READER_REGISTRY); val scaleAnnotationParser = new ScaleAnnotationParser(context); - val scaleReaderCompositor = ReaderCompositor.forAnyType(context, + val scaleReaderCompositor = ReaderCompositor.forAnyType( + context, typeVarMap, String.format("%s[$L].%s", DECODERS_ARG, READER_ACCESSOR), SCALE_READER_REGISTRY); diff --git a/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/EncoderCompositor.java b/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/EncoderCompositor.java index 16fae5be..158876ea 100644 --- a/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/EncoderCompositor.java +++ b/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/EncoderCompositor.java @@ -1,6 +1,7 @@ package com.strategyobject.substrateclient.rpc.codegen.encoder; import com.squareup.javapoet.CodeBlock; +import com.strategyobject.substrateclient.common.codegen.Constants; import com.strategyobject.substrateclient.common.codegen.ProcessorContext; import com.strategyobject.substrateclient.common.codegen.TypeTraverser; import com.strategyobject.substrateclient.rpc.core.EncoderPair; @@ -12,10 +13,7 @@ import lombok.val; import lombok.var; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.PrimitiveType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.*; import java.util.Map; import static com.strategyobject.substrateclient.rpc.codegen.Constants.*; @@ -29,6 +27,7 @@ public class EncoderCompositor extends TypeTraverser { private final String writerMethod; private final String encoderRegistryVarName; private final String scaleRegistryVarName; + private final TypeMirror arrayType; public EncoderCompositor(@NonNull ProcessorContext context, @NonNull Map typeVarMap, @@ -46,33 +45,7 @@ public EncoderCompositor(@NonNull ProcessorContext context, this.writerMethod = writerMethod; this.encoderRegistryVarName = encoderRegistryVarName; this.scaleRegistryVarName = scaleRegistryVarName; - } - - @Override - protected CodeBlock whenTypeVar(@NonNull TypeVariable type, TypeMirror _override) { - val builder = CodeBlock.builder() - .add("$T.$L(($T) ", EncoderPair.class, PAIR_FACTORY_METHOD, RpcEncoder.class); - - if (context.isAssignable(type, selfEncodable)) { - builder.add("$L.resolve($T.class)", encoderRegistryVarName, selfEncodable); - } else { - builder.add(encoderAccessor, typeVarMap.get(type.toString())); - } - - return builder.add(", ($T) ", ScaleWriter.class) - .add(writerAccessor, typeVarMap.get(type.toString())) - .add(")") - .build(); - } - - @Override - protected CodeBlock whenPrimitiveType(@NonNull PrimitiveType type, TypeMirror _override) { - return getNonGenericCodeBlock(type); - } - - @Override - protected CodeBlock whenNonGenericType(@NonNull DeclaredType type, TypeMirror _override) { - return getNonGenericCodeBlock(type); + this.arrayType = context.erasure(context.getType(Constants.ARRAY_TYPE)); } private CodeBlock getNonGenericCodeBlock(TypeMirror type) { @@ -89,8 +62,7 @@ private CodeBlock getNonGenericCodeBlock(TypeMirror type) { .build(); } - @Override - protected CodeBlock whenGenericType(@NonNull DeclaredType type, TypeMirror _override, @NonNull CodeBlock[] subtypes) { + private CodeBlock getGenericCodeBlock(TypeMirror type, CodeBlock[] subtypes) { TypeMirror resolveType = context.erasure(type); val builder = CodeBlock.builder() .add("$T.$L(", EncoderPair.class, PAIR_FACTORY_METHOD); @@ -116,6 +88,48 @@ protected CodeBlock whenGenericType(@NonNull DeclaredType type, TypeMirror _over return builder.add(")").build(); } + @Override + protected CodeBlock whenTypeVar(@NonNull TypeVariable type, TypeMirror _override) { + val builder = CodeBlock.builder() + .add("$T.$L(($T) ", EncoderPair.class, PAIR_FACTORY_METHOD, RpcEncoder.class); + + if (context.isAssignable(type, selfEncodable)) { + builder.add("$L.resolve($T.class)", encoderRegistryVarName, selfEncodable); + } else { + builder.add(encoderAccessor, typeVarMap.get(type.toString())); + } + + return builder.add(", ($T) ", ScaleWriter.class) + .add(writerAccessor, typeVarMap.get(type.toString())) + .add(")") + .build(); + } + + @Override + protected CodeBlock whenPrimitiveType(@NonNull PrimitiveType type, TypeMirror _override) { + return getNonGenericCodeBlock(type); + } + + @Override + protected CodeBlock whenNonGenericType(@NonNull DeclaredType type, TypeMirror _override) { + return getNonGenericCodeBlock(type); + } + + @Override + protected CodeBlock whenGenericType(@NonNull DeclaredType type, TypeMirror _override, @NonNull CodeBlock[] subtypes) { + return getGenericCodeBlock(type, subtypes); + } + + @Override + protected CodeBlock whenArrayPrimitiveType(@NonNull ArrayType type, TypeMirror _override) { + return getNonGenericCodeBlock(type); + } + + @Override + protected CodeBlock whenArrayType(@NonNull ArrayType type, TypeMirror _override, @NonNull CodeBlock subtype) { + return getGenericCodeBlock(arrayType, new CodeBlock[]{subtype}); + } + @Override protected boolean doTraverseArguments(@NonNull DeclaredType type, TypeMirror override) { return override != null || !context.isAssignable(context.erasure(type), selfEncodable); diff --git a/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcMethodProcessor.java b/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcMethodProcessor.java index 1d8b5faa..200d8faf 100644 --- a/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcMethodProcessor.java +++ b/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcMethodProcessor.java @@ -123,7 +123,8 @@ private CodeBlock getDecodeCodeBlock(AnnotatedConstruct annotated, } private CodeBlock getRpcDecodeCodeBlock(TypeMirror resultType, String arg, ProcessorContext context) { - val decoderCompositor = new DecoderCompositor(context.getTypeUtils(), + val decoderCompositor = new DecoderCompositor( + context, EMPTY_TYPE_VAR_MAP, String.format("%s[$L].%s", DECODERS_ARG, DECODER_UNSAFE_ACCESSOR), String.format("%s[$L].%s", DECODERS_ARG, READER_UNSAFE_ACCESSOR), diff --git a/rpc/rpc-core/build.gradle b/rpc/rpc-core/build.gradle index f6298418..2cf62202 100644 --- a/rpc/rpc-core/build.gradle +++ b/rpc/rpc-core/build.gradle @@ -3,5 +3,7 @@ dependencies { implementation project(':scale') implementation project(':transport') + testImplementation project(':tests') + testImplementation 'com.google.code.gson:gson:2.9.0' } \ No newline at end of file diff --git a/rpc/rpc-core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/ArrayDecoder.java b/rpc/rpc-core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/ArrayDecoder.java new file mode 100644 index 00000000..f22f1af6 --- /dev/null +++ b/rpc/rpc-core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/ArrayDecoder.java @@ -0,0 +1,24 @@ +package com.strategyobject.substrateclient.rpc.core.decoders; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.rpc.core.DecoderPair; +import com.strategyobject.substrateclient.transport.RpcObject; +import lombok.val; + +public class ArrayDecoder extends AbstractDecoder { + @Override + protected Object[] decodeNonNull(RpcObject value, DecoderPair[] decoders) { + val nestedDecoder = decoders[0].getDecoderOrThrow(); + + return value.asList() + .stream() + .map(nestedDecoder::decode) + .toArray(Object[]::new); + } + + @Override + protected void checkArguments(RpcObject value, DecoderPair[] decoders) { + Preconditions.checkArgument(decoders != null && decoders.length == 1); + Preconditions.checkNotNull(decoders[0]); + } +} diff --git a/rpc/rpc-core/src/main/java/com/strategyobject/substrateclient/rpc/core/encoders/ArrayEncoder.java b/rpc/rpc-core/src/main/java/com/strategyobject/substrateclient/rpc/core/encoders/ArrayEncoder.java new file mode 100644 index 00000000..292ddb4f --- /dev/null +++ b/rpc/rpc-core/src/main/java/com/strategyobject/substrateclient/rpc/core/encoders/ArrayEncoder.java @@ -0,0 +1,25 @@ +package com.strategyobject.substrateclient.rpc.core.encoders; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.rpc.core.EncoderPair; +import com.strategyobject.substrateclient.rpc.core.RpcEncoder; +import lombok.val; + +import java.util.Arrays; +import java.util.stream.Collectors; + +public class ArrayEncoder implements RpcEncoder { + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + public Object encode(Object[] source, EncoderPair... encoders) { + if (source == null) { + return null; + } + + Preconditions.checkArgument(encoders != null && encoders.length == 1); + Preconditions.checkNotNull(encoders[0]); + val nestedEncoder = (RpcEncoder) encoders[0].getEncoderOrThrow(); + + return Arrays.stream(source).map(nestedEncoder::encode).collect(Collectors.toList()); + } +} diff --git a/rpc/rpc-core/src/main/java/com/strategyobject/substrateclient/rpc/core/registries/RpcDecoderRegistry.java b/rpc/rpc-core/src/main/java/com/strategyobject/substrateclient/rpc/core/registries/RpcDecoderRegistry.java index 6ec2137e..dd14802c 100644 --- a/rpc/rpc-core/src/main/java/com/strategyobject/substrateclient/rpc/core/registries/RpcDecoderRegistry.java +++ b/rpc/rpc-core/src/main/java/com/strategyobject/substrateclient/rpc/core/registries/RpcDecoderRegistry.java @@ -1,5 +1,6 @@ package com.strategyobject.substrateclient.rpc.core.registries; +import com.strategyobject.substrateclient.common.CommonType; import com.strategyobject.substrateclient.common.reflection.Scanner; import com.strategyobject.substrateclient.rpc.core.RpcDecoder; import com.strategyobject.substrateclient.rpc.core.annotations.AutoRegister; @@ -20,7 +21,8 @@ public final class RpcDecoderRegistry { private final Map, RpcDecoder> decoders; private RpcDecoderRegistry() { - decoders = new ConcurrentHashMap<>(); + decoders = new ConcurrentHashMap<>(128); + register(new ListDecoder(), List.class); register(new MapDecoder(), Map.class); register(new VoidDecoder(), Void.class); @@ -32,6 +34,7 @@ private RpcDecoderRegistry() { register(new LongDecoder(), Long.class, long.class); register(new ShortDecoder(), Short.class, short.class); register(new StringDecoder(), String.class); + register(new ArrayDecoder(), CommonType.Array.class); registerAnnotatedFrom(ROOT_PREFIX); } diff --git a/rpc/rpc-core/src/main/java/com/strategyobject/substrateclient/rpc/core/registries/RpcEncoderRegistry.java b/rpc/rpc-core/src/main/java/com/strategyobject/substrateclient/rpc/core/registries/RpcEncoderRegistry.java index ca501e49..b4423f1c 100644 --- a/rpc/rpc-core/src/main/java/com/strategyobject/substrateclient/rpc/core/registries/RpcEncoderRegistry.java +++ b/rpc/rpc-core/src/main/java/com/strategyobject/substrateclient/rpc/core/registries/RpcEncoderRegistry.java @@ -1,8 +1,10 @@ package com.strategyobject.substrateclient.rpc.core.registries; +import com.strategyobject.substrateclient.common.CommonType; import com.strategyobject.substrateclient.common.reflection.Scanner; import com.strategyobject.substrateclient.rpc.core.RpcEncoder; import com.strategyobject.substrateclient.rpc.core.annotations.AutoRegister; +import com.strategyobject.substrateclient.rpc.core.encoders.ArrayEncoder; import com.strategyobject.substrateclient.rpc.core.encoders.ListEncoder; import com.strategyobject.substrateclient.rpc.core.encoders.MapEncoder; import com.strategyobject.substrateclient.rpc.core.encoders.PlainEncoder; @@ -22,12 +24,14 @@ public final class RpcEncoderRegistry { private final Map, RpcEncoder> encoders; private RpcEncoderRegistry() { - encoders = new ConcurrentHashMap<>(); + encoders = new ConcurrentHashMap<>(128); + register(new PlainEncoder<>(), Void.class, String.class, Boolean.class, boolean.class, Byte.class, byte.class, Double.class, double.class, Float.class, float.class, Integer.class, int.class, Long.class, long.class, Short.class, short.class); register(new ListEncoder(), List.class); register(new MapEncoder(), Map.class); + register(new ArrayEncoder(), CommonType.Array.class); registerAnnotatedFrom(ROOT_PREFIX); } diff --git a/rpc/rpc-core/src/test/java/com/strategyobject/substrateclient/rpc/core/decoders/ArrayDecoderTest.java b/rpc/rpc-core/src/test/java/com/strategyobject/substrateclient/rpc/core/decoders/ArrayDecoderTest.java new file mode 100644 index 00000000..b565b2fc --- /dev/null +++ b/rpc/rpc-core/src/test/java/com/strategyobject/substrateclient/rpc/core/decoders/ArrayDecoderTest.java @@ -0,0 +1,31 @@ +package com.strategyobject.substrateclient.rpc.core.decoders; + +import com.strategyobject.substrateclient.rpc.core.DecoderPair; +import com.strategyobject.substrateclient.transport.RpcObject; +import lombok.val; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +class ArrayDecoderTest { + + @Test + void decodeNull() { + val decoder = new ArrayDecoder().inject(DecoderPair.of(new IntDecoder(), null)); + val rpcObject = RpcObject.ofNull(); + val actual = decoder.decode(rpcObject); + + assertArrayEquals(null, actual); + } + + @Test + void decodeNonNull() { + val decoder = new ArrayDecoder().inject(DecoderPair.of(new IntDecoder(), null)); + val rpcObject = RpcObject.of(Arrays.asList(RpcObject.of(1), RpcObject.ofNull(), RpcObject.of(3))); + val actual = decoder.decode(rpcObject); + + assertArrayEquals(new Integer[]{1, null, 3}, actual); + } +} \ No newline at end of file diff --git a/rpc/rpc-core/src/test/java/com/strategyobject/substrateclient/rpc/core/decoders/KnownDecoderTests.java b/rpc/rpc-core/src/test/java/com/strategyobject/substrateclient/rpc/core/decoders/KnownDecoderTests.java index a0d9a2fe..657272f2 100644 --- a/rpc/rpc-core/src/test/java/com/strategyobject/substrateclient/rpc/core/decoders/KnownDecoderTests.java +++ b/rpc/rpc-core/src/test/java/com/strategyobject/substrateclient/rpc/core/decoders/KnownDecoderTests.java @@ -2,74 +2,109 @@ import com.strategyobject.substrateclient.rpc.core.DecoderPair; import com.strategyobject.substrateclient.rpc.core.RpcDecoder; +import com.strategyobject.substrateclient.tests.TestSuite; import com.strategyobject.substrateclient.transport.RpcObject; -import lombok.RequiredArgsConstructor; -import org.junit.jupiter.api.Test; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.val; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; import java.util.Arrays; import java.util.HashMap; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; public class KnownDecoderTests { - private final TestCase[] testCases = { - new TestCase<>(new BooleanDecoder(), RpcObject.of(true), true), - new TestCase<>(new BooleanDecoder(), RpcObject.ofNull(), null), - new TestCase<>(new ByteDecoder(), RpcObject.of(5), (byte) 5), - new TestCase<>(new ByteDecoder(), RpcObject.ofNull(), null), - new TestCase<>(new DoubleDecoder(), RpcObject.of(15.25), 15.25), - new TestCase<>(new DoubleDecoder(), RpcObject.ofNull(), null), - new TestCase<>(new FloatDecoder(), RpcObject.of(19.5), 19.5f), - new TestCase<>(new FloatDecoder(), RpcObject.ofNull(), null), - new TestCase<>(new IntDecoder(), RpcObject.of(-5), -5), - new TestCase<>(new IntDecoder(), RpcObject.ofNull(), null), - new TestCase<>(new LongDecoder(), RpcObject.of(2147483648L), 2147483648L), - new TestCase<>(new LongDecoder(), RpcObject.ofNull(), null), - new TestCase<>(new ShortDecoder(), RpcObject.of(290), (short) 290), - new TestCase<>(new ShortDecoder(), RpcObject.ofNull(), null), - new TestCase<>(new StringDecoder(), RpcObject.of("some"), "some"), - new TestCase<>(new StringDecoder(), RpcObject.ofNull(), null), - new TestCase<>(new VoidDecoder(), RpcObject.ofNull(), null), - new TestCase<>( - new ListDecoder().inject(DecoderPair.of(new IntDecoder(), null)), - RpcObject.of(Arrays.asList(RpcObject.of(1), RpcObject.ofNull(), RpcObject.of(3))), - Arrays.asList(1, null, 3)), - new TestCase<>( - new ListDecoder().inject(DecoderPair.of(new IntDecoder(), null)), - RpcObject.ofNull(), - null), - new TestCase<>( - new MapDecoder().inject( - DecoderPair.of(new StringDecoder(), null), - DecoderPair.of(new IntDecoder(), null)), - RpcObject.of(new HashMap() {{ - put("a", RpcObject.of(1)); - put("b", RpcObject.ofNull()); - put("c", RpcObject.of(3)); - }}), - new HashMap() {{ - put("a", 1); - put("b", null); - put("c", 3); - }}), - new TestCase<>( - new MapDecoder().inject( - DecoderPair.of(new StringDecoder(), null), - DecoderPair.of(new IntDecoder(), null)), - RpcObject.ofNull(), - null) - }; - @Test - public void decode() { - Arrays.stream(testCases) - .forEach(c -> assertEquals(c.expected, c.decoder.decode(c.rpcObject))); + @TestFactory + public Stream decode() { + return TestSuite.of( + Test.decode(new BooleanDecoder(), RpcObject.of(true), true), + Test.decode(new ByteDecoder(), RpcObject.of(5), (byte) 5), + Test.decode(new DoubleDecoder(), RpcObject.of(15.25), 15.25), + Test.decode(new FloatDecoder(), RpcObject.of(19.5), 19.5f), + Test.decode(new IntDecoder(), RpcObject.of(-5), -5), + Test.decode(new LongDecoder(), RpcObject.of(2147483648L), 2147483648L), + Test.decode(new ShortDecoder(), RpcObject.of(290), (short) 290), + Test.decode(new StringDecoder(), RpcObject.of("some"), "some"), + Test.decode(new ListDecoder().inject(DecoderPair.of(new IntDecoder(), null)), + RpcObject.of(Arrays.asList(RpcObject.of(1), RpcObject.ofNull(), RpcObject.of(3))), + Arrays.asList(1, null, 3)) + .withDecoderName(ListDecoder.class.getSimpleName()), + Test.decode(new MapDecoder().inject( + DecoderPair.of(new StringDecoder(), null), + DecoderPair.of(new IntDecoder(), null)), + RpcObject.of(new HashMap() {{ + put("a", RpcObject.of(1)); + put("b", RpcObject.ofNull()); + put("c", RpcObject.of(3)); + }}), + new HashMap() {{ + put("a", 1); + put("b", null); + put("c", 3); + }}) + .withDecoderName(MapDecoder.class.getSimpleName()) + ); } - @RequiredArgsConstructor - static class TestCase { + @TestFactory + public Stream decodeRpcNull() { + return TestSuite.of( + Test.decodeRpcNull(new BooleanDecoder(), null), + Test.decodeRpcNull(new ByteDecoder(), null), + Test.decodeRpcNull(new DoubleDecoder(), null), + Test.decodeRpcNull(new FloatDecoder(), null), + Test.decodeRpcNull(new IntDecoder(), null), + Test.decodeRpcNull(new LongDecoder(), null), + Test.decodeRpcNull(new ShortDecoder(), null), + Test.decodeRpcNull(new StringDecoder(), null), + Test.decodeRpcNull(new VoidDecoder(), null), + Test.decodeRpcNull( + new ListDecoder().inject(DecoderPair.of(new IntDecoder(), null)), + null) + .withDecoderName(ListDecoder.class.getSimpleName()), + Test.decodeRpcNull( + new MapDecoder().inject( + DecoderPair.of(new StringDecoder(), null), + DecoderPair.of(new IntDecoder(), null)), + null) + .withDecoderName(MapDecoder.class.getSimpleName()) + ); + } + + @AllArgsConstructor(access = AccessLevel.PRIVATE) + static class Test extends TestSuite.TestCase { private final RpcDecoder decoder; - private final RpcObject rpcObject; + private final RpcObject given; private final T expected; + private String decoderName; + + @Override + public String getDisplayName() { + val name = given.isNull() ? "Decode RpcNull with " : "Decode with "; + return name + decoderName; + } + + @Override + public void execute() { + val actual = decoder.decode(given); + assertEquals(expected, actual); + } + + public Test withDecoderName(String decoderName) { + this.decoderName = decoderName; + return this; + } + + public static Test decode(RpcDecoder decoder, RpcObject rpcObject, T expected) { + return new Test<>(decoder, rpcObject, expected, decoder.getClass().getSimpleName()); + } + + public static Test decodeRpcNull(RpcDecoder decoder, T expected) { + return new Test<>(decoder, RpcObject.ofNull(), expected, decoder.getClass().getSimpleName()); + } } } diff --git a/rpc/rpc-core/src/test/java/com/strategyobject/substrateclient/rpc/core/decoders/KnownEncoderTests.java b/rpc/rpc-core/src/test/java/com/strategyobject/substrateclient/rpc/core/decoders/KnownEncoderTests.java index 154c3c17..5864a7ef 100644 --- a/rpc/rpc-core/src/test/java/com/strategyobject/substrateclient/rpc/core/decoders/KnownEncoderTests.java +++ b/rpc/rpc-core/src/test/java/com/strategyobject/substrateclient/rpc/core/decoders/KnownEncoderTests.java @@ -6,40 +6,21 @@ import com.strategyobject.substrateclient.rpc.core.encoders.ListEncoder; import com.strategyobject.substrateclient.rpc.core.encoders.MapEncoder; import com.strategyobject.substrateclient.rpc.core.encoders.PlainEncoder; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import com.strategyobject.substrateclient.tests.TestSuite; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; import lombok.val; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; public class KnownEncoderTests { - private final TestCase[] testCases = { - new TestCase<>(new PlainEncoder<>(), false, false), - new TestCase<>(new PlainEncoder<>(), (byte) 5, (byte) 5), - new TestCase<>(new PlainEncoder<>(), 15.25, 15.25), - new TestCase<>(new PlainEncoder<>(), 19.5f, 19.5f), - new TestCase<>(new PlainEncoder<>(), -5, -5), - new TestCase<>(new PlainEncoder<>(), 2147483648L, 2147483648L), - new TestCase<>(new PlainEncoder<>(), (short) 290, (short) 290), - new TestCase<>(new PlainEncoder<>(), "some", "some"), - new TestCase<>(new PlainEncoder<>(), (Void) null, null), - - new TestCase<>( - new ListEncoder().inject(EncoderPair.of((source, encoders) -> source.toString(), null)), - Arrays.asList(1, 2, 3), - Arrays.asList("1", "2", "3")), - new TestCase<>( - new MapEncoder().inject( - EncoderPair.of(new PlainEncoder<>(), null), - EncoderPair.of((source, encoders) -> source.toString(), null)), - getSourceMap(), - getExpectedMap()), - }; private static Map getSourceMap() { val result = new HashMap(); @@ -59,22 +40,60 @@ private static Map getExpectedMap() { return result; } - @Test - @SuppressWarnings({"unchecked", "rawtypes"}) - public void encode() { - Gson gson = new Gson(); - - Arrays.stream(testCases) - .forEach(c -> assertEquals( - gson.toJson(c.getExpected()), - gson.toJson(((RpcEncoder)c.encoder).encode(c.source)))); + @TestFactory + public Stream encode() { + return TestSuite.of( + Test.encode(new PlainEncoder<>(), false, false), + Test.encode(new PlainEncoder<>(), (byte) 5, (byte) 5), + Test.encode(new PlainEncoder<>(), 15.25, 15.25), + Test.encode(new PlainEncoder<>(), 19.5f, 19.5f), + Test.encode(new PlainEncoder<>(), -5, -5), + Test.encode(new PlainEncoder<>(), 2147483648L, 2147483648L), + Test.encode(new PlainEncoder<>(), (short) 290, (short) 290), + Test.encode(new PlainEncoder<>(), "some", "some"), + Test.encode(new PlainEncoder<>(), (Void) null, null), + Test.encode( + new ListEncoder().inject(EncoderPair.of((source, encoders) -> source.toString(), null)), + Arrays.asList(1, 2, 3), + Arrays.asList("1", "2", "3")) + .withEncoderName(ListEncoder.class.getSimpleName()), + Test.encode( + new MapEncoder().inject( + EncoderPair.of(new PlainEncoder<>(), null), + EncoderPair.of((source, encoders) -> source.toString(), null)), + getSourceMap(), + getExpectedMap()) + .withEncoderName(MapEncoder.class.getSimpleName()) + ); } - @RequiredArgsConstructor - @Getter - static class TestCase { + @AllArgsConstructor(access = AccessLevel.PRIVATE) + static class Test extends TestSuite.TestCase { + private static final Gson GSON = new Gson(); + private final RpcEncoder encoder; - private final T source; + private final T given; private final Object expected; + private String encoderName; + + @Override + public String getDisplayName() { + return String.format("Encode %s with %s", given, encoderName); + } + + @Override + public void execute() { + val actual = encoder.encode(given); + assertEquals(GSON.toJson(expected), GSON.toJson(actual)); + } + + public Test withEncoderName(String encoderName) { + this.encoderName = encoderName; + return this; + } + + public static Test encode(RpcEncoder encoder, T given, Object expected) { + return new Test<>(encoder, given, expected, encoder.getClass().getSimpleName()); + } } } diff --git a/rpc/rpc-sections/build.gradle b/rpc/rpc-sections/build.gradle index 07cdb867..f9995649 100644 --- a/rpc/rpc-sections/build.gradle +++ b/rpc/rpc-sections/build.gradle @@ -1,11 +1,12 @@ dependencies { + implementation project(':common') compileOnly project(':rpc:rpc-codegen') - annotationProcessor project(':rpc:rpc-codegen') compileOnly project(':rpc:rpc-core') - compileOnly project(':transport') - implementation project(':types') implementation project(':rpc:rpc-types') implementation project(':scale') + compileOnly project(':transport') + implementation project(':types') + annotationProcessor project(':rpc:rpc-codegen') testImplementation project(':tests') testCompileOnly project(':rpc:rpc-core') @@ -14,8 +15,8 @@ dependencies { testImplementation project(':crypto') testAnnotationProcessor project(':scale:scale-codegen') - testImplementation 'org.testcontainers:testcontainers:1.16.3' - testImplementation 'org.testcontainers:junit-jupiter:1.16.3' + testImplementation 'org.testcontainers:testcontainers:1.17.1' + testImplementation 'org.testcontainers:junit-jupiter:1.17.1' testImplementation 'ch.qos.logback:logback-classic:1.2.11' testImplementation 'org.awaitility:awaitility:4.2.0' testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70' diff --git a/scale/build.gradle b/scale/build.gradle index 38415c7a..7cd8517c 100644 --- a/scale/build.gradle +++ b/scale/build.gradle @@ -1,4 +1,6 @@ dependencies { implementation project(':common') implementation project(':types') + + testImplementation project(':tests') } diff --git a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/ScaleAnnotationParser.java b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/ScaleAnnotationParser.java index 2dfd0dee..b6ef1d5e 100644 --- a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/ScaleAnnotationParser.java +++ b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/ScaleAnnotationParser.java @@ -4,6 +4,7 @@ import com.strategyobject.substrateclient.common.codegen.AnnotationUtils; import com.strategyobject.substrateclient.common.codegen.ProcessorContext; import com.strategyobject.substrateclient.common.codegen.TypeTraverser; +import com.strategyobject.substrateclient.common.codegen.TypeUtils; import com.strategyobject.substrateclient.common.utils.StringUtils; import com.strategyobject.substrateclient.scale.annotations.Scale; import com.strategyobject.substrateclient.scale.annotations.ScaleGeneric; @@ -13,7 +14,6 @@ import javax.lang.model.AnnotatedConstruct; import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import java.util.HashMap; import java.util.List; @@ -125,7 +125,7 @@ private Map getTypesMap(AnnotationMirror scaleGeneric) { } if (Strings.isNullOrEmpty(name)) { - name = ((DeclaredType) type).asElement().getSimpleName().toString(); + name = TypeUtils.getSimpleName(type); } result.put(name, type); diff --git a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/ReaderCompositor.java b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/ReaderCompositor.java index ef55bb37..1ae4da6e 100644 --- a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/ReaderCompositor.java +++ b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/ReaderCompositor.java @@ -2,15 +2,13 @@ import com.google.common.base.Strings; import com.squareup.javapoet.CodeBlock; +import com.strategyobject.substrateclient.common.codegen.Constants; import com.strategyobject.substrateclient.common.codegen.ProcessorContext; import com.strategyobject.substrateclient.common.codegen.TypeTraverser; import lombok.NonNull; import lombok.var; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.PrimitiveType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.*; import java.util.Map; public class ReaderCompositor extends TypeTraverser { @@ -18,6 +16,7 @@ public class ReaderCompositor extends TypeTraverser { private final Map typeVarMap; private final String readerAccessor; private final String registryVarName; + private final TypeMirror arrayType; private ReaderCompositor(ProcessorContext context, Map typeVarMap, @@ -29,6 +28,7 @@ private ReaderCompositor(ProcessorContext context, this.typeVarMap = typeVarMap; this.readerAccessor = readerAccessor; this.registryVarName = registryVarName; + this.arrayType = context.erasure(context.getType(Constants.ARRAY_TYPE)); } public static ReaderCompositor forAnyType(@NonNull ProcessorContext context, @@ -43,8 +43,7 @@ public static ReaderCompositor disallowOpenGeneric(@NonNull ProcessorContext con return new ReaderCompositor(context, null, null, registryVarName); } - @Override - protected CodeBlock whenTypeVar(@NonNull TypeVariable type, TypeMirror _override) { + private CodeBlock getTypeVarCodeBlock(TypeVariable type) { if (Strings.isNullOrEmpty(readerAccessor)) { throw new IllegalStateException("The compositor doesn't support open generics."); } @@ -54,27 +53,16 @@ protected CodeBlock whenTypeVar(@NonNull TypeVariable type, TypeMirror _override .build(); } - @Override - protected CodeBlock whenPrimitiveType(@NonNull PrimitiveType type, TypeMirror override) { - return getNonGenericCodeBlock(type, override); - } - - @Override - protected CodeBlock whenNonGenericType(@NonNull DeclaredType type, TypeMirror override) { - return getNonGenericCodeBlock(type, override); - } - private CodeBlock getNonGenericCodeBlock(TypeMirror type, TypeMirror override) { return CodeBlock.builder() .add("$L.resolve($T.class)", registryVarName, override != null ? override : type) .build(); } - @Override - protected CodeBlock whenGenericType(@NonNull DeclaredType type, TypeMirror override, @NonNull CodeBlock[] subtypes) { + private CodeBlock getGenericCodeBlock(TypeMirror type, TypeMirror override, CodeBlock[] subtypes) { TypeMirror resolveType; if (override != null) { - if (!context.isGeneric(override)) { + if (context.isNonGeneric(override)) { return CodeBlock.builder() .add("$L.resolve($T.class)", registryVarName, override) .build(); @@ -93,4 +81,35 @@ protected CodeBlock whenGenericType(@NonNull DeclaredType type, TypeMirror overr return builder.add(")").build(); } + + + @Override + protected CodeBlock whenTypeVar(@NonNull TypeVariable type, TypeMirror _override) { + return getTypeVarCodeBlock(type); + } + + @Override + protected CodeBlock whenPrimitiveType(@NonNull PrimitiveType type, TypeMirror override) { + return getNonGenericCodeBlock(type, override); + } + + @Override + protected CodeBlock whenNonGenericType(@NonNull DeclaredType type, TypeMirror override) { + return getNonGenericCodeBlock(type, override); + } + + @Override + protected CodeBlock whenGenericType(@NonNull DeclaredType type, TypeMirror override, @NonNull CodeBlock[] subtypes) { + return getGenericCodeBlock(type, override, subtypes); + } + + @Override + protected CodeBlock whenArrayPrimitiveType(@NonNull ArrayType type, TypeMirror override) { + return getNonGenericCodeBlock(type, override); + } + + @Override + protected CodeBlock whenArrayType(@NonNull ArrayType type, TypeMirror override, @NonNull CodeBlock subtype) { + return getGenericCodeBlock(arrayType, override, new CodeBlock[]{subtype}); + } } diff --git a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/WriterCompositor.java b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/WriterCompositor.java index 316486ce..5410e817 100644 --- a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/WriterCompositor.java +++ b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/WriterCompositor.java @@ -1,15 +1,13 @@ package com.strategyobject.substrateclient.scale.codegen.writer; import com.squareup.javapoet.CodeBlock; +import com.strategyobject.substrateclient.common.codegen.Constants; import com.strategyobject.substrateclient.common.codegen.ProcessorContext; import com.strategyobject.substrateclient.common.codegen.TypeTraverser; import lombok.NonNull; import lombok.var; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.PrimitiveType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.*; import java.util.Map; import static com.strategyobject.substrateclient.scale.codegen.ScaleProcessorHelper.SCALE_SELF_WRITABLE; @@ -20,6 +18,8 @@ public class WriterCompositor extends TypeTraverser { private final TypeMirror selfWritable; private final String writerAccessor; private final String registryVarName; + private final TypeMirror arrayType; + private WriterCompositor(ProcessorContext context, Map typeVarMap, @@ -32,6 +32,7 @@ private WriterCompositor(ProcessorContext context, this.selfWritable = context.erasure(context.getType(SCALE_SELF_WRITABLE)); this.writerAccessor = writerAccessor; this.registryVarName = registryVarName; + this.arrayType = context.erasure(context.getType(Constants.ARRAY_TYPE)); } public static WriterCompositor forAnyType(@NonNull ProcessorContext context, @@ -46,6 +47,45 @@ public static WriterCompositor disallowOpenGeneric(@NonNull ProcessorContext con return new WriterCompositor(context, null, null, registryVarName); } + private CodeBlock getNonGenericCodeBlock(TypeMirror type, TypeMirror override) { + return CodeBlock.builder() + .add("$L.resolve($T.class)", + registryVarName, + override != null ? + override : + context.isAssignable(type, selfWritable) ? + selfWritable : + type) + .build(); + } + + private CodeBlock getGenericCodeBlock(TypeMirror type, TypeMirror override, CodeBlock[] subtypes) { + TypeMirror resolveType; + if (override != null) { + if (context.isNonGeneric(override)) { + return CodeBlock.builder() + .add("$L.resolve($T.class)", registryVarName, override) + .build(); + } + + resolveType = override; + } else { + resolveType = context.erasure(type); + + if (context.isAssignable(resolveType, selfWritable)) { + return CodeBlock.builder().add("$L.resolve($T.class)", registryVarName, selfWritable).build(); + } + } + + var builder = CodeBlock.builder().add("$L.resolve($T.class).inject(", registryVarName, resolveType); + for (var i = 0; i < subtypes.length; i++) { + if (i > 0) builder.add(", "); + builder.add(subtypes[i]); + } + + return builder.add(")").build(); + } + @Override protected CodeBlock whenTypeVar(@NonNull TypeVariable type, TypeMirror _override) { return context.isAssignable(type, selfWritable) ? @@ -67,23 +107,11 @@ protected CodeBlock whenNonGenericType(@NonNull DeclaredType type, TypeMirror ov return getNonGenericCodeBlock(type, override); } - private CodeBlock getNonGenericCodeBlock(TypeMirror type, TypeMirror override) { - return CodeBlock.builder() - .add("$L.resolve($T.class)", - registryVarName, - override != null ? - override : - context.isAssignable(type, selfWritable) ? - selfWritable : - type) - .build(); - } - @Override protected CodeBlock whenGenericType(@NonNull DeclaredType type, TypeMirror override, @NonNull CodeBlock[] subtypes) { TypeMirror resolveType; if (override != null) { - if (!context.isGeneric(override)) { + if (context.isNonGeneric(override)) { return CodeBlock.builder() .add("$L.resolve($T.class)", registryVarName, override) .build(); @@ -107,6 +135,16 @@ protected CodeBlock whenGenericType(@NonNull DeclaredType type, TypeMirror overr return builder.add(")").build(); } + @Override + protected CodeBlock whenArrayPrimitiveType(@NonNull ArrayType type, TypeMirror override) { + return getNonGenericCodeBlock(type, override); + } + + @Override + protected CodeBlock whenArrayType(@NonNull ArrayType type, TypeMirror override, @NonNull CodeBlock subtype) { + return getGenericCodeBlock(arrayType, override, new CodeBlock[]{subtype}); + } + @Override protected boolean doTraverseArguments(@NonNull DeclaredType type, TypeMirror override) { return override != null || !context.isAssignable(context.erasure(type), selfWritable); diff --git a/scale/scale-codegen/src/test/java/com/strategyobject/substrateclient/scale/codegen/reader/ScaleReaderProcessorTest.java b/scale/scale-codegen/src/test/java/com/strategyobject/substrateclient/scale/codegen/reader/ScaleReaderProcessorTest.java index 24293cf5..6dad89f7 100644 --- a/scale/scale-codegen/src/test/java/com/strategyobject/substrateclient/scale/codegen/reader/ScaleReaderProcessorTest.java +++ b/scale/scale-codegen/src/test/java/com/strategyobject/substrateclient/scale/codegen/reader/ScaleReaderProcessorTest.java @@ -53,4 +53,15 @@ void compilesComplexGeneric() { assertThat(compilation).succeeded(); } + + @Test + void compilesArrays() { + val clazz = JavaFileObjects.forResource("Arrays.java"); + + val compilation = javac() + .withProcessors(new ScaleReaderProcessor()) + .compile(clazz); + + assertThat(compilation).succeeded(); + } } diff --git a/scale/scale-codegen/src/test/java/com/strategyobject/substrateclient/scale/codegen/writer/ScaleWriterProcessorTest.java b/scale/scale-codegen/src/test/java/com/strategyobject/substrateclient/scale/codegen/writer/ScaleWriterProcessorTest.java index 3bc93b7a..e05a0607 100644 --- a/scale/scale-codegen/src/test/java/com/strategyobject/substrateclient/scale/codegen/writer/ScaleWriterProcessorTest.java +++ b/scale/scale-codegen/src/test/java/com/strategyobject/substrateclient/scale/codegen/writer/ScaleWriterProcessorTest.java @@ -1,6 +1,7 @@ package com.strategyobject.substrateclient.scale.codegen.writer; import com.google.testing.compile.JavaFileObjects; +import com.strategyobject.substrateclient.scale.codegen.reader.ScaleReaderProcessor; import lombok.val; import org.junit.jupiter.api.Test; @@ -88,4 +89,15 @@ void compilesComplexGeneric() { assertThat(compilation).succeeded(); } + + @Test + void compilesArrays() { + val clazz = JavaFileObjects.forResource("Arrays.java"); + + val compilation = javac() + .withProcessors(new ScaleWriterProcessor()) + .compile(clazz); + + assertThat(compilation).succeeded(); + } } \ No newline at end of file diff --git a/scale/scale-codegen/src/test/resources/Annotated.java b/scale/scale-codegen/src/test/resources/Annotated.java index 4231671c..cf26757c 100644 --- a/scale/scale-codegen/src/test/resources/Annotated.java +++ b/scale/scale-codegen/src/test/resources/Annotated.java @@ -41,7 +41,6 @@ public class Annotated { @Scale(OptionBool.class) private Optional testOptionBool; - @ScaleGeneric( template = "Option", types = { diff --git a/scale/scale-codegen/src/test/resources/Arrays.java b/scale/scale-codegen/src/test/resources/Arrays.java new file mode 100644 index 00000000..d76a730d --- /dev/null +++ b/scale/scale-codegen/src/test/resources/Arrays.java @@ -0,0 +1,69 @@ +package com.strategyobject.substrateclient.scale; + +import com.strategyobject.substrateclient.scale.annotations.Scale; +import com.strategyobject.substrateclient.scale.annotations.ScaleGeneric; +import com.strategyobject.substrateclient.scale.annotations.ScaleReader; +import com.strategyobject.substrateclient.scale.annotations.ScaleWriter; + +import java.util.List; +import java.util.Optional; + +@ScaleReader +@ScaleWriter +public class Arrays { + private String[] stringArray; + + private Optional[] optionalArray; + + private List[] listArray; + + private T[] genericArray; + + @ScaleGeneric( + template = "Option[][]", + types = { + @Scale(ScaleType.Option[][].class), + @Scale(ScaleType.I32[].class) + }) + private Optional[][] optionalGenericArray; + + public String[] getStringArray() { + return stringArray; + } + + public void setStringArray(String[] stringArray) { + this.stringArray = stringArray; + } + + public Optional[] getOptionalArray() { + return optionalArray; + } + + public void setOptionalArray(Optional[] optionalArray) { + this.optionalArray = optionalArray; + } + + public List[] getListArray() { + return listArray; + } + + public void setListArray(List[] listArray) { + this.listArray = listArray; + } + + public T[] getGenericArray() { + return genericArray; + } + + public void setGenericArray(T[] genericArray) { + this.genericArray = genericArray; + } + + public Optional[][] getOptionalGenericArray() { + return optionalGenericArray; + } + + public void setOptionalGenericArray(Optional[][] optionalGenericArray) { + this.optionalGenericArray = optionalGenericArray; + } +} diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/ArrayReader.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/ArrayReader.java new file mode 100644 index 00000000..62ac4b8a --- /dev/null +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/ArrayReader.java @@ -0,0 +1,25 @@ +package com.strategyobject.substrateclient.scale.readers; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.scale.ScaleReader; +import lombok.NonNull; +import lombok.val; + +import java.io.IOException; +import java.io.InputStream; + +public class ArrayReader implements ScaleReader { + @Override + public Object[] read(@NonNull InputStream stream, @NonNull ScaleReader... readers) throws IOException { + Preconditions.checkArgument(readers.length == 1); + Preconditions.checkNotNull(readers[0]); + + val len = CompactIntegerReader.readInternal(stream); + val result = new Object[len]; + for (int i = 0; i < len; i++) { + result[i] = readers[0].read(stream); + } + + return result; + } +} diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/BooleanArrayReader.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/BooleanArrayReader.java new file mode 100644 index 00000000..af588962 --- /dev/null +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/BooleanArrayReader.java @@ -0,0 +1,27 @@ +package com.strategyobject.substrateclient.scale.readers; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.common.io.Streamer; +import com.strategyobject.substrateclient.scale.ScaleReader; +import lombok.NonNull; +import lombok.val; +import lombok.var; + +import java.io.IOException; +import java.io.InputStream; + +public class BooleanArrayReader implements ScaleReader { + @Override + public boolean[] read(@NonNull InputStream stream, ScaleReader... readers) throws IOException { + Preconditions.checkArgument(readers == null || readers.length == 0); + + val len = CompactIntegerReader.readInternal(stream); + val result = new boolean[len]; + val src = Streamer.readBytes(len, stream); + for (var i = 0; i < len; i++) { + result[i] = src[i] != 0; + } + + return result; + } +} diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/ByteArrayReader.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/ByteArrayReader.java new file mode 100644 index 00000000..baf11506 --- /dev/null +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/ByteArrayReader.java @@ -0,0 +1,20 @@ +package com.strategyobject.substrateclient.scale.readers; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.common.io.Streamer; +import com.strategyobject.substrateclient.scale.ScaleReader; +import lombok.NonNull; +import lombok.val; + +import java.io.IOException; +import java.io.InputStream; + +public class ByteArrayReader implements ScaleReader { + @Override + public byte[] read(@NonNull InputStream stream, ScaleReader... readers) throws IOException { + Preconditions.checkArgument(readers == null || readers.length == 0); + + val len = CompactIntegerReader.readInternal(stream); + return Streamer.readBytes(len, stream); + } +} diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/I16Reader.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/I16Reader.java index 7cb79b7b..0f6db5e4 100644 --- a/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/I16Reader.java +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/I16Reader.java @@ -17,7 +17,7 @@ public Short read(@NonNull InputStream stream, ScaleReader... readers) throws Preconditions.checkArgument(readers == null || readers.length == 0); val bytes = Streamer.readBytes(2, stream); - ByteBuffer buf = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN); + val buf = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN); buf.put(bytes); buf.flip(); return buf.getShort(); diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/I32Reader.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/I32Reader.java index d7c2b3c8..4b5dcab0 100644 --- a/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/I32Reader.java +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/I32Reader.java @@ -17,7 +17,7 @@ public Integer read(@NonNull InputStream stream, ScaleReader... readers) thro Preconditions.checkArgument(readers == null || readers.length == 0); val bytes = Streamer.readBytes(4, stream); - ByteBuffer buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); + val buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); buf.put(bytes); buf.flip(); return buf.getInt(); diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/I64Reader.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/I64Reader.java index 1aab245d..0924cef8 100644 --- a/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/I64Reader.java +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/I64Reader.java @@ -17,7 +17,7 @@ public Long read(@NonNull InputStream stream, ScaleReader... readers) throws Preconditions.checkArgument(readers == null || readers.length == 0); val bytes = Streamer.readBytes(8, stream); - ByteBuffer buf = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN); + val buf = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN); buf.put(bytes); buf.flip(); return buf.getLong(); diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/IntArrayReader.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/IntArrayReader.java new file mode 100644 index 00000000..3e717873 --- /dev/null +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/IntArrayReader.java @@ -0,0 +1,28 @@ +package com.strategyobject.substrateclient.scale.readers; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.common.io.Streamer; +import com.strategyobject.substrateclient.scale.ScaleReader; +import lombok.NonNull; +import lombok.val; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class IntArrayReader implements ScaleReader { + @Override + public int[] read(@NonNull InputStream stream, ScaleReader... readers) throws IOException { + Preconditions.checkArgument(readers == null || readers.length == 0); + + val len = CompactIntegerReader.readInternal(stream); + val result = new int[len]; + val src = Streamer.readBytes(len * 4, stream); + val buf = ByteBuffer.allocate(src.length).order(ByteOrder.LITTLE_ENDIAN); + buf.put(src); + buf.flip(); + buf.asIntBuffer().get(result); + return result; + } +} diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/LongArrayReader.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/LongArrayReader.java new file mode 100644 index 00000000..ea1a8f0b --- /dev/null +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/LongArrayReader.java @@ -0,0 +1,28 @@ +package com.strategyobject.substrateclient.scale.readers; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.common.io.Streamer; +import com.strategyobject.substrateclient.scale.ScaleReader; +import lombok.NonNull; +import lombok.val; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class LongArrayReader implements ScaleReader { + @Override + public long[] read(@NonNull InputStream stream, ScaleReader... readers) throws IOException { + Preconditions.checkArgument(readers == null || readers.length == 0); + + val len = CompactIntegerReader.readInternal(stream); + val result = new long[len]; + val src = Streamer.readBytes(len * 8, stream); + val buf = ByteBuffer.allocate(src.length).order(ByteOrder.LITTLE_ENDIAN); + buf.put(src); + buf.flip(); + buf.asLongBuffer().get(result); + return result; + } +} diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/ShortArrayReader.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/ShortArrayReader.java new file mode 100644 index 00000000..5ec48116 --- /dev/null +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/readers/ShortArrayReader.java @@ -0,0 +1,28 @@ +package com.strategyobject.substrateclient.scale.readers; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.common.io.Streamer; +import com.strategyobject.substrateclient.scale.ScaleReader; +import lombok.NonNull; +import lombok.val; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class ShortArrayReader implements ScaleReader { + @Override + public short[] read(@NonNull InputStream stream, ScaleReader... readers) throws IOException { + Preconditions.checkArgument(readers == null || readers.length == 0); + + val len = CompactIntegerReader.readInternal(stream); + val result = new short[len]; + val src = Streamer.readBytes(len * 2, stream); + val buf = ByteBuffer.allocate(src.length).order(ByteOrder.LITTLE_ENDIAN); + buf.put(src); + buf.flip(); + buf.asShortBuffer().get(result); + return result; + } +} diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/registries/ScaleReaderRegistry.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/registries/ScaleReaderRegistry.java index ea7477eb..e2d9d342 100644 --- a/scale/src/main/java/com/strategyobject/substrateclient/scale/registries/ScaleReaderRegistry.java +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/registries/ScaleReaderRegistry.java @@ -1,5 +1,6 @@ package com.strategyobject.substrateclient.scale.registries; +import com.strategyobject.substrateclient.common.CommonType; import com.strategyobject.substrateclient.common.reflection.Scanner; import com.strategyobject.substrateclient.scale.ScaleReader; import com.strategyobject.substrateclient.scale.ScaleType; @@ -26,7 +27,7 @@ public final class ScaleReaderRegistry { private final Map, ScaleReader> readers; private ScaleReaderRegistry() { - readers = new ConcurrentHashMap<>(34); + readers = new ConcurrentHashMap<>(128); register(new BoolReader(), ScaleType.Bool.class, Boolean.class, boolean.class); register(new CompactBigIntegerReader(), ScaleType.CompactBigInteger.class); @@ -58,6 +59,12 @@ private ScaleReaderRegistry() { register(new Union11Reader(), Union11.class, ScaleType.Union11.class); register(new Union12Reader(), Union12.class, ScaleType.Union12.class); register(new VecReader(), ScaleType.Vec.class, List.class); + register(new ArrayReader(), CommonType.Array.class); + register(new BooleanArrayReader(), boolean[].class); + register(new ByteArrayReader(), byte[].class); + register(new ShortArrayReader(), short[].class); + register(new IntArrayReader(), int[].class); + register(new LongArrayReader(), long[].class); register(new VoidReader(), Void.class); registerAnnotatedFrom(ROOT_PREFIX); diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/registries/ScaleWriterRegistry.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/registries/ScaleWriterRegistry.java index 0a564f05..2368863e 100644 --- a/scale/src/main/java/com/strategyobject/substrateclient/scale/registries/ScaleWriterRegistry.java +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/registries/ScaleWriterRegistry.java @@ -1,5 +1,6 @@ package com.strategyobject.substrateclient.scale.registries; +import com.strategyobject.substrateclient.common.CommonType; import com.strategyobject.substrateclient.common.reflection.Scanner; import com.strategyobject.substrateclient.scale.ScaleSelfWritable; import com.strategyobject.substrateclient.scale.ScaleType; @@ -29,7 +30,7 @@ public final class ScaleWriterRegistry { private final Map, ScaleWriter> writers; private ScaleWriterRegistry() { - writers = new ConcurrentHashMap<>(35); + writers = new ConcurrentHashMap<>(128); register(new BoolWriter(), ScaleType.Bool.class, Boolean.class, boolean.class); register(new CompactBigIntegerWriter(), ScaleType.CompactBigInteger.class); @@ -61,6 +62,12 @@ private ScaleWriterRegistry() { register(new Union11Writer(), Union11.class, ScaleType.Union11.class); register(new Union12Writer(), Union12.class, ScaleType.Union12.class); register(new VecWriter(), ScaleType.Vec.class, List.class); + register(new ArrayWriter(), CommonType.Array.class); + register(new BooleanArrayWriter(), boolean[].class); + register(new ByteArrayWriter(), byte[].class); + register(new ShortArrayWriter(), short[].class); + register(new IntArrayWriter(), int[].class); + register(new LongArrayWriter(), long[].class); register(new VoidWriter(), Void.class); register(new SelfWriter(), ScaleSelfWritable.class); register(new PublicKeyWriter(), PublicKey.class); diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/ArrayWriter.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/ArrayWriter.java new file mode 100644 index 00000000..204e4031 --- /dev/null +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/ArrayWriter.java @@ -0,0 +1,25 @@ +package com.strategyobject.substrateclient.scale.writers; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.scale.ScaleWriter; +import lombok.NonNull; +import lombok.val; + +import java.io.IOException; +import java.io.OutputStream; + +public class ArrayWriter implements ScaleWriter { + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + public void write(Object @NonNull [] value, @NonNull OutputStream stream, @NonNull ScaleWriter... writers) throws IOException { + Preconditions.checkArgument(writers != null && writers.length == 1); + Preconditions.checkNotNull(writers[0]); + + val nestedWriter = (ScaleWriter) writers[0]; + + CompactIntegerWriter.writeInternal(value.length, stream); + for (val item : value) { + nestedWriter.write(item, stream); + } + } +} diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/BooleanArrayWriter.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/BooleanArrayWriter.java new file mode 100644 index 00000000..711b4c1c --- /dev/null +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/BooleanArrayWriter.java @@ -0,0 +1,21 @@ +package com.strategyobject.substrateclient.scale.writers; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.scale.ScaleWriter; +import lombok.NonNull; +import lombok.var; + +import java.io.IOException; +import java.io.OutputStream; + +public class BooleanArrayWriter implements ScaleWriter { + @Override + public void write(boolean @NonNull [] value, @NonNull OutputStream stream, ScaleWriter... writers) throws IOException { + Preconditions.checkArgument(writers == null || writers.length == 0); + + CompactIntegerWriter.writeInternal(value.length, stream); + for (var e : value) { + stream.write(e ? 1 : 0); + } + } +} diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/ByteArrayWriter.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/ByteArrayWriter.java new file mode 100644 index 00000000..5661fafa --- /dev/null +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/ByteArrayWriter.java @@ -0,0 +1,19 @@ +package com.strategyobject.substrateclient.scale.writers; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.common.io.Streamer; +import com.strategyobject.substrateclient.scale.ScaleWriter; +import lombok.NonNull; + +import java.io.IOException; +import java.io.OutputStream; + +public class ByteArrayWriter implements ScaleWriter { + @Override + public void write(byte @NonNull [] value, @NonNull OutputStream stream, ScaleWriter... writers) throws IOException { + Preconditions.checkArgument(writers == null || writers.length == 0); + + CompactIntegerWriter.writeInternal(value.length, stream); + Streamer.writeBytes(value, stream); + } +} diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/I32Writer.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/I32Writer.java index 4c791173..024ae448 100644 --- a/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/I32Writer.java +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/I32Writer.java @@ -3,6 +3,7 @@ import com.google.common.base.Preconditions; import com.strategyobject.substrateclient.scale.ScaleWriter; import lombok.NonNull; +import lombok.val; import java.io.IOException; import java.io.OutputStream; @@ -14,7 +15,7 @@ public class I32Writer implements ScaleWriter { public void write(@NonNull Integer value, @NonNull OutputStream stream, ScaleWriter... writers) throws IOException { Preconditions.checkArgument(writers == null || writers.length == 0); - ByteBuffer buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); + val buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); buf.putInt(value); buf.flip(); stream.write(buf.array()); diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/I64Writer.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/I64Writer.java index 4d70d3b0..7bb7f516 100644 --- a/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/I64Writer.java +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/I64Writer.java @@ -3,6 +3,7 @@ import com.google.common.base.Preconditions; import com.strategyobject.substrateclient.scale.ScaleWriter; import lombok.NonNull; +import lombok.val; import java.io.IOException; import java.io.OutputStream; @@ -14,7 +15,7 @@ public class I64Writer implements ScaleWriter { public void write(@NonNull Long value, @NonNull OutputStream stream, ScaleWriter... writers) throws IOException { Preconditions.checkArgument(writers == null || writers.length == 0); - ByteBuffer buf = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN); + val buf = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN); buf.putLong(value); buf.flip(); stream.write(buf.array()); diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/IntArrayWriter.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/IntArrayWriter.java new file mode 100644 index 00000000..a5d7ffa4 --- /dev/null +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/IntArrayWriter.java @@ -0,0 +1,29 @@ +package com.strategyobject.substrateclient.scale.writers; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.common.io.Streamer; +import com.strategyobject.substrateclient.scale.ScaleWriter; +import lombok.NonNull; +import lombok.val; +import lombok.var; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class IntArrayWriter implements ScaleWriter { + @Override + public void write(int @NonNull [] value, @NonNull OutputStream stream, ScaleWriter... writers) throws IOException { + Preconditions.checkArgument(writers == null || writers.length == 0); + + CompactIntegerWriter.writeInternal(value.length, stream); + val buf = ByteBuffer.allocate(value.length * 4).order(ByteOrder.LITTLE_ENDIAN); + for (var e : value) { + buf.putInt(e); + } + + buf.flip(); + Streamer.writeBytes(buf.array(), stream); + } +} diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/LongArrayWriter.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/LongArrayWriter.java new file mode 100644 index 00000000..d20a64af --- /dev/null +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/LongArrayWriter.java @@ -0,0 +1,29 @@ +package com.strategyobject.substrateclient.scale.writers; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.common.io.Streamer; +import com.strategyobject.substrateclient.scale.ScaleWriter; +import lombok.NonNull; +import lombok.val; +import lombok.var; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class LongArrayWriter implements ScaleWriter { + @Override + public void write(long @NonNull [] value, @NonNull OutputStream stream, ScaleWriter... writers) throws IOException { + Preconditions.checkArgument(writers == null || writers.length == 0); + + CompactIntegerWriter.writeInternal(value.length, stream); + val buf = ByteBuffer.allocate(value.length * 8).order(ByteOrder.LITTLE_ENDIAN); + for (var e : value) { + buf.putLong(e); + } + + buf.flip(); + Streamer.writeBytes(buf.array(), stream); + } +} diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/ShortArrayWriter.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/ShortArrayWriter.java new file mode 100644 index 00000000..befb47d5 --- /dev/null +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/ShortArrayWriter.java @@ -0,0 +1,29 @@ +package com.strategyobject.substrateclient.scale.writers; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.common.io.Streamer; +import com.strategyobject.substrateclient.scale.ScaleWriter; +import lombok.NonNull; +import lombok.val; +import lombok.var; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class ShortArrayWriter implements ScaleWriter { + @Override + public void write(short @NonNull [] value, @NonNull OutputStream stream, ScaleWriter... writers) throws IOException { + Preconditions.checkArgument(writers == null || writers.length == 0); + + CompactIntegerWriter.writeInternal(value.length, stream); + val buf = ByteBuffer.allocate(value.length * 2).order(ByteOrder.LITTLE_ENDIAN); + for (var e : value) { + buf.putShort(e); + } + + buf.flip(); + Streamer.writeBytes(buf.array(), stream); + } +} diff --git a/scale/src/test/java/com/strategyobject/substrateclient/scale/readers/ArrayReadersTest.java b/scale/src/test/java/com/strategyobject/substrateclient/scale/readers/ArrayReadersTest.java new file mode 100644 index 00000000..978fc890 --- /dev/null +++ b/scale/src/test/java/com/strategyobject/substrateclient/scale/readers/ArrayReadersTest.java @@ -0,0 +1,125 @@ +package com.strategyobject.substrateclient.scale.readers; + +import com.strategyobject.substrateclient.common.utils.HexConverter; +import com.strategyobject.substrateclient.scale.ScaleReader; +import com.strategyobject.substrateclient.tests.TestSuite; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.val; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.function.BiConsumer; +import java.util.stream.Stream; + +public class ArrayReadersTest { + + @TestFactory + Stream read() { + val Array2DReader = new ArrayReader().inject( + new ArrayReader().inject( + new U16Reader())); + + return TestSuite.of( + Test.read(new BooleanArrayReader(), + "0x18010101000001", + new boolean[]{true, true, true, false, false, true}, + Assertions::assertArrayEquals), + Test.read(new ByteArrayReader(), + "0x1804080f10172a", + new byte[]{4, 8, 15, 16, 23, 42}, + Assertions::assertArrayEquals), + Test.read(new ShortArrayReader(), + "0x18040008000f00100017002a00", + new short[]{4, 8, 15, 16, 23, 42}, + Assertions::assertArrayEquals), + Test.read(new IntArrayReader(), + "0x1804000000080000000f00000010000000170000002a000000", + new int[]{4, 8, 15, 16, 23, 42}, + Assertions::assertArrayEquals), + Test.read(new LongArrayReader(), + "0x18040000000000000008000000000000000f00000000000000100000000000000017000000000000002a00000000000000", + new long[]{4, 8, 15, 16, 23, 42}, + Assertions::assertArrayEquals), + Test.read(new ArrayReader().inject(new U16Reader()), + "0x18040008000f00100017002a00", + new Integer[]{4, 8, 15, 16, 23, 42}, + Assertions::assertArrayEquals) + .withReaderName(ArrayReader.class.getSimpleName()), + Test.read(Array2DReader, + "0x080c040008000f000c100017002a00", + new Integer[][]{{4, 8, 15}, {16, 23, 42}}, + Assertions::assertArrayEquals) + .withReaderName("Array2DReader") + ); + } + + @TestFactory + Stream readEmpty() { + return TestSuite.of( + Test.readEmpty(new BooleanArrayReader(), new boolean[0], Assertions::assertArrayEquals), + Test.readEmpty(new ByteArrayReader(), new byte[0], Assertions::assertArrayEquals), + Test.readEmpty(new ShortArrayReader(), new short[0], Assertions::assertArrayEquals), + Test.readEmpty(new IntArrayReader(), new int[0], Assertions::assertArrayEquals), + Test.readEmpty(new LongArrayReader(), new long[0], Assertions::assertArrayEquals), + Test.readEmpty(new ArrayReader().inject(new U16Reader()), + new Integer[0], + Assertions::assertArrayEquals) + .withReaderName(ArrayReader.class.getSimpleName()) + ); + } + + @AllArgsConstructor(access = AccessLevel.PRIVATE) + static class Test extends TestSuite.TestCase { + private final ScaleReader reader; + private final String given; + private final T expected; + private final BiConsumer assertion; + private String readerName; + + @Override + public String getDisplayName() { + return String.format("Read %s with %s", given, readerName); + } + + @Override + public void execute() throws IOException { + val bytes = HexConverter.toBytes(given); + val stream = new ByteArrayInputStream(bytes); + + val actual = reader.read(stream); + assertion.accept(expected, actual); + } + + public Test withReaderName(String readerName) { + this.readerName = readerName; + return this; + } + + public static Test readEmpty(ScaleReader reader, + T expected, + BiConsumer assertion) { + return new Test<>( + reader, + "0x00", + expected, + assertion, + reader.getClass().getSimpleName()); + } + + public static Test read(ScaleReader reader, + String given, + T expected, + BiConsumer assertion) { + return new Test<>( + reader, + given, + expected, + assertion, + reader.getClass().getSimpleName()); + } + } +} \ No newline at end of file diff --git a/scale/src/test/java/com/strategyobject/substrateclient/scale/writers/ArrayWritersTest.java b/scale/src/test/java/com/strategyobject/substrateclient/scale/writers/ArrayWritersTest.java new file mode 100644 index 00000000..bb949436 --- /dev/null +++ b/scale/src/test/java/com/strategyobject/substrateclient/scale/writers/ArrayWritersTest.java @@ -0,0 +1,102 @@ +package com.strategyobject.substrateclient.scale.writers; + +import com.strategyobject.substrateclient.common.utils.HexConverter; +import com.strategyobject.substrateclient.scale.ScaleWriter; +import com.strategyobject.substrateclient.tests.TestSuite; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.val; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; + +import java.io.ByteArrayOutputStream; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ArrayWritersTest { + @TestFactory + Stream write() { + val Array2DWriter = new ArrayWriter().inject( + new ArrayWriter().inject( + new U16Writer())); + + return TestSuite.of( + Test.write(new BooleanArrayWriter(), + new boolean[]{true, true, true, false, false, true}, + "0x18010101000001"), + Test.write(new ByteArrayWriter(), + new byte[]{4, 8, 15, 16, 23, 42}, + "0x1804080f10172a"), + Test.write(new ShortArrayWriter(), + new short[]{4, 8, 15, 16, 23, 42}, + "0x18040008000f00100017002a00"), + Test.write(new IntArrayWriter(), + new int[]{4, 8, 15, 16, 23, 42}, + "0x1804000000080000000f00000010000000170000002a000000"), + Test.write(new LongArrayWriter(), + new long[]{4, 8, 15, 16, 23, 42}, + "0x18040000000000000008000000000000000f00000000000000100000000000000017000000000000002a00000000000000"), + Test.write(new ArrayWriter().inject(new U16Writer()), + new Integer[]{4, 8, 15, 16, 23, 42}, + "0x18040008000f00100017002a00") + .withWriterName(ArrayWriter.class.getSimpleName()), + Test.write(Array2DWriter, + new Integer[][]{{4, 8, 15}, {16, 23, 42}}, + "0x080c040008000f000c100017002a00") + .withWriterName("Array2DWriter") + ); + } + + @TestFactory + Stream writeEmpty() { + return TestSuite.of( + Test.writeEmpty(new BooleanArrayWriter(), new boolean[0]), + Test.writeEmpty(new ByteArrayWriter(), new byte[0]), + Test.writeEmpty(new ShortArrayWriter(), new short[0]), + Test.writeEmpty(new IntArrayWriter(), new int[0]), + Test.writeEmpty(new LongArrayWriter(), new long[0]), + Test.writeEmpty(new ArrayWriter().inject(new U16Writer()), new Integer[0]) + .withWriterName(ArrayWriter.class.getSimpleName()) + ); + } + + @AllArgsConstructor(access = AccessLevel.PRIVATE) + static class Test extends TestSuite.TestCase { + private final ScaleWriter writer; + private final T given; + private final String expected; + private String writerName; + + @Override + public String getDisplayName() { + return String.format("Write %s with %s", expected, writerName); + } + + @Override + public void execute() throws Throwable { + val stream = new ByteArrayOutputStream(); + writer.write(given, stream); + val actual = HexConverter.toHex(stream.toByteArray()); + assertEquals(expected, actual); + } + + public Test withWriterName(String writerName) { + this.writerName = writerName; + return this; + } + + public static Test writeEmpty(ScaleWriter writer, T given) { + return new Test<>(writer, given, "0x00", writer.getClass().getSimpleName()); + } + + + public static Test write(ScaleWriter writer, T given, String expected) { + return new Test<>( + writer, + given, + expected, + writer.getClass().getSimpleName()); + } + } +} diff --git a/storage/build.gradle b/storage/build.gradle index d69ffba5..703eee01 100644 --- a/storage/build.gradle +++ b/storage/build.gradle @@ -13,7 +13,7 @@ dependencies { testImplementation project(':tests') testCompileOnly project(':transport') - testImplementation 'org.testcontainers:testcontainers:1.16.3' - testImplementation 'org.testcontainers:junit-jupiter:1.16.3' - testImplementation 'ch.qos.logback:logback-classic:1.2.6' + testImplementation 'org.testcontainers:testcontainers:1.17.1' + testImplementation 'org.testcontainers:junit-jupiter:1.17.1' + testImplementation 'ch.qos.logback:logback-classic:1.2.11' } \ No newline at end of file diff --git a/tests/build.gradle b/tests/build.gradle index 18331c0e..dfab4466 100644 --- a/tests/build.gradle +++ b/tests/build.gradle @@ -1,3 +1,4 @@ dependencies { - implementation 'org.testcontainers:testcontainers:1.16.3' + implementation 'org.testcontainers:testcontainers:1.17.1' + implementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' } \ No newline at end of file diff --git a/tests/src/main/java/com/strategyobject/substrateclient/tests/TestSuite.java b/tests/src/main/java/com/strategyobject/substrateclient/tests/TestSuite.java new file mode 100644 index 00000000..7f99c498 --- /dev/null +++ b/tests/src/main/java/com/strategyobject/substrateclient/tests/TestSuite.java @@ -0,0 +1,28 @@ +package com.strategyobject.substrateclient.tests; + +import org.junit.jupiter.api.DynamicTest; + +import java.util.Arrays; +import java.util.stream.Stream; + + +public class TestSuite { + public static Stream of(TestCase... testCases) { + return Arrays.stream(testCases).map(TestCase::generate); + } + + private TestSuite() { + } + + public abstract static class TestCase { + public abstract String getDisplayName(); + + public abstract void execute() throws Throwable; + + public DynamicTest generate() { + return DynamicTest.dynamicTest(getDisplayName(), this::execute); + } + } +} + + diff --git a/transport/build.gradle b/transport/build.gradle index 0e4d40b9..a01a0c33 100644 --- a/transport/build.gradle +++ b/transport/build.gradle @@ -1,13 +1,13 @@ dependencies { implementation project(':common') - implementation 'org.java-websocket:Java-WebSocket:1.5.2' + implementation 'org.java-websocket:Java-WebSocket:1.5.3' implementation 'com.google.code.gson:gson:2.9.0' testImplementation project(':tests') testImplementation 'ch.qos.logback:logback-classic:1.2.11' - testImplementation 'org.testcontainers:testcontainers:1.16.3' - testImplementation 'org.testcontainers:junit-jupiter:1.16.3' - testImplementation "org.testcontainers:toxiproxy:1.16.3" + testImplementation 'org.testcontainers:testcontainers:1.17.1' + testImplementation 'org.testcontainers:junit-jupiter:1.17.1' + testImplementation 'org.testcontainers:toxiproxy:1.17.1' testImplementation 'org.awaitility:awaitility:4.2.0' } \ No newline at end of file