From 12dcc886e58bbd0767f44701e9f96d0d4c9bf50c Mon Sep 17 00:00:00 2001 From: Vadim Nabiev Date: Wed, 20 Oct 2021 03:00:23 +0300 Subject: [PATCH] implement rpc codec There is added code generation for RPC data transfer objects: RpcEncoderProcessor, RpcDecoderProcessor. RPC interfaces call generated encoders/decoders. Redundant mocks were removed. Proper types are annotated with RpcEncoder/RpcDecoder/ScaleWriter/ScaleReader. Generated ScaleWriters/ScaleReaders use getters/setters instead of fields. RpcCallProcessor and RpcSubscriptionProcessor are generalized using parent abstract class. Scale/ScaleGeneric annotations support parameters and methods(for return value). ProcessorContext and ProcessingException became common for all generators. Type(Read/Write)Generator(s) were renamed to (Reader/Writer)Compositor(s). ScaleReaderNotFoundException and ScaleWriterNotFoundException were removed to support recursive traversing of both types Scale and RPC in parallel and avoid visiting ahead. Auto registration is not supported more in terms were supposed. All annotated encoders/decoders/writers/readers prefixed the project name (including the company name) are still auto registrable. To auto register external classes should use the following API, e.g.: `ScaleReaderRegistry.getInstance().registerAnnotatedFrom("com.my-company.my-project")`. TypeTraverser has started to support primitives. TypeTraverser hasn't been visiting wild card types if generic is Self(Encodable/Writable). --- common/build.gradle | 2 +- .../common/codegen/AnnotationUtils.java | 18 +- .../common/codegen/JavaPoet.java | 24 +- .../common}/codegen/ProcessingException.java | 4 +- .../common/codegen/ProcessorContext.java | 79 +++++ .../common/codegen/TypeTraverser.java | 33 +- .../common/codegen/TypeUtils.java | 32 ++ .../common/reflection/Scanner.java | 26 +- .../common/utils/StringUtils.java | 4 + .../common/reflection/ScannerTest.java | 3 +- rpc/codegen/build.gradle | 5 + .../rpc/codegen/Constants.java | 34 ++- .../rpc/codegen/ProcessingException.java | 12 - .../rpc/codegen/RpcCallProcessor.java | 116 ------- .../rpc/codegen/RpcSubscriptionProcessor.java | 192 ------------ .../codegen/decoder/DecoderCompositor.java | 96 ++++++ .../decoder/RpcDecoderAnnotatedClass.java | 227 ++++++++++++++ .../codegen/decoder/RpcDecoderProcessor.java | 61 ++++ .../codegen/encoder/EncoderCompositor.java | 119 ++++++++ .../encoder/RpcEncoderAnnotatedClass.java | 225 ++++++++++++++ .../codegen/encoder/RpcEncoderProcessor.java | 61 ++++ .../CompoundRpcMethodProcessor.java | 11 +- .../rpc/codegen/sections/Constants.java | 16 + .../{ => sections}/RpcAnnotatedInterface.java | 43 +-- .../codegen/sections/RpcCallProcessor.java | 112 +++++++ .../RpcGeneratedSectionFactory.java | 16 +- .../RpcInterfaceInitializationException.java | 2 +- .../RpcInterfaceMethodProcessor.java | 9 +- ...RpcInterfaceMethodValidatingProcessor.java | 9 +- .../{ => sections}/RpcInterfaceProcessor.java | 37 +-- .../codegen/sections/RpcMethodProcessor.java | 275 +++++++++++++++++ .../sections/RpcSubscriptionProcessor.java | 208 +++++++++++++ .../decoder/RpcDecoderProcessorTests.java | 66 ++++ .../encoder/RpcEncoderProcessorTests.java | 82 +++++ .../RpcInterfaceProcessorTest.java | 2 +- .../{ => sections}/RpcSectionFactoryTest.java | 24 +- .../codegen/substitutes/TestDecodable.java | 24 ++ .../codegen/substitutes/TestEncodable.java | 39 +++ .../src/test/resources/RpcDecodable.java | 36 +++ .../test/resources/RpcDecodableInterface.java | 7 + .../resources/RpcDecodableWithoutSetter.java | 32 ++ .../src/test/resources/RpcEncodable.java | 36 +++ .../test/resources/RpcEncodableInterface.java | 7 + .../resources/RpcEncodableWithoutGetter.java | 32 ++ rpc/core/build.gradle | 7 +- .../substrateclient/rpc/core/DecoderPair.java | 28 ++ .../substrateclient/rpc/core/EncoderPair.java | 28 ++ .../rpc/core/ParameterConverter.java | 5 - .../rpc/core/PlainRpcEncoder.java | 8 - .../rpc/core/ResultConverter.java | 5 - .../substrateclient/rpc/core/RpcDecoder.java | 6 +- .../substrateclient/rpc/core/RpcEncoded.java | 11 - .../substrateclient/rpc/core/RpcEncoder.java | 6 +- .../rpc/core/RpcEncoderNotFoundException.java | 9 - .../rpc/core/RpcEncoderRegistry.java | 13 - .../rpc/core/RpcSelfEncodable.java | 12 + .../rpc/core/annotations/AutoRegister.java | 14 + .../rpc/core/annotations/Ignore.java | 11 + .../rpc/core/annotations/RpcDecoder.java | 11 + .../rpc/core/annotations/RpcEncoder.java | 11 + .../rpc/core/decoders/AbstractDecoder.java | 25 ++ .../rpc/core/decoders/BooleanDecoder.java | 10 + .../rpc/core/decoders/ByteDecoder.java | 10 + .../rpc/core/decoders/DoubleDecoder.java | 10 + .../rpc/core/decoders/FloatDecoder.java | 10 + .../rpc/core/decoders/IntDecoder.java | 10 + .../rpc/core/decoders/ListDecoder.java | 23 ++ .../rpc/core/decoders/LongDecoder.java | 10 + .../rpc/core/decoders/MapDecoder.java | 31 ++ .../rpc/core/decoders/ShortDecoder.java | 10 + .../rpc/core/decoders/StringDecoder.java | 10 + .../rpc/core/decoders/VoidDecoder.java | 14 + .../rpc/core/encoders/ListEncoder.java | 26 ++ .../rpc/core/encoders/MapEncoder.java | 31 ++ .../rpc/core/encoders/PlainEncoder.java | 14 + .../core/registries/RpcDecoderRegistry.java | 80 +++++ .../core/registries/RpcEncoderRegistry.java | 76 +++++ .../rpc/core/decoders/KnownDecoderTests.java | 60 ++++ .../rpc/core/decoders/KnownEncoderTests.java | 80 +++++ rpc/rpc-types/build.gradle | 3 + .../substrateclient/rpc/types/AccountId.java | 4 +- .../substrateclient/rpc/types/AddressId.java | 2 + .../rpc/types/AddressKindScaleWriter.java | 21 ++ .../substrateclient/rpc/types/BlockHash.java | 3 +- .../rpc/types/BlockHashScaleReader.java | 21 ++ .../substrateclient/rpc/types/Extrinsic.java | 5 +- .../rpc/types/ExtrinsicScaleWriter.java | 50 +++ .../rpc/types/ExtrinsicStatus.java | 141 ++++++++- .../rpc/types/ExtrinsicStatusRpcDecoder.java | 56 ++++ .../rpc/types/FixedBytesScaleWriter.java | 22 ++ .../substrateclient/rpc/types/Hash.java | 13 +- .../rpc/types/HashScaleReader.java | 21 ++ .../substrateclient/rpc/types/Header.java | 13 +- .../rpc/types/ImmortalEra.java | 5 + .../substrateclient/rpc/types/Metadata.java | 5 +- .../substrateclient/rpc/types/MortalEra.java | 5 + .../rpc/types/RuntimeVersion.java | 5 +- .../rpc/types/SignatureKindScaleWriter.java | 21 ++ .../rpc/types/SignaturePayload.java | 2 + .../rpc/types/SignedAdditionalExtra.java | 14 +- .../rpc/types/SignedExtra.java | 25 +- .../rpc/types/SignedPayload.java | 3 +- .../rpc/types/Sr25519Signature.java | 2 + rpc/sections/build.gradle | 1 + .../substrateclient/rpc/sections/Author.java | 10 +- .../substrateclient/rpc/sections/Chain.java | 3 + .../substrateclient/rpc/sections/State.java | 2 + .../rpc/sections/AuthorTests.java | 285 +++--------------- .../rpc/sections/ChainTests.java | 56 +--- .../rpc/sections/StateTests.java | 37 +-- .../rpc/sections/SystemTests.java | 34 +-- .../sections/substitutes/BalanceTransfer.java | 13 +- .../scale/codegen/ProcessorContext.java | 60 ---- .../scale/codegen/ScaleAnnotationParser.java | 17 +- .../scale/codegen/ScaleProcessorHelper.java | 19 ++ ...adGenerator.java => ReaderCompositor.java} | 35 ++- .../reader/ScaleReaderAnnotatedClass.java | 46 +-- .../codegen/reader/ScaleReaderProcessor.java | 33 +- .../writer/ScaleWriterAnnotatedClass.java | 38 +-- .../codegen/writer/ScaleWriterProcessor.java | 46 ++- .../codegen/writer/TypeWriteGenerator.java | 67 ---- .../codegen/writer/WriterCompositor.java | 101 +++++++ .../src/test/resources/Annotated.java | 220 ++++++++++++-- .../src/test/resources/ComplexGeneric.java | 30 +- .../resources/GenericScaleSelfWritable.java | 30 +- .../resources/MissesScaleSelfWritable.java | 10 +- .../src/test/resources/NonAnnotated.java | 120 +++++++- .../src/test/resources/WrongTemplate.java | 10 +- .../scale/ScaleSelfWritable.java | 5 +- .../substrateclient/scale/ScaleUtils.java | 36 +++ .../scale/annotations/Scale.java | 2 +- .../scale/annotations/ScaleGeneric.java | 2 +- .../ScaleReaderRegistry.java | 66 ++-- .../ScaleWriterRegistry.java | 70 ++--- .../ScaleReaderNotFoundException.java | 7 - .../ScaleWriterNotFoundException.java | 7 - .../scale/writers/PublicKeyWriter.java | 19 ++ .../scale/writers/SelfWriter.java | 7 +- .../scale/writers/SignatureDataWriter.java | 19 ++ .../transport/ProviderInterface.java | 2 +- 140 files changed, 3998 insertions(+), 1252 deletions(-) rename {scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale => common/src/main/java/com/strategyobject/substrateclient/common}/codegen/ProcessingException.java (89%) create mode 100644 common/src/main/java/com/strategyobject/substrateclient/common/codegen/ProcessorContext.java create mode 100644 common/src/main/java/com/strategyobject/substrateclient/common/codegen/TypeUtils.java delete mode 100644 rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/ProcessingException.java delete mode 100644 rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcCallProcessor.java delete mode 100644 rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcSubscriptionProcessor.java create mode 100644 rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/DecoderCompositor.java create mode 100644 rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/RpcDecoderAnnotatedClass.java create mode 100644 rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/RpcDecoderProcessor.java create mode 100644 rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/EncoderCompositor.java create mode 100644 rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/RpcEncoderAnnotatedClass.java create mode 100644 rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/RpcEncoderProcessor.java rename rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/{ => sections}/CompoundRpcMethodProcessor.java (68%) create mode 100644 rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/Constants.java rename rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/{ => sections}/RpcAnnotatedInterface.java (56%) create mode 100644 rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcCallProcessor.java rename rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/{ => sections}/RpcGeneratedSectionFactory.java (64%) rename rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/{ => sections}/RpcInterfaceInitializationException.java (71%) rename rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/{ => sections}/RpcInterfaceMethodProcessor.java (62%) rename rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/{ => sections}/RpcInterfaceMethodValidatingProcessor.java (88%) rename rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/{ => sections}/RpcInterfaceProcessor.java (69%) create mode 100644 rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcMethodProcessor.java create mode 100644 rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcSubscriptionProcessor.java create mode 100644 rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/decoder/RpcDecoderProcessorTests.java create mode 100644 rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/encoder/RpcEncoderProcessorTests.java rename rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/{ => sections}/RpcInterfaceProcessorTest.java (98%) rename rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/{ => sections}/RpcSectionFactoryTest.java (54%) create mode 100644 rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/substitutes/TestDecodable.java create mode 100644 rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/substitutes/TestEncodable.java create mode 100644 rpc/codegen/src/test/resources/RpcDecodable.java create mode 100644 rpc/codegen/src/test/resources/RpcDecodableInterface.java create mode 100644 rpc/codegen/src/test/resources/RpcDecodableWithoutSetter.java create mode 100644 rpc/codegen/src/test/resources/RpcEncodable.java create mode 100644 rpc/codegen/src/test/resources/RpcEncodableInterface.java create mode 100644 rpc/codegen/src/test/resources/RpcEncodableWithoutGetter.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/DecoderPair.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/EncoderPair.java delete mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/ParameterConverter.java delete mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/PlainRpcEncoder.java delete mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/ResultConverter.java delete mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcEncoded.java delete mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcEncoderNotFoundException.java delete mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcEncoderRegistry.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcSelfEncodable.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/AutoRegister.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/Ignore.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/RpcDecoder.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/RpcEncoder.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/AbstractDecoder.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/BooleanDecoder.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/ByteDecoder.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/DoubleDecoder.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/FloatDecoder.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/IntDecoder.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/ListDecoder.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/LongDecoder.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/MapDecoder.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/ShortDecoder.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/StringDecoder.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/VoidDecoder.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/encoders/ListEncoder.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/encoders/MapEncoder.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/encoders/PlainEncoder.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/registries/RpcDecoderRegistry.java create mode 100644 rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/registries/RpcEncoderRegistry.java create mode 100644 rpc/core/src/test/java/com/strategyobject/substrateclient/rpc/core/decoders/KnownDecoderTests.java create mode 100644 rpc/core/src/test/java/com/strategyobject/substrateclient/rpc/core/decoders/KnownEncoderTests.java create mode 100644 rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/AddressKindScaleWriter.java create mode 100644 rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/BlockHashScaleReader.java create mode 100644 rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/ExtrinsicScaleWriter.java create mode 100644 rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/ExtrinsicStatusRpcDecoder.java create mode 100644 rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/FixedBytesScaleWriter.java create mode 100644 rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/HashScaleReader.java create mode 100644 rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/SignatureKindScaleWriter.java delete mode 100644 scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/ProcessorContext.java create mode 100644 scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/ScaleProcessorHelper.java rename scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/{TypeReadGenerator.java => ReaderCompositor.java} (51%) delete mode 100644 scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/TypeWriteGenerator.java create mode 100644 scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/WriterCompositor.java create mode 100644 scale/src/main/java/com/strategyobject/substrateclient/scale/ScaleUtils.java rename scale/src/main/java/com/strategyobject/substrateclient/scale/{registry => registries}/ScaleReaderRegistry.java (71%) rename scale/src/main/java/com/strategyobject/substrateclient/scale/{registry => registries}/ScaleWriterRegistry.java (68%) delete mode 100644 scale/src/main/java/com/strategyobject/substrateclient/scale/registry/ScaleReaderNotFoundException.java delete mode 100644 scale/src/main/java/com/strategyobject/substrateclient/scale/registry/ScaleWriterNotFoundException.java create mode 100644 scale/src/main/java/com/strategyobject/substrateclient/scale/writers/PublicKeyWriter.java create mode 100644 scale/src/main/java/com/strategyobject/substrateclient/scale/writers/SignatureDataWriter.java diff --git a/common/build.gradle b/common/build.gradle index 7bd58319..0a99a26a 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -1,4 +1,4 @@ dependencies { - implementation 'org.reflections:reflections:0.9.12' + implementation 'org.reflections:reflections:0.10.1' implementation 'com.squareup:javapoet:1.13.0' } \ No newline at end of file diff --git a/common/src/main/java/com/strategyobject/substrateclient/common/codegen/AnnotationUtils.java b/common/src/main/java/com/strategyobject/substrateclient/common/codegen/AnnotationUtils.java index b91d430a..735dd9d9 100644 --- a/common/src/main/java/com/strategyobject/substrateclient/common/codegen/AnnotationUtils.java +++ b/common/src/main/java/com/strategyobject/substrateclient/common/codegen/AnnotationUtils.java @@ -1,16 +1,20 @@ package com.strategyobject.substrateclient.common.codegen; +import com.google.common.base.Preconditions; +import com.squareup.javapoet.AnnotationSpec; import lombok.NonNull; import lombok.val; import javax.lang.model.AnnotatedConstruct; import javax.lang.model.element.AnnotationMirror; import java.lang.annotation.Annotation; +import java.util.stream.Collectors; +import java.util.stream.IntStream; public final class AnnotationUtils { public static AnnotationMirror getAnnotationMirror(@NonNull AnnotatedConstruct target, @NonNull Class annotationType) { - String annotationTypeName = annotationType.getName(); + String annotationTypeName = annotationType.getCanonicalName(); for (val annotationMirror : target.getAnnotationMirrors()) { if (annotationMirror.getAnnotationType().toString().equals(annotationTypeName)) { return annotationMirror; @@ -41,6 +45,18 @@ public static T getValueFromAnnotation(@NonNull AnnotatedConstruct target, null; } + public static AnnotationSpec suppressWarnings(String... warnings) { + Preconditions.checkArgument(warnings != null && warnings.length > 0); + val format = IntStream.range(0, warnings.length) + .boxed() + .map(i -> "$S") + .collect(Collectors.joining(",", "{", "}")); + + return AnnotationSpec.builder(SuppressWarnings.class) + .addMember("value", format, (Object[]) warnings) + .build(); + } + private AnnotationUtils() { } } diff --git a/common/src/main/java/com/strategyobject/substrateclient/common/codegen/JavaPoet.java b/common/src/main/java/com/strategyobject/substrateclient/common/codegen/JavaPoet.java index a45bfb52..c90fed30 100644 --- a/common/src/main/java/com/strategyobject/substrateclient/common/codegen/JavaPoet.java +++ b/common/src/main/java/com/strategyobject/substrateclient/common/codegen/JavaPoet.java @@ -1,14 +1,15 @@ package com.strategyobject.substrateclient.common.codegen; -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.ParameterizedTypeName; -import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.WildcardTypeName; +import com.squareup.javapoet.*; import lombok.NonNull; import lombok.val; import javax.lang.model.element.TypeElement; import java.util.Collections; +import java.util.Map; +import java.util.stream.IntStream; + +import static java.util.stream.Collectors.toMap; public final class JavaPoet { public static ParameterizedTypeName parameterizeClass(@NonNull TypeElement classElement, TypeName... parameters) { @@ -29,6 +30,21 @@ public static TypeName setEachGenericParameterAsWildcard(@NonNull TypeElement cl return setEachGenericParameterAs(classElement, WildcardTypeName.subtypeOf(Object.class)); } + public static Map args(@NonNull Map map, @NonNull Object... params) { + val target = IntStream.range(0, params.length) + .boxed() + .collect(toMap(i -> String.format("p_%s", i + 1), i -> params[i])); + target.putAll(map); + + return target; + } + + public static CodeBlock named(@NonNull String format, @NonNull Map aliases) { + return CodeBlock.builder() + .addNamed(format, aliases) + .build(); + } + private JavaPoet() { } } diff --git a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/ProcessingException.java b/common/src/main/java/com/strategyobject/substrateclient/common/codegen/ProcessingException.java similarity index 89% rename from scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/ProcessingException.java rename to common/src/main/java/com/strategyobject/substrateclient/common/codegen/ProcessingException.java index 71705d6b..f81ce94b 100644 --- a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/ProcessingException.java +++ b/common/src/main/java/com/strategyobject/substrateclient/common/codegen/ProcessingException.java @@ -1,4 +1,4 @@ -package com.strategyobject.substrateclient.scale.codegen; +package com.strategyobject.substrateclient.common.codegen; import javax.lang.model.element.Element; @@ -14,4 +14,4 @@ public ProcessingException(Throwable cause, Element element, String message, Obj super(String.format(message, args), cause); this.element = element; } -} +} \ No newline at end of file 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 new file mode 100644 index 00000000..e3c35e5d --- /dev/null +++ b/common/src/main/java/com/strategyobject/substrateclient/common/codegen/ProcessorContext.java @@ -0,0 +1,79 @@ +package com.strategyobject.substrateclient.common.codegen; + +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +import javax.annotation.processing.Filer; +import javax.annotation.processing.Messager; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; +import javax.tools.Diagnostic; + +@RequiredArgsConstructor +@Getter +public class ProcessorContext { + private final @NonNull Types typeUtils; + private final @NonNull Elements elementUtils; + private final @NonNull Filer filer; + private final @NonNull Messager messager; + + public String getPackageName(@NonNull TypeElement classElement) { + return elementUtils.getPackageOf(classElement).getQualifiedName().toString(); + } + + public boolean isSubtypeOf(@NonNull TypeMirror candidate, @NonNull TypeMirror supertype) { + return typeUtils.isAssignable(candidate, supertype); + } + + public boolean isGeneric(@NonNull TypeMirror type) { + return ((TypeElement) typeUtils.asElement(type)) + .getTypeParameters() + .size() > 0; + } + + public TypeMirror erasure(@NonNull TypeMirror type) { + return typeUtils.erasure(type); + } + + public TypeMirror getBoxed(@NonNull TypeMirror type) { + return type instanceof PrimitiveType ? + typeUtils.boxedClass((PrimitiveType) type).asType() : + type; + } + + public TypeMirror getType(Class clazz) { + return elementUtils.getTypeElement(clazz.getCanonicalName()).asType(); + } + + public boolean isSameType(@NonNull TypeMirror left, @NonNull TypeMirror right) { + return typeUtils.isSameType(left, right); + } + + public void error(Exception exception) { + messager.printMessage( + Diagnostic.Kind.ERROR, + exception.getMessage() + ); + } + + public void error(Element e, Exception exception) { + messager.printMessage( + Diagnostic.Kind.ERROR, + exception.getMessage(), + e + ); + } + + public void error(Element e, String message, Object... args) { + messager.printMessage( + Diagnostic.Kind.ERROR, + String.format(message, args), + e + ); + } +} 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 21a68efb..7a1b2c51 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 @@ -5,16 +5,13 @@ import lombok.NonNull; import lombok.val; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.*; import java.lang.reflect.Array; +import java.util.Collections; import java.util.LinkedList; import java.util.List; public abstract class TypeTraverser { - private final Class clazz; public TypeTraverser(Class clazz) { @@ -23,22 +20,32 @@ public TypeTraverser(Class clazz) { protected abstract T whenTypeVar(@NonNull TypeVariable type, TypeMirror override); + protected abstract T whenPrimitiveType(@NonNull PrimitiveType type, TypeMirror override); + protected abstract T whenNonGenericType(@NonNull DeclaredType type, TypeMirror override); protected abstract T whenGenericType(@NonNull DeclaredType type, TypeMirror override, @NonNull T[] subtypes); + protected boolean doTraverseArguments(@NonNull DeclaredType type, TypeMirror override) { + return true; + } + @SuppressWarnings("unchecked") public T traverse(@NonNull TypeMirror type) { if (type.getKind() == TypeKind.TYPEVAR) { return whenTypeVar((TypeVariable) type, null); } + if (type.getKind().isPrimitive()) { + return whenPrimitiveType((PrimitiveType) type, null); + } + if (!(type instanceof DeclaredType)) { throw new IllegalArgumentException("Type is not supported: " + type); } val declaredType = (DeclaredType) type; - val typeArguments = declaredType.getTypeArguments(); + val typeArguments = getTypeArgumentsOrDefault(declaredType, null); return typeArguments.size() == 0 ? whenNonGenericType(declaredType, null) : whenGenericType( @@ -55,12 +62,16 @@ public T traverse(@NonNull TypeMirror type, @NonNull TypeTraverser.TypeTreeNode return whenTypeVar((TypeVariable) type, typeOverride.type); } + if (type.getKind().isPrimitive()) { + return whenPrimitiveType((PrimitiveType) type, typeOverride.type); + } + if (!(type instanceof DeclaredType)) { throw new IllegalArgumentException("Type is not supported: " + type); } val declaredType = (DeclaredType) type; - val typeArguments = declaredType.getTypeArguments(); + val typeArguments = getTypeArgumentsOrDefault(declaredType, typeOverride.type); val typeArgumentsSize = typeArguments.size(); val typeOverrideSize = typeOverride.children.size(); if (typeIsNonGeneric(typeArgumentsSize, typeOverrideSize)) { @@ -90,10 +101,16 @@ public T traverse(@NonNull TypeMirror type, @NonNull TypeTraverser.TypeTreeNode .toArray(x -> (T[]) Array.newInstance(clazz, typeArguments.size()))); } + private List getTypeArgumentsOrDefault(DeclaredType declaredType, TypeMirror override) { + return (doTraverseArguments(declaredType, override) ? + declaredType.getTypeArguments() : + Collections.emptyList()); + } + private boolean typeIsNonGeneric(int typeArgumentsSize, int typeOverrideSize) { if (typeArgumentsSize == 0) { if (typeOverrideSize > 0) { - throw new IllegalArgumentException("Nongeneric type cannot be overridden by generic"); + throw new IllegalArgumentException("Non generic type cannot be overridden by generic"); } return true; 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 new file mode 100644 index 00000000..f7ff2e17 --- /dev/null +++ b/common/src/main/java/com/strategyobject/substrateclient/common/codegen/TypeUtils.java @@ -0,0 +1,32 @@ +package com.strategyobject.substrateclient.common.codegen; + +import lombok.val; + +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; + +import static com.strategyobject.substrateclient.common.utils.StringUtils.capitalize; + +public class TypeUtils { + private static final String SETTER_PREFIX = "set"; + private static final String DEFAULT_GETTER_PREFIX = "get"; + private static final String BOOLEAN_GETTER_PREFIX = "is"; + + public static String getSetterName(String field) { + return SETTER_PREFIX + capitalize(field); + } + + public static String getGetterName(VariableElement field) { + val isPrimitiveBoolean = field.asType().getKind() == TypeKind.BOOLEAN; + val fieldName = field.getSimpleName().toString(); + if (isPrimitiveBoolean && fieldName.startsWith(BOOLEAN_GETTER_PREFIX)) { + return fieldName; + } + + val prefix = isPrimitiveBoolean ? + BOOLEAN_GETTER_PREFIX : + DEFAULT_GETTER_PREFIX; + + return prefix + capitalize(fieldName); + } +} diff --git a/common/src/main/java/com/strategyobject/substrateclient/common/reflection/Scanner.java b/common/src/main/java/com/strategyobject/substrateclient/common/reflection/Scanner.java index a05839dc..75fb3f90 100644 --- a/common/src/main/java/com/strategyobject/substrateclient/common/reflection/Scanner.java +++ b/common/src/main/java/com/strategyobject/substrateclient/common/reflection/Scanner.java @@ -1,21 +1,33 @@ package com.strategyobject.substrateclient.common.reflection; +import lombok.NonNull; import org.reflections.Reflections; -import org.reflections.scanners.SubTypesScanner; +import org.reflections.util.ClasspathHelper; +import org.reflections.util.ConfigurationBuilder; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Set; +import java.util.stream.Collectors; public final class Scanner { - private static final Reflections reflections; + private final Reflections reflections; - static { - reflections = new Reflections("", new SubTypesScanner()); + private Scanner(String[] prefixes) { + reflections = new Reflections( + new ConfigurationBuilder() + .setUrls( + Arrays.stream(prefixes) + .flatMap(p -> ClasspathHelper.forPackage(p).stream()) + .collect(Collectors.toCollection(ArrayList::new))) + ); } - public static Set> getSubTypesOf(Class clazz) { - return reflections.getSubTypesOf(clazz); + public static Scanner forPrefixes(@NonNull String... prefixes){ + return new Scanner(prefixes); } - private Scanner() { + public Set> getSubTypesOf(Class clazz) { + return reflections.getSubTypesOf(clazz); } } diff --git a/common/src/main/java/com/strategyobject/substrateclient/common/utils/StringUtils.java b/common/src/main/java/com/strategyobject/substrateclient/common/utils/StringUtils.java index 6d7b641a..4673604f 100644 --- a/common/src/main/java/com/strategyobject/substrateclient/common/utils/StringUtils.java +++ b/common/src/main/java/com/strategyobject/substrateclient/common/utils/StringUtils.java @@ -38,6 +38,10 @@ public static List allIndexesOfAny(@NonNull String target, @NonNull Str return result; } + public static String capitalize(@NonNull String source) { + return source.substring(0, 1).toUpperCase() + source.substring(1); + } + private StringUtils() { } } diff --git a/common/src/test/java/com/strategyobject/substrateclient/common/reflection/ScannerTest.java b/common/src/test/java/com/strategyobject/substrateclient/common/reflection/ScannerTest.java index 49c834e5..c6b8dae1 100644 --- a/common/src/test/java/com/strategyobject/substrateclient/common/reflection/ScannerTest.java +++ b/common/src/test/java/com/strategyobject/substrateclient/common/reflection/ScannerTest.java @@ -9,7 +9,8 @@ class ScannerTest { @Test void getSubTypesOf() { - val subtypes = Scanner.getSubTypesOf(TestInterface.class); + val subtypes = Scanner.forPrefixes(TestInterface.class.getPackage().getName()) + .getSubTypesOf(TestInterface.class); assertNotNull(subtypes); assertEquals(1, subtypes.size()); diff --git a/rpc/codegen/build.gradle b/rpc/codegen/build.gradle index db18ef30..e5f40859 100644 --- a/rpc/codegen/build.gradle +++ b/rpc/codegen/build.gradle @@ -1,5 +1,8 @@ dependencies { + implementation project(':common') implementation project(':rpc:core') + implementation project(':scale') + implementation project(':scale:scale-codegen') implementation project(':transport') compileOnly 'com.google.auto.service:auto-service-annotations:1.0' @@ -7,7 +10,9 @@ dependencies { implementation 'com.squareup:javapoet:1.13.0' testImplementation 'com.google.testing.compile:compile-testing:0.19' + testImplementation 'com.google.code.gson:gson:2.8.8' testCompileOnly project(':rpc:codegen') testAnnotationProcessor project(':rpc:codegen') + testAnnotationProcessor project(':scale:scale-codegen') } \ No newline at end of file diff --git a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/Constants.java b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/Constants.java index f34a63ae..2936bc62 100644 --- a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/Constants.java +++ b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/Constants.java @@ -1,6 +1,34 @@ package com.strategyobject.substrateclient.rpc.codegen; -class Constants { - static final String CLASS_NAME_TEMPLATE = "%sImpl"; - static final String RPC_METHOD_NAME_TEMPLATE = "%s_%s"; +import com.strategyobject.substrateclient.rpc.core.RpcSelfEncodable; + +public class Constants { + public static final Class RPC_SELF_ENCODABLE = RpcSelfEncodable.class; + + public static final String AUTO_REGISTER_TYPES_ARG = "types"; + public static final String PAIR_FACTORY_METHOD = "of"; + + public static final String ENCODE_METHOD_NAME = "encode"; + public static final String DECODE_METHOD_NAME = "decode"; + + public static final String TO_HEX = "toHexString"; + public static final String FROM_HEX_STRING = "fromHexString"; + + public static final String ENCODER_UNSAFE_ACCESSOR = "getEncoder()"; + public static final String ENCODER_ACCESSOR = "getEncoderOrThrow()"; + public static final String DECODER_UNSAFE_ACCESSOR = "getDecoder()"; + public static final String DECODER_ACCESSOR = "getDecoderOrThrow()"; + + public static final String WRITER_UNSAFE_ACCESSOR = "getScaleWriter()"; + public static final String WRITER_ACCESSOR = "getScaleWriterOrThrow()"; + public static final String READER_UNSAFE_ACCESSOR = "getScaleReader()"; + public static final String READER_ACCESSOR = "getScaleReaderOrThrow()"; + public static final String DECODERS_ARG = "decoders"; + public static final String ENCODERS_ARG = "encoders"; + + public static final String ENCODER_REGISTRY = "encoderRegistry"; + public static final String DECODER_REGISTRY = "decoderRegistry"; + + public static final String SCALE_WRITER_REGISTRY = "scaleWriterRegistry"; + public static final String SCALE_READER_REGISTRY = "scaleReaderRegistry"; } diff --git a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/ProcessingException.java b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/ProcessingException.java deleted file mode 100644 index 21174ad3..00000000 --- a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/ProcessingException.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.strategyobject.substrateclient.rpc.codegen; - -import javax.lang.model.element.TypeElement; - -public class ProcessingException extends Exception { - protected final TypeElement element; - - public ProcessingException(TypeElement element, String message, Object... args) { - super(String.format(message, args)); - this.element = element; - } -} diff --git a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcCallProcessor.java b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcCallProcessor.java deleted file mode 100644 index 67525bee..00000000 --- a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcCallProcessor.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.strategyobject.substrateclient.rpc.codegen; - -import com.google.common.base.Strings; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.TypeSpec; -import com.strategyobject.substrateclient.rpc.core.annotations.RpcCall; -import lombok.NonNull; -import lombok.val; - -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; -import java.util.concurrent.CompletableFuture; - -import static com.strategyobject.substrateclient.rpc.codegen.Constants.RPC_METHOD_NAME_TEMPLATE; - -class RpcCallProcessor extends RpcInterfaceMethodProcessor { - public RpcCallProcessor(@NonNull TypeElement interfaceElement) { - super(interfaceElement); - } - - @Override - void process(@NonNull String section, - @NonNull ExecutableElement method, - @NonNull Types typeUtils, - TypeSpec.@NonNull Builder typeSpecBuilder, - @NonNull Elements elementUtils) throws ProcessingException { - val callAnnotation = method.getAnnotation(RpcCall.class); - if (callAnnotation == null) { - return; - } - - ensureAnnotationIsFilled(method, callAnnotation); - - typeSpecBuilder.addMethod(createMethod(section, method, callAnnotation, typeUtils, elementUtils)); - } - - private void ensureAnnotationIsFilled(ExecutableElement method, RpcCall methodAnnotation) - throws ProcessingException { - if (Strings.isNullOrEmpty(methodAnnotation.method())) { - throw new ProcessingException( - interfaceElement, - "`@%s` of `%s.%s` contains null or empty `method`.", - methodAnnotation.getClass().getSimpleName(), - interfaceElement.getQualifiedName().toString(), - method.getSimpleName()); - } - } - - private MethodSpec createMethod(String section, - ExecutableElement method, - RpcCall methodAnnotation, - Types typeUtils, - Elements elementUtils) throws ProcessingException { - val returnType = method.getReturnType(); - ensureMethodHasAppropriateReturnType(method, typeUtils, elementUtils, returnType); - - val futureParameter = ((DeclaredType) returnType).getTypeArguments().get(0); - val isReturnVoid = typeUtils.isSameType(futureParameter, - elementUtils.getTypeElement(Void.class.getTypeName()).asType()); - - val methodSpecBuilder = MethodSpec.methodBuilder(method.getSimpleName().toString()) - .addModifiers(Modifier.PUBLIC) - .addAnnotation(Override.class) - .returns(TypeName.get(returnType)); - - String paramsArgument; - if (!method.getParameters().isEmpty()) { - methodSpecBuilder.addStatement("java.util.List params = new java.util.ArrayList()"); - - for (val param : method.getParameters()) { - val parameterName = param.getSimpleName().toString(); - methodSpecBuilder - .addParameter(TypeName.get(param.asType()), parameterName) - .addStatement("params.add(parameterConverter.convert($N))", parameterName); - } - - paramsArgument = ", params"; - } else { - paramsArgument = ""; - } - - val methodName = String.format(RPC_METHOD_NAME_TEMPLATE, section, methodAnnotation.method()); - val thenApply = isReturnVoid - ? "r -> null" - : "resultConverter::convert"; - methodSpecBuilder.addStatement( - "return providerInterface.send($S$L).thenApply($L)", - methodName, - paramsArgument, - thenApply); - - return methodSpecBuilder.build(); - } - - private void ensureMethodHasAppropriateReturnType(ExecutableElement method, - Types typeUtils, - Elements elementUtils, - TypeMirror returnType) throws ProcessingException { - val expectedReturnType = typeUtils.erasure( - elementUtils.getTypeElement(CompletableFuture.class.getTypeName()).asType()); - if (!typeUtils.isSameType(expectedReturnType, typeUtils.erasure(returnType))) { - throw new ProcessingException( - interfaceElement, - "Method `%s.%s` has unexpected return type. Must be `%s`.", - interfaceElement.getQualifiedName().toString(), - method.getSimpleName(), - CompletableFuture.class.getSimpleName()); - } - } -} diff --git a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcSubscriptionProcessor.java b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcSubscriptionProcessor.java deleted file mode 100644 index 03672b1e..00000000 --- a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcSubscriptionProcessor.java +++ /dev/null @@ -1,192 +0,0 @@ -package com.strategyobject.substrateclient.rpc.codegen; - -import com.google.common.base.Strings; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.TypeSpec; -import com.strategyobject.substrateclient.rpc.core.annotations.RpcSubscription; -import lombok.NonNull; -import lombok.val; -import lombok.var; - -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; -import java.util.concurrent.CompletableFuture; -import java.util.function.BiConsumer; -import java.util.function.Supplier; - -import static com.strategyobject.substrateclient.rpc.codegen.Constants.RPC_METHOD_NAME_TEMPLATE; - -class RpcSubscriptionProcessor extends RpcInterfaceMethodProcessor { - public RpcSubscriptionProcessor(@NonNull TypeElement interfaceElement) { - super(interfaceElement); - } - - @Override - void process(@NonNull String section, - @NonNull ExecutableElement method, - @NonNull Types typeUtils, - @NonNull TypeSpec.Builder typeSpecBuilder, - @NonNull Elements elementUtils) throws ProcessingException { - val subscriptionAnnotation = method.getAnnotation(RpcSubscription.class); - if (subscriptionAnnotation == null) { - return; - } - - ensureAnnotationIsFilled(method, subscriptionAnnotation); - typeSpecBuilder.addMethod(createMethod(section, method, subscriptionAnnotation, typeUtils, elementUtils)); - } - - private MethodSpec createMethod( - String section, - ExecutableElement method, - RpcSubscription subscriptionAnnotation, - Types typeUtils, - Elements elementUtils) throws ProcessingException { - val returnType = method.getReturnType(); - ensureMethodHasAppropriateReturnType(method, typeUtils, elementUtils, returnType); - - val methodSpecBuilder = MethodSpec.methodBuilder(method.getSimpleName().toString()) - .addModifiers(Modifier.PUBLIC) - .addAnnotation(Override.class) - .returns(TypeName.get(returnType)) - .addStatement("java.util.List params = new java.util.ArrayList()"); - - var hasCallback = false; - val callbackType = elementUtils.getTypeElement(BiConsumer.class.getTypeName()).asType(); - val exceptionType = elementUtils.getTypeElement(Exception.class.getTypeName()).asType(); - TypeMirror callbackParameter = null; - String callbackName = null; - for (val param : method.getParameters()) { - val parameterName = param.getSimpleName().toString(); - val paramType = param.asType(); - methodSpecBuilder.addParameter(TypeName.get(paramType), parameterName); - - if (typeUtils.isSameType(typeUtils.erasure(paramType), typeUtils.erasure(callbackType))) { // is callback - if (hasCallback) { - throw new ProcessingException( - interfaceElement, - "Method `%s.%s` contains more than one callback.", - interfaceElement.getQualifiedName().toString(), - method.getSimpleName()); - } - - callbackParameter = ((DeclaredType) paramType).getTypeArguments().get(1); - if (!typeUtils.isSameType(exceptionType, ((DeclaredType) paramType).getTypeArguments().get(0))) { - throw new ProcessingException( - interfaceElement, - "Method `%s.%s` contains the incorrect callback. Must be %s<%s, T>.", - interfaceElement.getQualifiedName().toString(), - method.getSimpleName(), - BiConsumer.class.getSimpleName(), - Exception.class.getSimpleName()); - } - - hasCallback = true; - callbackName = parameterName; - } else { - methodSpecBuilder.addStatement("params.add(parameterConverter.convert($N))", parameterName); - } - } - - if (!hasCallback) { - throw new ProcessingException( - interfaceElement, - "Method `%s.%s` doesn't contain a callback.", - interfaceElement.getQualifiedName().toString(), - method.getSimpleName()); - } - - val convertCall = typeUtils.isSameType(callbackParameter, - elementUtils.getTypeElement(Void.class.getTypeName()).asType()) // if callback is BiConsumer - ? "null" - : "resultConverter.convert(r)"; - - val typeName = String.format(RPC_METHOD_NAME_TEMPLATE, section, subscriptionAnnotation.type()); - val subscribeMethod = String.format(RPC_METHOD_NAME_TEMPLATE, section, subscriptionAnnotation.subscribeMethod()); - val unsubscribeMethod = String.format(RPC_METHOD_NAME_TEMPLATE, section, subscriptionAnnotation.unsubscribeMethod()); - methodSpecBuilder - .addStatement( - "$T<$T, Object> callbackProxy = (e, r) -> { $N.accept(e, $L); }", - BiConsumer.class, - Exception.class, - callbackName, - convertCall) - .addStatement( - "return providerInterface.subscribe($1S, $2S, params, callbackProxy)" + - ".thenApply(id -> () -> providerInterface.unsubscribe($1S, $3S, id))", - typeName, - subscribeMethod, - unsubscribeMethod); - - return methodSpecBuilder.build(); - } - - private void ensureAnnotationIsFilled(ExecutableElement method, RpcSubscription subscriptionAnnotation) - throws ProcessingException { - if (Strings.isNullOrEmpty(subscriptionAnnotation.type())) { - throw new ProcessingException( - interfaceElement, - "`@%s` of `%s.%s` contains null or empty `type`.", - subscriptionAnnotation.getClass().getSimpleName(), - interfaceElement.getQualifiedName().toString(), - method.getSimpleName()); - } - - if (Strings.isNullOrEmpty(subscriptionAnnotation.subscribeMethod())) { - throw new ProcessingException( - interfaceElement, - "`@%s` of `%s.%s` contains null or empty `subscribeMethod`.", - subscriptionAnnotation.getClass().getSimpleName(), - interfaceElement.getQualifiedName().toString(), - method.getSimpleName()); - } - - if (Strings.isNullOrEmpty(subscriptionAnnotation.unsubscribeMethod())) { - throw new ProcessingException( - interfaceElement, - "`@%s` of `%s.%s` contains null or empty `unsubscribeMethod`.", - subscriptionAnnotation.getClass().getSimpleName(), - interfaceElement.getQualifiedName().toString(), - method.getSimpleName()); - } - } - - private void ensureMethodHasAppropriateReturnType(ExecutableElement method, - Types typeUtils, - Elements elementUtils, - TypeMirror returnType) throws ProcessingException { - val futureType = typeUtils.erasure(elementUtils.getTypeElement(CompletableFuture.class.getTypeName()).asType()); - if (typeUtils.isSameType(futureType, typeUtils.erasure(returnType))) { // CompletableFuture<> - val supplier = typeUtils.erasure(elementUtils.getTypeElement(Supplier.class.getTypeName()).asType()); - val futureParameter = ((DeclaredType) returnType).getTypeArguments().get(0); - - if (typeUtils.isSameType(supplier, typeUtils.erasure(futureParameter))) { // CompletableFuture> - val supplierParameter = ((DeclaredType) futureParameter).getTypeArguments().get(0); - - if (typeUtils.isSameType(futureType, typeUtils.erasure(supplierParameter))) { // CompletableFuture>> - val boolType = elementUtils.getTypeElement(Boolean.class.getTypeName()).asType(); - val subFutureParameter = ((DeclaredType) supplierParameter).getTypeArguments().get(0); - - if (typeUtils.isSameType(boolType, subFutureParameter)) { // CompletableFuture>> - return; - } - } - } - } - - throw new ProcessingException( - interfaceElement, - "Method `%s.%s` has unexpected return type. Must be `%3$s<%4$s<%3$s<%5$s>>>`.", - interfaceElement.getQualifiedName().toString(), - method.getSimpleName(), - CompletableFuture.class.getSimpleName(), - Supplier.class.getSimpleName(), - Boolean.class.getSimpleName()); - } -} diff --git a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/DecoderCompositor.java b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/DecoderCompositor.java new file mode 100644 index 00000000..c5e93a7b --- /dev/null +++ b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/DecoderCompositor.java @@ -0,0 +1,96 @@ +package com.strategyobject.substrateclient.rpc.codegen.decoder; + +import com.squareup.javapoet.CodeBlock; +import com.strategyobject.substrateclient.common.codegen.TypeTraverser; +import com.strategyobject.substrateclient.rpc.core.DecoderPair; +import com.strategyobject.substrateclient.rpc.core.RpcDecoder; +import com.strategyobject.substrateclient.scale.ScaleReader; +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 java.util.Map; + +import static com.strategyobject.substrateclient.rpc.codegen.Constants.PAIR_FACTORY_METHOD; + +public class DecoderCompositor extends TypeTraverser { + private final Types typeUtils; + private final Map typeVarMap; + private final String decoderAccessor; + private final String readerAccessor; + private final String decoderRegistryVarName; + private final String scaleRegistryVarName; + + public DecoderCompositor(@NonNull Types typeUtils, + @NonNull Map typeVarMap, + @NonNull String decoderAccessor, + @NonNull String readerAccessor, + @NonNull String decoderRegistryVarName, + @NonNull String scaleRegistryVarName) { + super(CodeBlock.class); + this.typeUtils = typeUtils; + this.typeVarMap = typeVarMap; + this.decoderAccessor = decoderAccessor; + this.readerAccessor = readerAccessor; + this.decoderRegistryVarName = decoderRegistryVarName; + this.scaleRegistryVarName = scaleRegistryVarName; + } + + @Override + protected CodeBlock whenTypeVar(@NonNull TypeVariable type, TypeMirror _override) { + return CodeBlock.builder() + .add("$T.$L(($T) ", DecoderPair.class, PAIR_FACTORY_METHOD, RpcDecoder.class) + .add(decoderAccessor, typeVarMap.get(type.toString())) + .add(", ($T) ", ScaleReader.class) + .add(readerAccessor, 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); + } + + private CodeBlock getNonGenericCodeBlock(TypeMirror type) { + return CodeBlock.builder() + .add("$T.$L(", DecoderPair.class, PAIR_FACTORY_METHOD) + .add("($T) $L.resolve($T.class)", RpcDecoder.class, decoderRegistryVarName, type) + .add(", ") + .add("($T) $L.resolve($T.class)", ScaleReader.class, scaleRegistryVarName, type) + .add(")") + .build(); + } + + @Override + protected CodeBlock whenGenericType(@NonNull DeclaredType type, TypeMirror _override, @NonNull CodeBlock[] subtypes) { + TypeMirror resolveType = typeUtils.erasure(type); + + var builder = CodeBlock.builder() + .add("$T.$L(", DecoderPair.class, PAIR_FACTORY_METHOD) + .add("($T) $L.resolve($T.class).inject(", RpcDecoder.class, decoderRegistryVarName, resolveType); + for (var i = 0; i < subtypes.length; i++) { + if (i > 0) builder.add(", "); + builder.add(subtypes[i]); + } + + builder.add("), ($T) $L.resolve($T.class).inject(", ScaleReader.class, scaleRegistryVarName, resolveType); + for (var i = 0; i < subtypes.length; i++) { + if (i > 0) builder.add(", "); + builder.add(subtypes[i]); + } + builder.add(")"); + + return builder + .add(")").build(); + } +} diff --git a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/RpcDecoderAnnotatedClass.java b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/RpcDecoderAnnotatedClass.java new file mode 100644 index 00000000..94054e51 --- /dev/null +++ b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/RpcDecoderAnnotatedClass.java @@ -0,0 +1,227 @@ +package com.strategyobject.substrateclient.rpc.codegen.decoder; + +import com.squareup.javapoet.*; +import com.strategyobject.substrateclient.common.codegen.JavaPoet; +import com.strategyobject.substrateclient.common.codegen.ProcessingException; +import com.strategyobject.substrateclient.common.codegen.ProcessorContext; +import com.strategyobject.substrateclient.rpc.core.DecoderPair; +import com.strategyobject.substrateclient.rpc.core.RpcDecoder; +import com.strategyobject.substrateclient.rpc.core.annotations.AutoRegister; +import com.strategyobject.substrateclient.rpc.core.annotations.Ignore; +import com.strategyobject.substrateclient.rpc.core.registries.RpcDecoderRegistry; +import com.strategyobject.substrateclient.scale.ScaleReader; +import com.strategyobject.substrateclient.scale.ScaleUtils; +import com.strategyobject.substrateclient.scale.annotations.Scale; +import com.strategyobject.substrateclient.scale.annotations.ScaleGeneric; +import com.strategyobject.substrateclient.scale.codegen.ScaleAnnotationParser; +import com.strategyobject.substrateclient.scale.codegen.reader.ReaderCompositor; +import com.strategyobject.substrateclient.scale.registries.ScaleReaderRegistry; +import lombok.NonNull; +import lombok.val; +import lombok.var; + +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeVariable; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static com.strategyobject.substrateclient.common.codegen.AnnotationUtils.suppressWarnings; +import static com.strategyobject.substrateclient.common.codegen.TypeUtils.getSetterName; +import static com.strategyobject.substrateclient.rpc.codegen.Constants.*; +import static java.util.stream.Collectors.toMap; + +public class RpcDecoderAnnotatedClass { + private final static String DECODER_NAME_TEMPLATE = "%sDecoder"; + private static final String VALUE_ARG = "value"; + private static final String RESULT_VAR = "result"; + private static final String MAP_VAR = "sourceMap"; + + private final TypeElement classElement; + private final Map typeVarMap; + private final List fields; + + public RpcDecoderAnnotatedClass(@NonNull TypeElement classElement) { + this.classElement = classElement; + val typeParameters = classElement.getTypeParameters(); + this.typeVarMap = IntStream.range(0, typeParameters.size()) + .boxed() + .collect(toMap(i -> typeParameters.get(i).toString(), Function.identity())); + this.fields = classElement.getEnclosedElements().stream() + .filter(e -> e.getKind() == ElementKind.FIELD) + .map(e -> (VariableElement) e) + .collect(Collectors.toList()); + } + + public void generateDecoder(@NonNull ProcessorContext context) throws ProcessingException, IOException { + val decoderName = String.format(DECODER_NAME_TEMPLATE, classElement.getSimpleName().toString()); + val classWildcardTyped = JavaPoet.setEachGenericParameterAsWildcard(classElement); + + val typeSpecBuilder = TypeSpec.classBuilder(decoderName) + .addAnnotation(AnnotationSpec.builder(AutoRegister.class) + .addMember(AUTO_REGISTER_TYPES_ARG, "{$L.class}", classElement.getQualifiedName().toString()) + .build()) + .addModifiers(Modifier.PUBLIC) + .addSuperinterface(ParameterizedTypeName.get(ClassName.get(RpcDecoder.class), classWildcardTyped)) + .addMethod(generateDecodeMethod(context, classWildcardTyped)); + + JavaFile.builder( + context.getPackageName(classElement), + typeSpecBuilder.build() + ).build().writeTo(context.getFiler()); + } + + private MethodSpec generateDecodeMethod(ProcessorContext context, TypeName classWildcardTyped) throws ProcessingException { + val methodSpec = MethodSpec.methodBuilder(DECODE_METHOD_NAME) + .addAnnotation(Override.class) + .addAnnotation(suppressWarnings("unchecked")) + .addModifiers(Modifier.PUBLIC) + .returns(classWildcardTyped) + .addParameter(Object.class, VALUE_ARG) + .addParameter(ArrayTypeName.of( + ParameterizedTypeName.get( + ClassName.get(DecoderPair.class), + WildcardTypeName.subtypeOf(Object.class))), + DECODERS_ARG) + .varargs(true); + + addValidationRules(methodSpec); + addMethodBody(methodSpec, context); + + return methodSpec.build(); + } + + private void addMethodBody(MethodSpec.Builder methodSpec, ProcessorContext context) throws ProcessingException { + val resultType = JavaPoet.setEachGenericParameterAs(classElement, TypeName.OBJECT); + methodSpec + .addStatement("if ($L == null) { return null; }", VALUE_ARG) + .addStatement("$1T $2L = $1T.getInstance()", RpcDecoderRegistry.class, DECODER_REGISTRY) + .addStatement("$1T $2L = $1T.getInstance()", ScaleReaderRegistry.class, SCALE_READER_REGISTRY) + .addStatement("$1T<$2T, ?> $3L = ($1T<$2T, ?>)$4L", Map.class, String.class, MAP_VAR, VALUE_ARG) + .addStatement("$1T $2L = new $1T()", resultType, RESULT_VAR) + .beginControlFlow("try"); + + setFields(methodSpec, context); + + methodSpec + .nextControlFlow("catch ($T e)", Exception.class) + .addStatement("throw new $T(e)", RuntimeException.class) + .endControlFlow() + .addStatement("return $L", RESULT_VAR); + } + + private void setFields(MethodSpec.Builder methodSpec, ProcessorContext context) throws ProcessingException { + val decoderCompositor = new DecoderCompositor(context.getTypeUtils(), + typeVarMap, + String.format("%s[$L].%s", DECODERS_ARG, DECODER_UNSAFE_ACCESSOR), + String.format("%s[$L].%s", DECODERS_ARG, READER_UNSAFE_ACCESSOR), + DECODER_REGISTRY, + SCALE_READER_REGISTRY); + val scaleAnnotationParser = new ScaleAnnotationParser(context); + val scaleReaderCompositor = new ReaderCompositor(context, + typeVarMap, + String.format("%s[$L].%s", DECODERS_ARG, READER_ACCESSOR), + SCALE_READER_REGISTRY); + + for (VariableElement field : fields) { + if (field.getAnnotation(Ignore.class) != null) { + continue; + } + + if (field.getAnnotation(Scale.class) != null || field.getAnnotation(ScaleGeneric.class) != null) { + setScaleField(methodSpec, context, field, scaleAnnotationParser, scaleReaderCompositor); + } else { + setField(methodSpec, field, context, decoderCompositor); + } + } + } + + private void setScaleField(MethodSpec.Builder methodSpec, + ProcessorContext context, + VariableElement field, + ScaleAnnotationParser scaleAnnotationParser, + ReaderCompositor readerCompositor) throws ProcessingException { + try { + val fieldType = field.asType(); + val code = CodeBlock.builder(); + if (fieldType instanceof TypeVariable) { + code.add("$L.$L(", RESULT_VAR, getSetterName(field.getSimpleName().toString())); + } else { + code.add("$L.$L(($T)", + RESULT_VAR, + getSetterName(field.getSimpleName().toString()), + context.getBoxed(context.erasure(fieldType))); + } + + val typeOverride = scaleAnnotationParser.parse(field); + val readerCode = typeOverride != null ? + readerCompositor.traverse(fieldType, typeOverride) : + readerCompositor.traverse(fieldType); + methodSpec + .addStatement(code + .add("$T.$L(", + ScaleUtils.class, + FROM_HEX_STRING) + .add("($T)$L.get($S), ", + String.class, + MAP_VAR, + field) + .add("($T)", ScaleReader.class) + .add(readerCode) + .add("))") + .build()); + } catch (Exception e) { + throw new ProcessingException(e, field, e.getMessage()); + } + } + + private void setField(MethodSpec.Builder methodSpec, + VariableElement field, + ProcessorContext context, + DecoderCompositor decoderCompositor) throws ProcessingException { + try { + val fieldType = field.asType(); + val code = CodeBlock.builder(); + if (fieldType instanceof TypeVariable) { + code.add("$L.$L((", RESULT_VAR, getSetterName(field.getSimpleName().toString())); + } else { + code.add("$L.$L(($T)(", + RESULT_VAR, + getSetterName(field.getSimpleName().toString()), + context.getBoxed(context.erasure(fieldType))); + } + + val decoderCode = decoderCompositor.traverse(fieldType); + methodSpec.addStatement(code + .add(decoderCode) + .add(".$L", DECODER_ACCESSOR) + .add(".$L($L.get($S))))", + DECODE_METHOD_NAME, + MAP_VAR, + field) + .build()); + } catch (Exception e) { + throw new ProcessingException(e, field, e.getMessage()); + } + } + + private void addValidationRules(MethodSpec.Builder methodSpec) { + val classTypeParametersSize = classElement.getTypeParameters().size(); + if (classTypeParametersSize == 0) { + methodSpec.addStatement("if ($1L != null && $1L.length > 0) throw new $2T()", DECODERS_ARG, IllegalArgumentException.class); + } else { + methodSpec + .addStatement("if ($1L == null) throw new $2T(\"$1L is null\")", DECODERS_ARG, IllegalArgumentException.class) + .addStatement("if ($L.length != $L) throw new $T()", DECODERS_ARG, classTypeParametersSize, IllegalArgumentException.class); + for (var i = 0; i < classTypeParametersSize; i++) { + methodSpec.addStatement("if ($L[$L] == null) throw new $T()", DECODERS_ARG, i, NullPointerException.class); + } + } + } +} diff --git a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/RpcDecoderProcessor.java b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/RpcDecoderProcessor.java new file mode 100644 index 00000000..24156fb6 --- /dev/null +++ b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/RpcDecoderProcessor.java @@ -0,0 +1,61 @@ +package com.strategyobject.substrateclient.rpc.codegen.decoder; + +import com.google.auto.service.AutoService; +import com.strategyobject.substrateclient.common.codegen.ProcessingException; +import com.strategyobject.substrateclient.common.codegen.ProcessorContext; +import com.strategyobject.substrateclient.rpc.core.annotations.RpcDecoder; +import lombok.val; + +import javax.annotation.processing.*; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import java.io.IOException; +import java.util.Set; + +@SupportedAnnotationTypes("com.strategyobject.substrateclient.rpc.core.annotations.RpcDecoder") +@SupportedSourceVersion(SourceVersion.RELEASE_8) +@AutoService(Processor.class) +public class RpcDecoderProcessor extends AbstractProcessor { + private ProcessorContext context; + + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + super.init(processingEnv); + context = new ProcessorContext(processingEnv.getTypeUtils(), + processingEnv.getElementUtils(), + processingEnv.getFiler(), + processingEnv.getMessager()); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + if (annotations.isEmpty()) { + return false; + } + + for (val annotatedElement : roundEnv.getElementsAnnotatedWith(RpcDecoder.class)) { + if (annotatedElement.getKind() != ElementKind.CLASS) { + context.error( + annotatedElement, + "Only classes can be annotated with `@%s`.", + RpcDecoder.class.getSimpleName()); + + return true; + } + + val typeElement = (TypeElement) annotatedElement; + try { + new RpcDecoderAnnotatedClass(typeElement).generateDecoder(context); + } catch (ProcessingException e) { + context.error(typeElement, e); + return true; + } catch (IOException e) { + context.error(e); + return true; + } + } + + return false; + } +} diff --git a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/EncoderCompositor.java b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/EncoderCompositor.java new file mode 100644 index 00000000..a06bab2d --- /dev/null +++ b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/EncoderCompositor.java @@ -0,0 +1,119 @@ +package com.strategyobject.substrateclient.rpc.codegen.encoder; + +import com.squareup.javapoet.CodeBlock; +import com.strategyobject.substrateclient.common.codegen.ProcessorContext; +import com.strategyobject.substrateclient.common.codegen.TypeTraverser; +import com.strategyobject.substrateclient.rpc.core.EncoderPair; +import com.strategyobject.substrateclient.rpc.core.RpcEncoder; +import com.strategyobject.substrateclient.scale.ScaleWriter; +import lombok.NonNull; +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 java.util.Map; + +import static com.strategyobject.substrateclient.rpc.codegen.Constants.PAIR_FACTORY_METHOD; +import static com.strategyobject.substrateclient.rpc.codegen.Constants.RPC_SELF_ENCODABLE; + +public class EncoderCompositor extends TypeTraverser { + private final ProcessorContext context; + private final Map typeVarMap; + private final TypeMirror selfEncodable; + private final String encoderAccessor; + private final String writerAccessor; + private final String encoderRegistryVarName; + private final String scaleRegistryVarName; + + public EncoderCompositor(@NonNull ProcessorContext context, + @NonNull Map typeVarMap, + @NonNull String encoderAccessor, + @NonNull String writerAccessor, + @NonNull String encoderRegistryVarName, + @NonNull String scaleRegistryVarName) { + super(CodeBlock.class); + this.context = context; + this.typeVarMap = typeVarMap; + this.selfEncodable = context.erasure(context.getType(RPC_SELF_ENCODABLE)); + this.encoderAccessor = encoderAccessor; + this.writerAccessor = writerAccessor; + 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.isSubtypeOf(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); + } + + private CodeBlock getNonGenericCodeBlock(TypeMirror type) { + return CodeBlock.builder() + .add("$T.$L(($T) ", EncoderPair.class, PAIR_FACTORY_METHOD, RpcEncoder.class) + .add("$L.resolve($T.class)", + encoderRegistryVarName, + context.isSubtypeOf(type, selfEncodable) ? + selfEncodable : + type) + .add(", ($T) ", ScaleWriter.class) + .add("$L.resolve($T.class)", scaleRegistryVarName, type) + .add(")") + .build(); + } + + @Override + protected CodeBlock whenGenericType(@NonNull DeclaredType type, TypeMirror _override, @NonNull CodeBlock[] subtypes) { + TypeMirror resolveType = context.erasure(type); + val builder = CodeBlock.builder() + .add("$T.$L(($T) ", EncoderPair.class, PAIR_FACTORY_METHOD, RpcEncoder.class); + + if (context.isSubtypeOf(resolveType, selfEncodable)) { + builder.add("registry.resolve($T.class)", selfEncodable); + } else { + builder.add("$L.resolve($T.class).inject(", encoderRegistryVarName, resolveType); + for (var i = 0; i < subtypes.length; i++) { + if (i > 0) builder.add(", "); + builder.add(subtypes[i]); + } + builder.add(")"); + } + + builder.add("), ($T) $L.resolve($T.class).inject(", ScaleWriter.class, scaleRegistryVarName, resolveType); + for (var i = 0; i < subtypes.length; i++) { + if (i > 0) builder.add(", "); + builder.add(subtypes[i]); + } + builder.add(")"); + + return builder.add(")").build(); + } + + @Override + protected boolean doTraverseArguments(@NonNull DeclaredType type, TypeMirror override) { + return override != null || !context.isSubtypeOf(context.erasure(type), selfEncodable); + } +} diff --git a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/RpcEncoderAnnotatedClass.java b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/RpcEncoderAnnotatedClass.java new file mode 100644 index 00000000..218a3e4c --- /dev/null +++ b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/RpcEncoderAnnotatedClass.java @@ -0,0 +1,225 @@ +package com.strategyobject.substrateclient.rpc.codegen.encoder; + +import com.squareup.javapoet.*; +import com.strategyobject.substrateclient.common.codegen.JavaPoet; +import com.strategyobject.substrateclient.common.codegen.ProcessingException; +import com.strategyobject.substrateclient.common.codegen.ProcessorContext; +import com.strategyobject.substrateclient.rpc.core.EncoderPair; +import com.strategyobject.substrateclient.rpc.core.RpcEncoder; +import com.strategyobject.substrateclient.rpc.core.annotations.AutoRegister; +import com.strategyobject.substrateclient.rpc.core.annotations.Ignore; +import com.strategyobject.substrateclient.rpc.core.registries.RpcEncoderRegistry; +import com.strategyobject.substrateclient.scale.ScaleUtils; +import com.strategyobject.substrateclient.scale.ScaleWriter; +import com.strategyobject.substrateclient.scale.annotations.Scale; +import com.strategyobject.substrateclient.scale.annotations.ScaleGeneric; +import com.strategyobject.substrateclient.scale.codegen.ScaleAnnotationParser; +import com.strategyobject.substrateclient.scale.codegen.writer.WriterCompositor; +import com.strategyobject.substrateclient.scale.registries.ScaleWriterRegistry; +import lombok.NonNull; +import lombok.val; +import lombok.var; + +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static com.strategyobject.substrateclient.common.codegen.AnnotationUtils.suppressWarnings; +import static com.strategyobject.substrateclient.common.codegen.TypeUtils.getGetterName; +import static com.strategyobject.substrateclient.rpc.codegen.Constants.*; +import static java.util.stream.Collectors.toMap; + +public class RpcEncoderAnnotatedClass { + private static final String ENCODER_NAME_TEMPLATE = "%sEncoder"; + private static final String SOURCE_ARG = "source"; + private static final String RESULT_VAR = "result"; + private final TypeElement classElement; + private final Map typeVarMap; + private final List fields; + + public RpcEncoderAnnotatedClass(@NonNull TypeElement classElement) { + this.classElement = classElement; + val typeParameters = classElement.getTypeParameters(); + this.typeVarMap = IntStream.range(0, typeParameters.size()) + .boxed() + .collect(toMap(i -> typeParameters.get(i).toString(), Function.identity())); + fields = classElement.getEnclosedElements().stream() + .filter(e -> e.getKind() == ElementKind.FIELD) + .map(e -> (VariableElement) e) + .collect(Collectors.toList()); + } + + public void generateEncoder(@NonNull ProcessorContext context) throws ProcessingException, IOException { + val encoder = String.format(ENCODER_NAME_TEMPLATE, classElement.getSimpleName().toString()); + val classWildcardTyped = JavaPoet.setEachGenericParameterAsWildcard(classElement); + + val typeSpecBuilder = TypeSpec.classBuilder(encoder) + .addAnnotation(AnnotationSpec.builder(AutoRegister.class) + .addMember(AUTO_REGISTER_TYPES_ARG, "{$L.class}", classElement.getQualifiedName().toString()) + .build()) + .addModifiers(Modifier.PUBLIC) + .addSuperinterface(ParameterizedTypeName.get(ClassName.get(RpcEncoder.class), classWildcardTyped)) + .addMethod(generateEncodeMethod(context, classWildcardTyped)); + + JavaFile.builder( + context.getPackageName(classElement), + typeSpecBuilder.build() + ).build().writeTo(context.getFiler()); + } + + private MethodSpec generateEncodeMethod(ProcessorContext context, TypeName classWildcardTyped) throws ProcessingException { + val methodSpec = MethodSpec.methodBuilder(ENCODE_METHOD_NAME) + .addAnnotation(Override.class) + .addAnnotation(suppressWarnings("unchecked")) + .addModifiers(Modifier.PUBLIC) + .returns(Object.class) + .addParameter(classWildcardTyped, SOURCE_ARG) + .addParameter(ArrayTypeName.of( + ParameterizedTypeName.get( + ClassName.get(EncoderPair.class), + WildcardTypeName.subtypeOf(Object.class))), + ENCODERS_ARG) + .varargs(true); + + addValidationRules(methodSpec, context); + addMethodBody(methodSpec, context); + + return methodSpec.build(); + } + + private void addMethodBody(MethodSpec.Builder methodSpec, ProcessorContext context) throws ProcessingException { + methodSpec + .addStatement("if ($L == null) { return null; }", SOURCE_ARG) + .addStatement("$1T $2L = $1T.getInstance()", RpcEncoderRegistry.class, ENCODER_REGISTRY) + .addStatement("$1T $2L = $1T.getInstance()", ScaleWriterRegistry.class, SCALE_WRITER_REGISTRY) + .addStatement("$1T<$2T, $3T> $4L = new $1T<>()", HashMap.class, String.class, Object.class, RESULT_VAR) + .beginControlFlow("try"); + + setFields(methodSpec, context); + + methodSpec + .nextControlFlow("catch ($T e)", Exception.class) + .addStatement("throw new $T(e)", RuntimeException.class) + .endControlFlow() + .addStatement("return $L", RESULT_VAR); + } + + private void setFields(MethodSpec.Builder methodSpec, ProcessorContext context) throws ProcessingException { + val encoderCompositor = new EncoderCompositor(context, + typeVarMap, + String.format("%s[$L].%s", ENCODERS_ARG, ENCODER_UNSAFE_ACCESSOR), + String.format("%s[$L].%s", ENCODERS_ARG, WRITER_UNSAFE_ACCESSOR), + ENCODER_REGISTRY, + SCALE_WRITER_REGISTRY); + val scaleAnnotationParser = new ScaleAnnotationParser(context); + val scaleWriterCompositor = new WriterCompositor(context, + typeVarMap, + String.format("%s[$L].%s", ENCODERS_ARG, WRITER_ACCESSOR), + SCALE_WRITER_REGISTRY); + + for (VariableElement field : fields) { + if (field.getAnnotation(Ignore.class) != null) { + continue; + } + + if (field.getAnnotation(Scale.class) != null || field.getAnnotation(ScaleGeneric.class) != null) { + setScaleField(methodSpec, field, scaleAnnotationParser, scaleWriterCompositor); + } else { + setField(methodSpec, field, context, encoderCompositor); + } + } + } + + private void setField(MethodSpec.Builder methodSpec, + VariableElement field, + ProcessorContext context, + EncoderCompositor encoderCompositor) throws ProcessingException { + try { + val fieldType = field.asType(); + val encoderCode = encoderCompositor.traverse(fieldType); + + methodSpec.addStatement(CodeBlock.builder() + .add("$1T $2L = ($1T)(($3T)(", getNonVariableType(fieldType, context), field, RpcEncoder.class) + .add(encoderCode) + .add(".$L", ENCODER_ACCESSOR) + .add(")).$L($L.$L())", ENCODE_METHOD_NAME, SOURCE_ARG, getGetterName(field)) + .build()); + if (fieldType instanceof PrimitiveType) { + methodSpec.addStatement("$1L.put($2S, $2L)", RESULT_VAR, field); + } else { + methodSpec.addStatement("if ($1L != null) $2L.put($1S, $1L)", field, RESULT_VAR); + } + } catch (Exception e) { + throw new ProcessingException(e, field, e.getMessage()); + } + } + + private TypeMirror getNonVariableType(TypeMirror fieldType, ProcessorContext context) { + if (fieldType instanceof TypeVariable) { + return context.getType(Object.class); + } + + return context.getBoxed(context.erasure(fieldType)); + } + + private void setScaleField(MethodSpec.Builder methodSpec, + VariableElement field, + ScaleAnnotationParser scaleAnnotationParser, + WriterCompositor writerCompositor) throws ProcessingException { + try { + val fieldType = field.asType(); + val typeOverride = scaleAnnotationParser.parse(field); + val writerCode = typeOverride != null ? + writerCompositor.traverse(fieldType, typeOverride) : + writerCompositor.traverse(fieldType); + + val getter = getGetterName(field); + + val code = CodeBlock.builder() + .add("$T $L = $T.$L($L.$L(), ($T)", String.class, field, ScaleUtils.class, TO_HEX, SOURCE_ARG, getter, ScaleWriter.class) + .add(writerCode) + .add(")") + .build(); + + if (fieldType instanceof PrimitiveType) { + methodSpec + .addStatement(code) + .addStatement("$1L.put($2S, $2L)", RESULT_VAR, field); + } else { + methodSpec + .beginControlFlow("if ($L.$L() != null)", SOURCE_ARG, getter) + .addStatement(code) + .addStatement("$1L.put($2S, $2L)", RESULT_VAR, field) + .endControlFlow(); + } + } catch (Exception e) { + throw new ProcessingException(e, field, e.getMessage()); + } + + } + + private void addValidationRules(MethodSpec.Builder methodSpec, ProcessorContext context) { + val classTypeParametersSize = classElement.getTypeParameters().size(); + if (classTypeParametersSize == 0 || context.isSubtypeOf(classElement.asType(), context.erasure(context.getType(RPC_SELF_ENCODABLE)))) { + methodSpec.addStatement("if ($1L != null && $1L.length > 0) throw new $2T()", ENCODERS_ARG, IllegalArgumentException.class); + } else { + methodSpec + .addStatement("if ($1L == null) throw new $2T(\"$1L is null\")", ENCODERS_ARG, IllegalArgumentException.class) + .addStatement("if ($L.length != $L) throw new $T()", ENCODERS_ARG, classTypeParametersSize, IllegalArgumentException.class); + for (var i = 0; i < classTypeParametersSize; i++) { + methodSpec.addStatement("if ($L[$L] == null) throw new $T()", ENCODERS_ARG, i, NullPointerException.class); + } + } + } +} diff --git a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/RpcEncoderProcessor.java b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/RpcEncoderProcessor.java new file mode 100644 index 00000000..374bbd4d --- /dev/null +++ b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/RpcEncoderProcessor.java @@ -0,0 +1,61 @@ +package com.strategyobject.substrateclient.rpc.codegen.encoder; + +import com.google.auto.service.AutoService; +import com.strategyobject.substrateclient.common.codegen.ProcessingException; +import com.strategyobject.substrateclient.common.codegen.ProcessorContext; +import com.strategyobject.substrateclient.rpc.core.annotations.RpcEncoder; +import lombok.val; + +import javax.annotation.processing.*; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import java.io.IOException; +import java.util.Set; + +@SupportedAnnotationTypes("com.strategyobject.substrateclient.rpc.core.annotations.RpcEncoder") +@SupportedSourceVersion(SourceVersion.RELEASE_8) +@AutoService(Processor.class) +public class RpcEncoderProcessor extends AbstractProcessor { + private ProcessorContext context; + + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + super.init(processingEnv); + context = new ProcessorContext(processingEnv.getTypeUtils(), + processingEnv.getElementUtils(), + processingEnv.getFiler(), + processingEnv.getMessager()); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + if (annotations.isEmpty()) { + return false; + } + + for (val annotatedElement : roundEnv.getElementsAnnotatedWith(RpcEncoder.class)) { + if (annotatedElement.getKind() != ElementKind.CLASS) { + context.error( + annotatedElement, + "Only classes can be annotated with `@%s`.", + RpcEncoder.class.getSimpleName()); + + return true; + } + + val typeElement = (TypeElement) annotatedElement; + try { + new RpcEncoderAnnotatedClass(typeElement).generateEncoder(context); + } catch (ProcessingException e) { + context.error(typeElement, e); + return true; + } catch (IOException e) { + context.error(e); + return true; + } + } + + return false; + } +} diff --git a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/CompoundRpcMethodProcessor.java b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/CompoundRpcMethodProcessor.java similarity index 68% rename from rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/CompoundRpcMethodProcessor.java rename to rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/CompoundRpcMethodProcessor.java index 36db5a74..8b11f3b2 100644 --- a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/CompoundRpcMethodProcessor.java +++ b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/CompoundRpcMethodProcessor.java @@ -1,13 +1,13 @@ -package com.strategyobject.substrateclient.rpc.codegen; +package com.strategyobject.substrateclient.rpc.codegen.sections; import com.squareup.javapoet.TypeSpec; +import com.strategyobject.substrateclient.common.codegen.ProcessingException; +import com.strategyobject.substrateclient.common.codegen.ProcessorContext; import lombok.NonNull; import lombok.var; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; import java.util.List; class CompoundRpcMethodProcessor extends RpcInterfaceMethodProcessor { @@ -22,11 +22,10 @@ public CompoundRpcMethodProcessor(@NonNull TypeElement interfaceElement, @Override void process(@NonNull String section, @NonNull ExecutableElement method, - @NonNull Types typeUtils, @NonNull TypeSpec.Builder typeSpecBuilder, - @NonNull Elements elementUtils) throws ProcessingException { + @NonNull ProcessorContext context) throws ProcessingException { for (var processor : processors) { - processor.process(section, method, typeUtils, typeSpecBuilder, elementUtils); + processor.process(section, method, typeSpecBuilder, context); } } } diff --git a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/Constants.java b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/Constants.java new file mode 100644 index 00000000..8ef72408 --- /dev/null +++ b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/Constants.java @@ -0,0 +1,16 @@ +package com.strategyobject.substrateclient.rpc.codegen.sections; + +import java.util.HashMap; +import java.util.Map; + +class Constants { + static final String CLASS_NAME_TEMPLATE = "%sImpl"; + static final String RPC_METHOD_NAME_TEMPLATE = "%s_%s"; + static final Map EMPTY_TYPE_VAR_MAP = new HashMap<>(); + static final String PROVIDER_INTERFACE = "providerInterface"; + static final String SEND = "send"; + static final String SUBSCRIBE = "subscribe"; + public static final String UNSUBSCRIBE = "unsubscribe"; + static final String THEN_APPLY = "thenApply"; + static final String ACCEPT = "accept"; +} diff --git a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcAnnotatedInterface.java b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcAnnotatedInterface.java similarity index 56% rename from rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcAnnotatedInterface.java rename to rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcAnnotatedInterface.java index f1cd65dd..20bd7ad5 100644 --- a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcAnnotatedInterface.java +++ b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcAnnotatedInterface.java @@ -1,26 +1,24 @@ -package com.strategyobject.substrateclient.rpc.codegen; +package com.strategyobject.substrateclient.rpc.codegen.sections; import com.google.common.base.Strings; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; +import com.strategyobject.substrateclient.common.codegen.ProcessingException; +import com.strategyobject.substrateclient.common.codegen.ProcessorContext; import com.strategyobject.substrateclient.rpc.core.annotations.RpcInterface; -import com.strategyobject.substrateclient.rpc.core.ParameterConverter; -import com.strategyobject.substrateclient.rpc.core.ResultConverter; import com.strategyobject.substrateclient.transport.ProviderInterface; import lombok.NonNull; import lombok.val; -import javax.annotation.processing.Filer; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; import java.io.IOException; -import static com.strategyobject.substrateclient.rpc.codegen.Constants.CLASS_NAME_TEMPLATE; +import static com.strategyobject.substrateclient.rpc.codegen.sections.Constants.CLASS_NAME_TEMPLATE; +import static com.strategyobject.substrateclient.rpc.codegen.sections.Constants.PROVIDER_INTERFACE; public class RpcAnnotatedInterface { private final TypeElement interfaceElement; @@ -50,46 +48,33 @@ public RpcAnnotatedInterface(@NonNull TypeElement interfaceElement, this.methodProcessor = methodProcessor; } - public void generateClass(@NonNull Types typeUtils, @NonNull Elements elementUtils, @NonNull Filer filer) + public void generateClass(@NonNull ProcessorContext context) throws ProcessingException, IOException { val interfaceName = interfaceElement.getSimpleName().toString(); val className = String.format(CLASS_NAME_TEMPLATE, interfaceName); - val pkg = elementUtils.getPackageOf(interfaceElement); - val packageName = pkg.getQualifiedName().toString(); + val packageName = context.getPackageName(interfaceElement); val typeSpecBuilder = TypeSpec.classBuilder(className) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addSuperinterface(TypeName.get(interfaceElement.asType())) - .addField(ProviderInterface.class, "providerInterface") - .addField(ParameterConverter.class, "parameterConverter") - .addField(ResultConverter.class, "resultConverter") + .addField(ProviderInterface.class, PROVIDER_INTERFACE) .addMethod(createConstructor()); for (val method : interfaceElement.getEnclosedElements()) { - methodProcessor.process(section, (ExecutableElement) method, typeUtils, typeSpecBuilder, elementUtils); + methodProcessor.process(section, (ExecutableElement) method, typeSpecBuilder, context); } - JavaFile.builder(packageName, typeSpecBuilder.build()).build().writeTo(filer); + JavaFile.builder(packageName, typeSpecBuilder.build()).build().writeTo(context.getFiler()); } private MethodSpec createConstructor() { return MethodSpec.constructorBuilder() .addModifiers(Modifier.PUBLIC) - .addParameter(ProviderInterface.class, "providerInterface") - .addParameter(ParameterConverter.class, "parameterConverter") - .addParameter(ResultConverter.class, "resultConverter") - .beginControlFlow("if (providerInterface == null)") - .addStatement("throw new $T(\"ProviderInterface can't be null.\")", IllegalArgumentException.class) + .addParameter(ProviderInterface.class, PROVIDER_INTERFACE) + .beginControlFlow("if ($L == null)", PROVIDER_INTERFACE) + .addStatement("throw new $T(\"$L can't be null.\")", IllegalArgumentException.class, PROVIDER_INTERFACE) .endControlFlow() - .beginControlFlow("if (parameterConverter == null)") - .addStatement("throw new $T(\"ParameterConverter can't be null.\")", IllegalArgumentException.class) - .endControlFlow() - .beginControlFlow("if (resultConverter == null)") - .addStatement("throw new $T(\"ResultConverter can't be null.\")", IllegalArgumentException.class) - .endControlFlow() - .addStatement("this.providerInterface = providerInterface") - .addStatement("this.parameterConverter = parameterConverter") - .addStatement("this.resultConverter = resultConverter") + .addStatement("this.$1L = $1L", PROVIDER_INTERFACE) .build(); } } diff --git a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcCallProcessor.java b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcCallProcessor.java new file mode 100644 index 00000000..10cc9eaa --- /dev/null +++ b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcCallProcessor.java @@ -0,0 +1,112 @@ +package com.strategyobject.substrateclient.rpc.codegen.sections; + +import com.google.common.base.Strings; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.MethodSpec; +import com.strategyobject.substrateclient.common.codegen.ProcessingException; +import com.strategyobject.substrateclient.common.codegen.ProcessorContext; +import com.strategyobject.substrateclient.rpc.core.annotations.RpcCall; +import lombok.NonNull; +import lombok.val; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiFunction; + +import static com.strategyobject.substrateclient.rpc.codegen.sections.Constants.*; + +class RpcCallProcessor extends RpcMethodProcessor { + private static final String CALL_BACK_ARG = "r"; + + public RpcCallProcessor(@NonNull TypeElement interfaceElement) { + super(RpcCall.class, interfaceElement); + } + + @Override + protected void ensureAnnotationIsFilled(ExecutableElement method, RpcCall methodAnnotation) + throws ProcessingException { + if (Strings.isNullOrEmpty(methodAnnotation.method())) { + throw new ProcessingException( + interfaceElement, + "`@%s` of `%s.%s` contains null or empty `method`.", + methodAnnotation.getClass().getSimpleName(), + interfaceElement.getQualifiedName().toString(), + method.getSimpleName()); + } + } + + @Override + protected void onProcessStarting(ProcessorContext context) { + + } + + @Override + protected void callProviderInterface(MethodSpec.Builder methodSpecBuilder, + ExecutableElement method, + String section, + RpcCall annotation, + ProcessorContext context, + BiFunction decoder) { + val rpcMethodName = String.format(RPC_METHOD_NAME_TEMPLATE, section, annotation.method()); + val paramsArgument = method.getParameters().size() == 0 ? "" : String.format(", %s", PARAMS_VAR); + + val isReturnVoid = isReturnVoid(method, context); + val code = CodeBlock.builder() + .add("return $L.$L($S$L).$L(", PROVIDER_INTERFACE, SEND, rpcMethodName, paramsArgument, THEN_APPLY); + + if (isReturnVoid) { + code.add("$L -> null", CALL_BACK_ARG); + } else { + code + .add("$L -> ", CALL_BACK_ARG) + .add(decoder.apply(getFutureParameter(method), CALL_BACK_ARG)); + } + code.add(")"); + + methodSpecBuilder.addStatement(code.build()); + } + + @Override + protected boolean useDecodeRegistries(ExecutableElement method, ProcessorContext context) { + return !isReturnVoid(method, context); + } + + @Override + protected boolean shouldBePassedToProvider(ExecutableElement method, VariableElement param, ProcessorContext context) { + return true; + } + + @Override + protected void onParametersVisited(ExecutableElement method) { + } + + @Override + protected void ensureMethodHasAppropriateReturnType(ExecutableElement method, + TypeMirror returnType, + ProcessorContext context) throws ProcessingException { + val expectedReturnType = context.erasure( + context.getType(CompletableFuture.class)); + if (!context.isSameType(expectedReturnType, context.erasure(returnType))) { + throw new ProcessingException( + interfaceElement, + "Method `%s.%s` has unexpected return type. Must be `%s`.", + interfaceElement.getQualifiedName().toString(), + method.getSimpleName(), + CompletableFuture.class.getSimpleName()); + } + } + + private boolean isReturnVoid(ExecutableElement method, ProcessorContext context) { + val futureParameter = getFutureParameter(method); + + return context.isSameType(futureParameter, context.getType(Void.class)); + } + + private TypeMirror getFutureParameter(ExecutableElement method) { + return ((DeclaredType) method.getReturnType()).getTypeArguments().get(0); + } +} diff --git a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcGeneratedSectionFactory.java b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcGeneratedSectionFactory.java similarity index 64% rename from rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcGeneratedSectionFactory.java rename to rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcGeneratedSectionFactory.java index 497724e6..73b3e4a4 100644 --- a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcGeneratedSectionFactory.java +++ b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcGeneratedSectionFactory.java @@ -1,21 +1,17 @@ -package com.strategyobject.substrateclient.rpc.codegen; +package com.strategyobject.substrateclient.rpc.codegen.sections; import com.strategyobject.substrateclient.rpc.core.annotations.RpcInterface; -import com.strategyobject.substrateclient.rpc.core.ParameterConverter; -import com.strategyobject.substrateclient.rpc.core.ResultConverter; import com.strategyobject.substrateclient.transport.ProviderInterface; import lombok.NonNull; import lombok.val; import java.lang.reflect.InvocationTargetException; -import static com.strategyobject.substrateclient.rpc.codegen.Constants.CLASS_NAME_TEMPLATE; +import static com.strategyobject.substrateclient.rpc.codegen.sections.Constants.CLASS_NAME_TEMPLATE; public class RpcGeneratedSectionFactory { public T create(@NonNull Class interfaceClass, - @NonNull ProviderInterface provider, - @NonNull ParameterConverter parameterConverter, - @NonNull ResultConverter resultConverter) throws RpcInterfaceInitializationException { + @NonNull ProviderInterface provider) throws RpcInterfaceInitializationException { if (interfaceClass.getDeclaredAnnotationsByType(RpcInterface.class).length == 0) { throw new IllegalArgumentException( String.format("`%s` can't be constructed because isn't annotated with `@%s`.", @@ -25,10 +21,10 @@ public T create(@NonNull Class interfaceClass, Class clazz; try { - clazz = Class.forName(String.format(CLASS_NAME_TEMPLATE, interfaceClass.getName())); - val ctor = clazz.getConstructor(ProviderInterface.class, ParameterConverter.class, ResultConverter.class); + clazz = Class.forName(String.format(CLASS_NAME_TEMPLATE, interfaceClass.getCanonicalName())); + val ctor = clazz.getConstructor(ProviderInterface.class); - return interfaceClass.cast(ctor.newInstance(provider, parameterConverter, resultConverter)); + return interfaceClass.cast(ctor.newInstance(provider)); } catch (ClassCastException | ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RpcInterfaceInitializationException(e); } diff --git a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcInterfaceInitializationException.java b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcInterfaceInitializationException.java similarity index 71% rename from rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcInterfaceInitializationException.java rename to rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcInterfaceInitializationException.java index aab062dd..90871791 100644 --- a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcInterfaceInitializationException.java +++ b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcInterfaceInitializationException.java @@ -1,4 +1,4 @@ -package com.strategyobject.substrateclient.rpc.codegen; +package com.strategyobject.substrateclient.rpc.codegen.sections; public class RpcInterfaceInitializationException extends Exception { public RpcInterfaceInitializationException(Throwable ex) { diff --git a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcInterfaceMethodProcessor.java b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcInterfaceMethodProcessor.java similarity index 62% rename from rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcInterfaceMethodProcessor.java rename to rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcInterfaceMethodProcessor.java index 6b098862..e3f9565f 100644 --- a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcInterfaceMethodProcessor.java +++ b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcInterfaceMethodProcessor.java @@ -1,13 +1,13 @@ -package com.strategyobject.substrateclient.rpc.codegen; +package com.strategyobject.substrateclient.rpc.codegen.sections; import com.squareup.javapoet.TypeSpec; +import com.strategyobject.substrateclient.common.codegen.ProcessingException; +import com.strategyobject.substrateclient.common.codegen.ProcessorContext; import lombok.NonNull; import lombok.RequiredArgsConstructor; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; @RequiredArgsConstructor abstract class RpcInterfaceMethodProcessor { @@ -16,7 +16,6 @@ abstract class RpcInterfaceMethodProcessor { abstract void process(@NonNull String section, @NonNull ExecutableElement method, - @NonNull Types typeUtils, @NonNull TypeSpec.Builder typeSpecBuilder, - @NonNull Elements elementUtils) throws ProcessingException; + @NonNull ProcessorContext context) throws ProcessingException; } diff --git a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcInterfaceMethodValidatingProcessor.java b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcInterfaceMethodValidatingProcessor.java similarity index 88% rename from rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcInterfaceMethodValidatingProcessor.java rename to rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcInterfaceMethodValidatingProcessor.java index 72d6ddac..d97e9b6d 100644 --- a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcInterfaceMethodValidatingProcessor.java +++ b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcInterfaceMethodValidatingProcessor.java @@ -1,6 +1,8 @@ -package com.strategyobject.substrateclient.rpc.codegen; +package com.strategyobject.substrateclient.rpc.codegen.sections; import com.squareup.javapoet.TypeSpec; +import com.strategyobject.substrateclient.common.codegen.ProcessingException; +import com.strategyobject.substrateclient.common.codegen.ProcessorContext; import com.strategyobject.substrateclient.rpc.core.annotations.RpcCall; import com.strategyobject.substrateclient.rpc.core.annotations.RpcSubscription; import lombok.NonNull; @@ -10,8 +12,6 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; class RpcInterfaceMethodValidatingProcessor extends RpcInterfaceMethodProcessor { public RpcInterfaceMethodValidatingProcessor(@NonNull TypeElement interfaceElement) { @@ -21,9 +21,8 @@ public RpcInterfaceMethodValidatingProcessor(@NonNull TypeElement interfaceEleme @Override void process(@NonNull String section, @NonNull ExecutableElement method, - @NonNull Types typeUtils, TypeSpec.@NonNull Builder typeSpecBuilder, - @NonNull Elements elementUtils) throws ProcessingException { + @NonNull ProcessorContext context) throws ProcessingException { ensureMethodIsNotAbstractOrProperlyAnnotated(method); } diff --git a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcInterfaceProcessor.java b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcInterfaceProcessor.java similarity index 69% rename from rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcInterfaceProcessor.java rename to rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcInterfaceProcessor.java index 48c27212..29e86679 100644 --- a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/RpcInterfaceProcessor.java +++ b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcInterfaceProcessor.java @@ -1,17 +1,15 @@ -package com.strategyobject.substrateclient.rpc.codegen; +package com.strategyobject.substrateclient.rpc.codegen.sections; import com.google.auto.service.AutoService; +import com.strategyobject.substrateclient.common.codegen.ProcessingException; +import com.strategyobject.substrateclient.common.codegen.ProcessorContext; import com.strategyobject.substrateclient.rpc.core.annotations.RpcInterface; import lombok.val; import javax.annotation.processing.*; import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; -import javax.tools.Diagnostic; import java.io.IOException; import java.util.Arrays; import java.util.Set; @@ -20,18 +18,15 @@ @SupportedSourceVersion(SourceVersion.RELEASE_8) @AutoService(Processor.class) public class RpcInterfaceProcessor extends AbstractProcessor { - private Types typeUtils; - private Elements elementUtils; - private Filer filer; - private Messager messager; + private ProcessorContext context; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); - typeUtils = processingEnv.getTypeUtils(); - elementUtils = processingEnv.getElementUtils(); - filer = processingEnv.getFiler(); - messager = processingEnv.getMessager(); + context = new ProcessorContext(processingEnv.getTypeUtils(), + processingEnv.getElementUtils(), + processingEnv.getFiler(), + processingEnv.getMessager()); } @Override @@ -42,7 +37,7 @@ public boolean process(Set annotations, RoundEnvironment for (val annotatedElement : roundEnv.getElementsAnnotatedWith(RpcInterface.class)) { if (annotatedElement.getKind() != ElementKind.INTERFACE) { - error( + context.error( annotatedElement, "Only interfaces can be annotated with `@%s`.", RpcInterface.class.getSimpleName()); @@ -62,24 +57,16 @@ public boolean process(Set annotations, RoundEnvironment new RpcSubscriptionProcessor(typeElement) ))); - annotatedInterface.generateClass(typeUtils, elementUtils, filer); + annotatedInterface.generateClass(context); } catch (ProcessingException e) { - error(typeElement, e.getMessage()); + context.error(typeElement, e); return true; } catch (IOException e) { - error(null, e.getMessage()); + context.error(e); return true; } } return true; } - - private void error(Element e, String message, Object... args) { - messager.printMessage( - Diagnostic.Kind.ERROR, - String.format(message, args), - e - ); - } } diff --git a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcMethodProcessor.java b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcMethodProcessor.java new file mode 100644 index 00000000..fabfcd76 --- /dev/null +++ b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcMethodProcessor.java @@ -0,0 +1,275 @@ +package com.strategyobject.substrateclient.rpc.codegen.sections; + +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import com.strategyobject.substrateclient.common.codegen.ProcessingException; +import com.strategyobject.substrateclient.common.codegen.ProcessorContext; +import com.strategyobject.substrateclient.rpc.codegen.decoder.DecoderCompositor; +import com.strategyobject.substrateclient.rpc.codegen.encoder.EncoderCompositor; +import com.strategyobject.substrateclient.rpc.core.RpcDecoder; +import com.strategyobject.substrateclient.rpc.core.registries.RpcDecoderRegistry; +import com.strategyobject.substrateclient.rpc.core.registries.RpcEncoderRegistry; +import com.strategyobject.substrateclient.scale.ScaleReader; +import com.strategyobject.substrateclient.scale.ScaleUtils; +import com.strategyobject.substrateclient.scale.ScaleWriter; +import com.strategyobject.substrateclient.scale.annotations.Scale; +import com.strategyobject.substrateclient.scale.annotations.ScaleGeneric; +import com.strategyobject.substrateclient.scale.codegen.ScaleAnnotationParser; +import com.strategyobject.substrateclient.scale.codegen.reader.ReaderCompositor; +import com.strategyobject.substrateclient.scale.codegen.writer.WriterCompositor; +import com.strategyobject.substrateclient.scale.registries.ScaleReaderRegistry; +import com.strategyobject.substrateclient.scale.registries.ScaleWriterRegistry; +import lombok.NonNull; +import lombok.val; + +import javax.lang.model.AnnotatedConstruct; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; + +import static com.strategyobject.substrateclient.common.codegen.AnnotationUtils.suppressWarnings; +import static com.strategyobject.substrateclient.rpc.codegen.Constants.*; +import static com.strategyobject.substrateclient.rpc.codegen.sections.Constants.EMPTY_TYPE_VAR_MAP; + +public abstract class RpcMethodProcessor extends RpcInterfaceMethodProcessor { + protected static final String PARAMS_VAR = "params"; + + private final Class processingAnnotation; + + protected RpcMethodProcessor(@NonNull Class clazz, @NonNull TypeElement interfaceElement) { + super(interfaceElement); + + processingAnnotation = clazz; + } + + @Override + void process(@NonNull String section, + @NonNull ExecutableElement method, + TypeSpec.@NonNull Builder typeSpecBuilder, + @NonNull ProcessorContext context) throws ProcessingException { + onProcessStarting(context); + + val annotation = method.getAnnotation(processingAnnotation); + if (annotation == null) { + return; + } + + ensureAnnotationIsFilled(method, annotation); + typeSpecBuilder.addMethod(createMethod(section, method, annotation, context)); + } + + protected abstract void onProcessStarting(ProcessorContext context); + + private MethodSpec createMethod(String section, ExecutableElement method, A annotation, ProcessorContext context) throws ProcessingException { + val returnType = method.getReturnType(); + ensureMethodHasAppropriateReturnType(method, returnType, context); + + val methodSpecBuilder = MethodSpec.methodBuilder(method.getSimpleName().toString()) + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .addAnnotation(suppressWarnings("unchecked")) + .returns(TypeName.get(returnType)); + + val scaleAnnotationParser = new ScaleAnnotationParser(context); + + processParameters(methodSpecBuilder, method, scaleAnnotationParser, context); + + sendRequest(methodSpecBuilder, method, section, annotation, scaleAnnotationParser, context); + + return methodSpecBuilder.build(); + } + + private void sendRequest(MethodSpec.Builder methodSpecBuilder, + ExecutableElement method, + String section, + A annotation, + ScaleAnnotationParser scaleAnnotationParser, + ProcessorContext context) { + if (useDecodeRegistries(method, context)) { + declareDecoderAndReaderRegistries(methodSpecBuilder); + } + + callProviderInterface(methodSpecBuilder, + method, + section, + annotation, + context, + (resultType, arg) -> getDecodeCodeBlock(method, resultType, arg, scaleAnnotationParser, context)); + } + + protected abstract void callProviderInterface(MethodSpec.Builder methodSpecBuilder, + ExecutableElement method, + String section, + A annotation, + ProcessorContext context, + BiFunction decoder); + + private CodeBlock getDecodeCodeBlock(AnnotatedConstruct annotated, + TypeMirror resultType, + String arg, + ScaleAnnotationParser scaleAnnotationParser, + ProcessorContext context) { + return annotated.getAnnotation(Scale.class) != null || annotated.getAnnotation(ScaleGeneric.class) != null ? + getScaleReadCodeBlock(annotated, resultType, arg, scaleAnnotationParser, context) : + getRpcDecodeCodeBlock(resultType, arg, context); + } + + private CodeBlock getRpcDecodeCodeBlock(TypeMirror resultType, String arg, ProcessorContext context) { + val decoderCompositor = new DecoderCompositor(context.getTypeUtils(), + EMPTY_TYPE_VAR_MAP, + String.format("%s[$L].%s", DECODERS_ARG, DECODER_UNSAFE_ACCESSOR), + String.format("%s[$L].%s", DECODERS_ARG, READER_UNSAFE_ACCESSOR), + DECODER_REGISTRY, + SCALE_READER_REGISTRY); + + val decodeCode = decoderCompositor.traverse(resultType); + + return CodeBlock.builder() + .add("($T) (($T)", resultType, RpcDecoder.class) + .add(decodeCode) + .add(".$L)", DECODER_ACCESSOR) + .add(".$L($L)", DECODE_METHOD_NAME, arg) + .build(); + } + + private CodeBlock getScaleReadCodeBlock(AnnotatedConstruct annotated, + TypeMirror resultType, + String arg, + ScaleAnnotationParser scaleAnnotationParser, + ProcessorContext context) { + val readerCompositor = new ReaderCompositor(context, + EMPTY_TYPE_VAR_MAP, + String.format("%s[$L].%s", DECODERS_ARG, READER_ACCESSOR), + SCALE_READER_REGISTRY); + val typeOverride = scaleAnnotationParser.parse(annotated); + val readerCode = typeOverride != null ? + readerCompositor.traverse(resultType, typeOverride) : + readerCompositor.traverse(resultType); + + return CodeBlock.builder() + .add("($T) (", resultType) + .add("$T.$L(", ScaleUtils.class, FROM_HEX_STRING) + .add("($T) $L, ($T) ", String.class, arg, ScaleReader.class) + .add(readerCode) + .add("))") + .build(); + } + + protected abstract boolean useDecodeRegistries(ExecutableElement method, ProcessorContext context); + + private void processParameters(MethodSpec.Builder methodSpecBuilder, + ExecutableElement method, + ScaleAnnotationParser scaleAnnotationParser, + ProcessorContext context) throws ProcessingException { + if (method.getParameters().isEmpty()) { + onParametersVisited(method); + + return; + } + + val writerCompositor = new WriterCompositor(context, + EMPTY_TYPE_VAR_MAP, + String.format("%s[$L].%s", ENCODERS_ARG, WRITER_ACCESSOR), + SCALE_WRITER_REGISTRY); + val encoderCompositor = new EncoderCompositor( + context, + EMPTY_TYPE_VAR_MAP, + String.format("%s[$L].%s", ENCODERS_ARG, ENCODER_UNSAFE_ACCESSOR), + String.format("%s[$L].%s", ENCODERS_ARG, WRITER_UNSAFE_ACCESSOR), + ENCODER_REGISTRY, + SCALE_WRITER_REGISTRY); + + methodSpecBuilder + .addStatement("$1T<$2T> $3L = new $4T<$2T>()", + List.class, + Object.class, + PARAMS_VAR, + ArrayList.class) + .addStatement("$1T $2L = $1T.getInstance()", RpcEncoderRegistry.class, ENCODER_REGISTRY) + .addStatement("$1T $2L = $1T.getInstance()", ScaleWriterRegistry.class, SCALE_WRITER_REGISTRY); + + for (val param : method.getParameters()) { + try { + processParameter(methodSpecBuilder, method, param, encoderCompositor, writerCompositor, scaleAnnotationParser, context); + } catch (Exception e){ + throw new ProcessingException(e, param, e.getMessage()); + } + } + + onParametersVisited(method); + } + + private void processParameter(MethodSpec.Builder methodSpecBuilder, + ExecutableElement method, + VariableElement param, + EncoderCompositor encoderCompositor, + WriterCompositor writerCompositor, + ScaleAnnotationParser scaleAnnotationParser, + ProcessorContext context) throws ProcessingException { + methodSpecBuilder + .addParameter(TypeName.get(param.asType()), param.getSimpleName().toString()); + + if (shouldBePassedToProvider(method, param, context)) { + CodeBlock encodeBlock; + if (param.getAnnotation(Scale.class) != null || param.getAnnotation(ScaleGeneric.class) != null) { + encodeBlock = getScaleWriteCodeBlock(param, scaleAnnotationParser, writerCompositor); + } else { + encodeBlock = getRpcEncodeCodeBlock(param, encoderCompositor); + } + + methodSpecBuilder + .addStatement(encodeBlock); + } + } + + private CodeBlock getScaleWriteCodeBlock(VariableElement param, + ScaleAnnotationParser scaleAnnotationParser, + WriterCompositor writerCompositor) { + val type = param.asType(); + val typeOverride = scaleAnnotationParser.parse(param); + val writerCode = typeOverride != null ? + writerCompositor.traverse(type, typeOverride) : + writerCompositor.traverse(type); + + return CodeBlock.builder() + .add("params.add(") + .add("$T.$L($L, ($T)", ScaleUtils.class, TO_HEX, param, ScaleWriter.class) + .add(writerCode) + .add("))") + .build(); + } + + private CodeBlock getRpcEncodeCodeBlock(VariableElement param, + EncoderCompositor encoderCompositor) { + val encoderCode = encoderCompositor.traverse(param.asType()); + + return CodeBlock.builder() + .add("params.add(") + .add(encoderCode) + .add(".$L", ENCODER_ACCESSOR) + .add(".$L($L))", ENCODE_METHOD_NAME, param) + .build(); + } + + protected abstract boolean shouldBePassedToProvider(ExecutableElement method, VariableElement param, ProcessorContext context) throws ProcessingException; + + protected abstract void onParametersVisited(ExecutableElement method) throws ProcessingException; + + private void declareDecoderAndReaderRegistries(MethodSpec.Builder methodSpecBuilder) { + methodSpecBuilder + .addStatement("$1T $2L = $1T.getInstance()", RpcDecoderRegistry.class, DECODER_REGISTRY) + .addStatement("$1T $2L = $1T.getInstance()", ScaleReaderRegistry.class, SCALE_READER_REGISTRY); + } + + protected abstract void ensureMethodHasAppropriateReturnType(ExecutableElement method, TypeMirror returnType, ProcessorContext context) throws ProcessingException; + + protected abstract void ensureAnnotationIsFilled(ExecutableElement method, A annotation) throws ProcessingException; +} diff --git a/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcSubscriptionProcessor.java b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcSubscriptionProcessor.java new file mode 100644 index 00000000..4936f580 --- /dev/null +++ b/rpc/codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcSubscriptionProcessor.java @@ -0,0 +1,208 @@ +package com.strategyobject.substrateclient.rpc.codegen.sections; + +import com.google.common.base.Strings; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.MethodSpec; +import com.strategyobject.substrateclient.common.codegen.ProcessingException; +import com.strategyobject.substrateclient.common.codegen.ProcessorContext; +import com.strategyobject.substrateclient.rpc.core.annotations.RpcSubscription; +import lombok.NonNull; +import lombok.val; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Supplier; + +import static com.strategyobject.substrateclient.rpc.codegen.sections.Constants.*; + +class RpcSubscriptionProcessor extends RpcMethodProcessor { + private static final String CALL_BACK_ARG = "r"; + private static final String CALL_BACK_EX_ARG = "e"; + private static final String CALL_BACK_PROXY = "callbackProxy"; + private static final String ID = "id"; + + private boolean hasCallback = false; + private String callbackName; + private TypeMirror callbackParameter = null; + private TypeMirror callbackType; + private TypeMirror exceptionType; + + public RpcSubscriptionProcessor(@NonNull TypeElement interfaceElement) { + super(RpcSubscription.class, interfaceElement); + } + + @Override + protected void onProcessStarting(ProcessorContext context) { + callbackType = context.getType(BiConsumer.class); + exceptionType = context.getType(Exception.class); + } + + @Override + protected void callProviderInterface(MethodSpec.Builder methodSpecBuilder, + ExecutableElement method, + String section, + RpcSubscription annotation, + ProcessorContext context, + BiFunction decoder) { + val typeName = String.format(RPC_METHOD_NAME_TEMPLATE, section, annotation.type()); + val subscribeMethod = String.format(RPC_METHOD_NAME_TEMPLATE, section, annotation.subscribeMethod()); + val unsubscribeMethod = String.format(RPC_METHOD_NAME_TEMPLATE, section, annotation.unsubscribeMethod()); + + val callBackCode = CodeBlock.builder() + .add("$1T<$2T, $3T> $4L = ($5L, $6L) -> { $7N.$8L($5L, ", + BiConsumer.class, + Exception.class, + Object.class, + CALL_BACK_PROXY, + CALL_BACK_EX_ARG, + CALL_BACK_ARG, + callbackName, + ACCEPT); + + if (isCallbackResultVoid(context)) { + callBackCode.add("null"); + } else { + callBackCode.add(decoder.apply(callbackParameter, CALL_BACK_ARG)); + } + + callBackCode.add("); }"); + + methodSpecBuilder.addStatement(callBackCode.build()) + .addStatement(CodeBlock.builder() + .add("return $L.$L($S, $S, $L, $L)", + PROVIDER_INTERFACE, + SUBSCRIBE, + typeName, + subscribeMethod, + PARAMS_VAR, + CALL_BACK_PROXY) + .add(".$1L($2L -> () -> $3L.$4L($5S, $6S, $2L))", + THEN_APPLY, + ID, + PROVIDER_INTERFACE, + UNSUBSCRIBE, + typeName, + unsubscribeMethod) + .build()); + } + + @Override + protected boolean useDecodeRegistries(ExecutableElement method, ProcessorContext context) { + return !isCallbackResultVoid(context); + } + + @Override + protected boolean shouldBePassedToProvider(ExecutableElement method, VariableElement param, ProcessorContext context) throws ProcessingException { + val paramType = param.asType(); + if (!context.isSameType(context.erasure(paramType), context.erasure(callbackType))) { // is not callback + return true; + } + + if (hasCallback) { + throw new ProcessingException( + interfaceElement, + "Method `%s.%s` contains more than one callback.", + interfaceElement.getQualifiedName().toString(), + method.getSimpleName()); + } + + callbackParameter = ((DeclaredType) paramType).getTypeArguments().get(1); + if (!context.isSameType(exceptionType, ((DeclaredType) paramType).getTypeArguments().get(0))) { + throw new ProcessingException( + interfaceElement, + "Method `%s.%s` contains the incorrect callback. Must be %s<%s, T>.", + interfaceElement.getQualifiedName().toString(), + method.getSimpleName(), + BiConsumer.class.getSimpleName(), + Exception.class.getSimpleName()); + } + + hasCallback = true; + callbackName = param.getSimpleName().toString(); + + return false; + } + + @Override + protected void onParametersVisited(ExecutableElement method) throws ProcessingException { + if (!hasCallback) { + throw new ProcessingException( + interfaceElement, + "Method `%s.%s` doesn't contain a callback.", + interfaceElement.getQualifiedName().toString(), + method.getSimpleName()); + } + } + + @Override + protected void ensureMethodHasAppropriateReturnType(ExecutableElement method, TypeMirror returnType, ProcessorContext context) throws ProcessingException { + val futureType = context.erasure(context.getType(CompletableFuture.class)); + if (context.isSameType(futureType, context.erasure(returnType))) { // CompletableFuture<> + val supplier = context.erasure(context.getType(Supplier.class)); + val futureParameter = ((DeclaredType) returnType).getTypeArguments().get(0); + + if (context.isSameType(supplier, context.erasure(futureParameter))) { // CompletableFuture> + val supplierParameter = ((DeclaredType) futureParameter).getTypeArguments().get(0); + + if (context.isSameType(futureType, context.erasure(supplierParameter))) { // CompletableFuture>> + val boolType = context.getType(Boolean.class); + val subFutureParameter = ((DeclaredType) supplierParameter).getTypeArguments().get(0); + + if (context.isSameType(boolType, subFutureParameter)) { // CompletableFuture>> + return; + } + } + } + } + + throw new ProcessingException( + interfaceElement, + "Method `%s.%s` has unexpected return type. Must be `%3$s<%4$s<%3$s<%5$s>>>`.", + interfaceElement.getQualifiedName().toString(), + method.getSimpleName(), + CompletableFuture.class.getSimpleName(), + Supplier.class.getSimpleName(), + Boolean.class.getSimpleName()); + } + + @Override + protected void ensureAnnotationIsFilled(ExecutableElement method, RpcSubscription annotation) throws ProcessingException { + if (Strings.isNullOrEmpty(annotation.type())) { + throw new ProcessingException( + interfaceElement, + "`@%s` of `%s.%s` contains null or empty `type`.", + annotation.getClass().getSimpleName(), + interfaceElement.getQualifiedName().toString(), + method.getSimpleName()); + } + + if (Strings.isNullOrEmpty(annotation.subscribeMethod())) { + throw new ProcessingException( + interfaceElement, + "`@%s` of `%s.%s` contains null or empty `subscribeMethod`.", + annotation.getClass().getSimpleName(), + interfaceElement.getQualifiedName().toString(), + method.getSimpleName()); + } + + if (Strings.isNullOrEmpty(annotation.unsubscribeMethod())) { + throw new ProcessingException( + interfaceElement, + "`@%s` of `%s.%s` contains null or empty `unsubscribeMethod`.", + annotation.getClass().getSimpleName(), + interfaceElement.getQualifiedName().toString(), + method.getSimpleName()); + } + } + + private boolean isCallbackResultVoid(ProcessorContext context) { + return context.isSameType(callbackParameter, + context.getType(Void.class)); + } +} diff --git a/rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/decoder/RpcDecoderProcessorTests.java b/rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/decoder/RpcDecoderProcessorTests.java new file mode 100644 index 00000000..c01eae2e --- /dev/null +++ b/rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/decoder/RpcDecoderProcessorTests.java @@ -0,0 +1,66 @@ +package com.strategyobject.substrateclient.rpc.codegen.decoder; + +import com.google.gson.Gson; +import com.google.testing.compile.JavaFileObjects; +import com.strategyobject.substrateclient.rpc.codegen.substitutes.TestDecodable; +import com.strategyobject.substrateclient.rpc.core.DecoderPair; +import com.strategyobject.substrateclient.rpc.core.registries.RpcDecoderRegistry; +import lombok.val; +import org.junit.jupiter.api.Test; + +import static com.google.testing.compile.CompilationSubject.assertThat; +import static com.google.testing.compile.Compiler.javac; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class RpcDecoderProcessorTests { + private final Gson gson = new Gson(); + + @Test + void failsWhenAnnotationIsAppliedToClass() { + val clazz = JavaFileObjects.forResource("RpcDecodableInterface.java"); + + val compilation = javac() + .withProcessors(new RpcDecoderProcessor()) + .compile(clazz); + + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining("Only classes"); + } + + @Test + void failsWhenDoesNotHaveSetter() { + val clazz = JavaFileObjects.forResource("RpcDecodableWithoutSetter.java"); + + val compilation = javac() + .withProcessors(new RpcDecoderProcessor()) + .compile(clazz); + + assertThat(compilation).failed(); + } + + @Test + void compiles() { + val clazz = JavaFileObjects.forResource("RpcDecodable.java"); + + val compilation = javac() + .withProcessors(new RpcDecoderProcessor()) + .compile(clazz); + + assertThat(compilation).succeeded(); + } + + @Test + void compilesAndDecodes() { // TODO move this test out of the project + val registry = RpcDecoderRegistry.getInstance(); + val decoder = registry.resolve(TestDecodable.class) + .inject(DecoderPair.of(registry.resolve(String.class), null)); + + Object source = gson.fromJson("{a: 4, b: \"123\", c: \"some\"}", Object.class); + val expected = new TestDecodable<>(4, "123", "some"); + + val actual = decoder.decode(source); + + assertEquals(gson.toJson(expected), gson.toJson(actual)); + } +} diff --git a/rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/encoder/RpcEncoderProcessorTests.java b/rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/encoder/RpcEncoderProcessorTests.java new file mode 100644 index 00000000..0598a993 --- /dev/null +++ b/rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/encoder/RpcEncoderProcessorTests.java @@ -0,0 +1,82 @@ +package com.strategyobject.substrateclient.rpc.codegen.encoder; + +import com.google.gson.Gson; +import com.google.testing.compile.JavaFileObjects; +import com.strategyobject.substrateclient.rpc.codegen.substitutes.TestEncodable; +import com.strategyobject.substrateclient.rpc.core.EncoderPair; +import com.strategyobject.substrateclient.rpc.core.RpcEncoder; +import com.strategyobject.substrateclient.rpc.core.registries.RpcEncoderRegistry; +import com.strategyobject.substrateclient.scale.ScaleUtils; +import com.strategyobject.substrateclient.scale.writers.I32Writer; +import lombok.val; +import org.junit.jupiter.api.Test; + +import static com.google.testing.compile.CompilationSubject.assertThat; +import static com.google.testing.compile.Compiler.javac; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class RpcEncoderProcessorTests { + private final Gson gson = new Gson(); + + @Test + void failsWhenAnnotationIsAppliedToClass() { + val clazz = JavaFileObjects.forResource("RpcEncodableInterface.java"); + + val compilation = javac() + .withProcessors(new RpcEncoderProcessor()) + .compile(clazz); + + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining("Only classes"); + } + + @Test + void failsWhenDoesNotHaveGetter() { + val clazz = JavaFileObjects.forResource("RpcEncodableWithoutGetter.java"); + + val compilation = javac() + .withProcessors(new RpcEncoderProcessor()) + .compile(clazz); + + assertThat(compilation).failed(); + } + + @Test + void compiles() { + val clazz = JavaFileObjects.forResource("RpcEncodable.java"); + + val compilation = javac() + .withProcessors(new RpcEncoderProcessor()) + .compile(clazz); + + assertThat(compilation).succeeded(); + } + + @Test + @SuppressWarnings("unchecked") + void compilesAndDecodes() { // TODO move this test out of the project + val registry = RpcEncoderRegistry.getInstance(); + val encoder = (RpcEncoder>) registry.resolve(TestEncodable.class) + .inject( + EncoderPair.of( + registry + .resolve(TestEncodable.Subclass.class) + .inject(EncoderPair.of( + registry.resolve(int.class), + null + )), + null)); + + val source = new TestEncodable<>(4, "some", new TestEncodable.Subclass<>(123), true); + val expected = gson.fromJson( + "{\"a\":\"" + ScaleUtils.toHexString(4, new I32Writer()) + "\", \"b\": \"some\", \"c\": {\"a\": 123}, \"d\": true}", + Object.class); + + val actual = gson.fromJson( + gson.toJson(encoder.encode(source)), + Object.class); + + assertEquals(gson.toJson(expected), gson.toJson(actual)); + } +} diff --git a/rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/RpcInterfaceProcessorTest.java b/rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcInterfaceProcessorTest.java similarity index 98% rename from rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/RpcInterfaceProcessorTest.java rename to rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcInterfaceProcessorTest.java index 90d02701..52464ebe 100644 --- a/rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/RpcInterfaceProcessorTest.java +++ b/rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcInterfaceProcessorTest.java @@ -1,4 +1,4 @@ -package com.strategyobject.substrateclient.rpc.codegen; +package com.strategyobject.substrateclient.rpc.codegen.sections; import com.google.testing.compile.JavaFileObjects; import com.strategyobject.substrateclient.rpc.core.annotations.RpcCall; diff --git a/rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/RpcSectionFactoryTest.java b/rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcSectionFactoryTest.java similarity index 54% rename from rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/RpcSectionFactoryTest.java rename to rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcSectionFactoryTest.java index 7c8fa929..ff10fd4d 100644 --- a/rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/RpcSectionFactoryTest.java +++ b/rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcSectionFactoryTest.java @@ -1,8 +1,6 @@ -package com.strategyobject.substrateclient.rpc.codegen; +package com.strategyobject.substrateclient.rpc.codegen.sections; import com.strategyobject.substrateclient.rpc.codegen.substitutes.TestSection; -import com.strategyobject.substrateclient.rpc.core.ParameterConverter; -import com.strategyobject.substrateclient.rpc.core.ResultConverter; import com.strategyobject.substrateclient.transport.ProviderInterface; import lombok.val; import org.junit.jupiter.api.Test; @@ -17,27 +15,19 @@ import static org.mockito.Mockito.when; public class RpcSectionFactoryTest { + // TODO move this test out of the project @Test - void createsRpcSectionAndCallMethod() throws ExecutionException, InterruptedException, RpcInterfaceInitializationException { - val factory = new RpcGeneratedSectionFactory(); - + void createsRpcSectionAndCallsMethod() throws ExecutionException, InterruptedException, RpcInterfaceInitializationException { val expected = true; - val sendFuture = CompletableFuture.completedFuture((Object) String.valueOf(expected)); + val sendFuture = CompletableFuture.completedFuture((Object) Boolean.valueOf(expected)); val provider = mock(ProviderInterface.class); when(provider.send(anyString(), anyList())) .thenReturn(sendFuture); - val parameterConverter = mock(ParameterConverter.class); - when(parameterConverter.convert(anyString())) - .thenReturn("a"); - - val resultConverter = mock(ResultConverter.class); - when(resultConverter.convert(anyString())) - .thenAnswer(invocation -> Boolean.valueOf(invocation.getArgument(0))); - - val rpcSection = factory.create(TestSection.class, provider, parameterConverter, resultConverter); + val factory = new RpcGeneratedSectionFactory(); + val rpcSection = factory.create(TestSection.class, provider); - val actual = rpcSection.doNothing(anyString()).get(); + val actual = rpcSection.doNothing("some").get(); assertEquals(expected, actual); } diff --git a/rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/substitutes/TestDecodable.java b/rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/substitutes/TestDecodable.java new file mode 100644 index 00000000..83940e47 --- /dev/null +++ b/rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/substitutes/TestDecodable.java @@ -0,0 +1,24 @@ +package com.strategyobject.substrateclient.rpc.codegen.substitutes; + +import com.strategyobject.substrateclient.rpc.core.annotations.RpcDecoder; +import lombok.Getter; +import lombok.Setter; + +@RpcDecoder +@Getter +@Setter +public class TestDecodable { + public int a; + public String b; + public T c; + + public TestDecodable() { + + } + + public TestDecodable(int a, String b, T c) { + this.a = a; + this.b = b; + this.c = c; + } +} diff --git a/rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/substitutes/TestEncodable.java b/rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/substitutes/TestEncodable.java new file mode 100644 index 00000000..f0f4c4ba --- /dev/null +++ b/rpc/codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/substitutes/TestEncodable.java @@ -0,0 +1,39 @@ +package com.strategyobject.substrateclient.rpc.codegen.substitutes; + +import com.strategyobject.substrateclient.rpc.core.annotations.RpcEncoder; +import com.strategyobject.substrateclient.scale.annotations.Scale; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@RpcEncoder +@Getter +@Setter +public class TestEncodable { + @Scale + public int a; + public String b; + public T c; + public boolean d; + + public TestEncodable() { + + } + + public TestEncodable(int a, String b, T c, boolean d) { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + } + + @Getter + @Setter + @AllArgsConstructor + @NoArgsConstructor + @RpcEncoder + public static class Subclass { + public T a; + } +} diff --git a/rpc/codegen/src/test/resources/RpcDecodable.java b/rpc/codegen/src/test/resources/RpcDecodable.java new file mode 100644 index 00000000..f32d8350 --- /dev/null +++ b/rpc/codegen/src/test/resources/RpcDecodable.java @@ -0,0 +1,36 @@ +import com.strategyobject.substrateclient.rpc.core.annotations.RpcDecoder; +import com.strategyobject.substrateclient.scale.annotations.Scale; + +@RpcDecoder +public class RpcDecodable { + public int a; + + @Scale + public int b; + + public T c; + + public int getA() { + return a; + } + + public void setA(int a) { + this.a = a; + } + + public int getB() { + return b; + } + + public void setB(int b) { + this.b = b; + } + + public T getC() { + return c; + } + + public void setC(T c) { + this.c = c; + } +} diff --git a/rpc/codegen/src/test/resources/RpcDecodableInterface.java b/rpc/codegen/src/test/resources/RpcDecodableInterface.java new file mode 100644 index 00000000..4cf88508 --- /dev/null +++ b/rpc/codegen/src/test/resources/RpcDecodableInterface.java @@ -0,0 +1,7 @@ +import com.strategyobject.substrateclient.rpc.core.annotations.RpcDecoder; + +@RpcDecoder +public interface RpcDecodableInterface { + int getA(); + void setA(int value); +} diff --git a/rpc/codegen/src/test/resources/RpcDecodableWithoutSetter.java b/rpc/codegen/src/test/resources/RpcDecodableWithoutSetter.java new file mode 100644 index 00000000..24e0fa63 --- /dev/null +++ b/rpc/codegen/src/test/resources/RpcDecodableWithoutSetter.java @@ -0,0 +1,32 @@ +import com.strategyobject.substrateclient.rpc.core.annotations.RpcDecoder; +import com.strategyobject.substrateclient.rpc.core.annotations.Scale; + +@RpcDecoder +public class RpcDecodableWithoutSetter { + public int a; + + @Scale + public int b; + + public T c; + + public void setB(int b) { + this.b = b; + } + + public void setC(T c) { + this.c = c; + } + + public int getA() { + return a; + } + + public int getB() { + return b; + } + + public T getC() { + return c; + } +} diff --git a/rpc/codegen/src/test/resources/RpcEncodable.java b/rpc/codegen/src/test/resources/RpcEncodable.java new file mode 100644 index 00000000..ad41eff8 --- /dev/null +++ b/rpc/codegen/src/test/resources/RpcEncodable.java @@ -0,0 +1,36 @@ +import com.strategyobject.substrateclient.rpc.core.annotations.RpcEncoder; +import com.strategyobject.substrateclient.scale.annotations.Scale; + +@RpcEncoder +public class RpcEncodable { + public long a; + + @Scale + public int b; + + public T c; + + public long getA() { + return a; + } + + public void setA(long a) { + this.a = a; + } + + public int getB() { + return b; + } + + public void setB(int b) { + this.b = b; + } + + public T getC() { + return c; + } + + public void setC(T c) { + this.c = c; + } +} diff --git a/rpc/codegen/src/test/resources/RpcEncodableInterface.java b/rpc/codegen/src/test/resources/RpcEncodableInterface.java new file mode 100644 index 00000000..0761301c --- /dev/null +++ b/rpc/codegen/src/test/resources/RpcEncodableInterface.java @@ -0,0 +1,7 @@ +import com.strategyobject.substrateclient.rpc.core.annotations.RpcEncoder; + +@RpcEncoder +public interface RpcEncodableInterface { + int getA(); + void setA(int value); +} diff --git a/rpc/codegen/src/test/resources/RpcEncodableWithoutGetter.java b/rpc/codegen/src/test/resources/RpcEncodableWithoutGetter.java new file mode 100644 index 00000000..a2d3d2e9 --- /dev/null +++ b/rpc/codegen/src/test/resources/RpcEncodableWithoutGetter.java @@ -0,0 +1,32 @@ +import com.strategyobject.substrateclient.rpc.core.annotations.RpcEncoder; +import com.strategyobject.substrateclient.rpc.core.annotations.Scale; + +@RpcEncoder +public class RpcEncodableWithoutGetter { + public int a; + + @Scale + public int b; + + public T c; + + public int getB() { + return b; + } + + public T getC() { + return c; + } + + public void setA(int a) { + this.a = a; + } + + public void setB(int b) { + this.b = b; + } + + public void setC(T c) { + this.c = c; + } +} diff --git a/rpc/core/build.gradle b/rpc/core/build.gradle index 281f896d..8678c1d5 100644 --- a/rpc/core/build.gradle +++ b/rpc/core/build.gradle @@ -1 +1,6 @@ -dependencies {} \ No newline at end of file +dependencies { + implementation project(':common') + implementation project(':scale') + + testImplementation 'com.google.code.gson:gson:2.8.8' +} \ No newline at end of file diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/DecoderPair.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/DecoderPair.java new file mode 100644 index 00000000..fabc2c30 --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/DecoderPair.java @@ -0,0 +1,28 @@ +package com.strategyobject.substrateclient.rpc.core; + +import com.strategyobject.substrateclient.scale.ScaleReader; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor(staticName = "of") +@Getter +public class DecoderPair { + private final RpcDecoder decoder; + private final ScaleReader scaleReader; + + public RpcDecoder getDecoderOrThrow() { + if (decoder == null) { + throw new NullPointerException(); + } + + return decoder; + } + + public ScaleReader getScaleReaderOrThrow() { + if (scaleReader == null) { + throw new NullPointerException(); + } + + return scaleReader; + } +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/EncoderPair.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/EncoderPair.java new file mode 100644 index 00000000..e0d8893c --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/EncoderPair.java @@ -0,0 +1,28 @@ +package com.strategyobject.substrateclient.rpc.core; + +import com.strategyobject.substrateclient.scale.ScaleWriter; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor(staticName = "of") +@Getter +public class EncoderPair { + private final RpcEncoder encoder; + private final ScaleWriter scaleWriter; + + public RpcEncoder getEncoderOrThrow() { + if (encoder == null) { + throw new NullPointerException(); + } + + return encoder; + } + + public ScaleWriter getScaleWriterOrThrow() { + if (scaleWriter == null) { + throw new NullPointerException(); + } + + return scaleWriter; + } +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/ParameterConverter.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/ParameterConverter.java deleted file mode 100644 index a9093a21..00000000 --- a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/ParameterConverter.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.strategyobject.substrateclient.rpc.core; - -public interface ParameterConverter { - Object convert(T param); -} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/PlainRpcEncoder.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/PlainRpcEncoder.java deleted file mode 100644 index 3b81472a..00000000 --- a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/PlainRpcEncoder.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.strategyobject.substrateclient.rpc.core; - -public class PlainRpcEncoder implements RpcEncoder { - @Override - public Object encode(T obj) { - return obj; - } -} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/ResultConverter.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/ResultConverter.java deleted file mode 100644 index 5f69002c..00000000 --- a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/ResultConverter.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.strategyobject.substrateclient.rpc.core; - -public interface ResultConverter { - TOut convert(RpcResult result); -} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcDecoder.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcDecoder.java index 138757b7..83ba1916 100644 --- a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcDecoder.java +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcDecoder.java @@ -1,5 +1,9 @@ package com.strategyobject.substrateclient.rpc.core; public interface RpcDecoder { - T decode(Object rpcObj); + T decode(Object value, DecoderPair... decoders); + + default RpcDecoder inject(DecoderPair... dependencies) { + return (jsonToken, decoders) -> this.decode(jsonToken, dependencies); + } } diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcEncoded.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcEncoded.java deleted file mode 100644 index b0749adf..00000000 --- a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcEncoded.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.strategyobject.substrateclient.rpc.core; - -public interface RpcEncoded> { - @SuppressWarnings("unchecked") - default Object encode() throws RpcEncoderNotFoundException { - return RpcEncoderRegistry - .getInstance() - .resolve((Class) this.getClass()) - .encode((T) this); - } -} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcEncoder.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcEncoder.java index 00ae9abe..0c088b47 100644 --- a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcEncoder.java +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcEncoder.java @@ -1,5 +1,9 @@ package com.strategyobject.substrateclient.rpc.core; public interface RpcEncoder { - Object encode(T obj); + Object encode(T source, EncoderPair... encoders); + + default RpcEncoder inject(EncoderPair... dependencies) { + return (source, encoders) -> this.encode(source, dependencies); + } } diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcEncoderNotFoundException.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcEncoderNotFoundException.java deleted file mode 100644 index daa86d8d..00000000 --- a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcEncoderNotFoundException.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.strategyobject.substrateclient.rpc.core; - -import lombok.NonNull; - -public class RpcEncoderNotFoundException extends Exception { - public RpcEncoderNotFoundException(@NonNull Class clazz) { - super(String.format("RpcEncoder wasn't registered for %s.", clazz)); - } -} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcEncoderRegistry.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcEncoderRegistry.java deleted file mode 100644 index cb0a47b5..00000000 --- a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcEncoderRegistry.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.strategyobject.substrateclient.rpc.core; - -import lombok.NonNull; - -public class RpcEncoderRegistry { - public RpcEncoder resolve(@NonNull Class clazz) throws RpcEncoderNotFoundException { - throw new RpcEncoderNotFoundException(clazz); // TODO It will be done later. - } - - public static RpcEncoderRegistry getInstance() { - throw new UnsupportedOperationException(); // TODO It will be done later. - } -} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcSelfEncodable.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcSelfEncodable.java new file mode 100644 index 00000000..364dabcf --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/RpcSelfEncodable.java @@ -0,0 +1,12 @@ +package com.strategyobject.substrateclient.rpc.core; + +import com.strategyobject.substrateclient.rpc.core.registries.RpcEncoderRegistry; + +public interface RpcSelfEncodable> { + @SuppressWarnings("unchecked") + default Object encode(T source) { + return ((RpcSelfEncodable) RpcEncoderRegistry.getInstance() + .resolve(this.getClass())) + .encode(source); + } +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/AutoRegister.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/AutoRegister.java new file mode 100644 index 00000000..b29fe35d --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/AutoRegister.java @@ -0,0 +1,14 @@ +package com.strategyobject.substrateclient.rpc.core.annotations; + +import lombok.NonNull; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface AutoRegister { + @NonNull Class[] types(); +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/Ignore.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/Ignore.java new file mode 100644 index 00000000..69bb3840 --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/Ignore.java @@ -0,0 +1,11 @@ +package com.strategyobject.substrateclient.rpc.core.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.SOURCE) +public @interface Ignore { +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/RpcDecoder.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/RpcDecoder.java new file mode 100644 index 00000000..1e2c3713 --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/RpcDecoder.java @@ -0,0 +1,11 @@ +package com.strategyobject.substrateclient.rpc.core.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.SOURCE) +public @interface RpcDecoder { +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/RpcEncoder.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/RpcEncoder.java new file mode 100644 index 00000000..5c7a6be9 --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/RpcEncoder.java @@ -0,0 +1,11 @@ +package com.strategyobject.substrateclient.rpc.core.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.PARAMETER}) +@Retention(RetentionPolicy.SOURCE) +public @interface RpcEncoder { +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/AbstractDecoder.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/AbstractDecoder.java new file mode 100644 index 00000000..31972a31 --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/AbstractDecoder.java @@ -0,0 +1,25 @@ +package com.strategyobject.substrateclient.rpc.core.decoders; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.rpc.core.DecoderPair; +import com.strategyobject.substrateclient.rpc.core.RpcDecoder; + +public abstract class AbstractDecoder implements RpcDecoder { + @Override + public final T decode(Object value, DecoderPair... decoders) { + if (value == null) { + return null; + } + + // TODO I am not sure should it be the first instruction of the method or not + checkArguments(value, decoders); + + return decodeNonNull(value, decoders); + } + + protected abstract T decodeNonNull(Object value, DecoderPair[] decoders); + + protected void checkArguments(Object value, DecoderPair[] decoders) { + Preconditions.checkArgument(decoders == null || decoders.length == 0); + } +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/BooleanDecoder.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/BooleanDecoder.java new file mode 100644 index 00000000..368a7ae7 --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/BooleanDecoder.java @@ -0,0 +1,10 @@ +package com.strategyobject.substrateclient.rpc.core.decoders; + +import com.strategyobject.substrateclient.rpc.core.DecoderPair; + +public class BooleanDecoder extends AbstractDecoder { + @Override + protected Boolean decodeNonNull(Object value, DecoderPair[] decoders) { + return (Boolean) value; + } +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/ByteDecoder.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/ByteDecoder.java new file mode 100644 index 00000000..396f838f --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/ByteDecoder.java @@ -0,0 +1,10 @@ +package com.strategyobject.substrateclient.rpc.core.decoders; + +import com.strategyobject.substrateclient.rpc.core.DecoderPair; + +public class ByteDecoder extends AbstractDecoder { + @Override + protected Byte decodeNonNull(Object value, DecoderPair[] decoders) { + return ((Double) value).byteValue(); + } +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/DoubleDecoder.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/DoubleDecoder.java new file mode 100644 index 00000000..5a353511 --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/DoubleDecoder.java @@ -0,0 +1,10 @@ +package com.strategyobject.substrateclient.rpc.core.decoders; + +import com.strategyobject.substrateclient.rpc.core.DecoderPair; + +public class DoubleDecoder extends AbstractDecoder { + @Override + protected Double decodeNonNull(Object value, DecoderPair[] decoders) { + return (Double) value; + } +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/FloatDecoder.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/FloatDecoder.java new file mode 100644 index 00000000..f5602d37 --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/FloatDecoder.java @@ -0,0 +1,10 @@ +package com.strategyobject.substrateclient.rpc.core.decoders; + +import com.strategyobject.substrateclient.rpc.core.DecoderPair; + +public class FloatDecoder extends AbstractDecoder { + @Override + protected Float decodeNonNull(Object value, DecoderPair[] decoders) { + return ((Double) value).floatValue(); + } +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/IntDecoder.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/IntDecoder.java new file mode 100644 index 00000000..f9fcff14 --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/IntDecoder.java @@ -0,0 +1,10 @@ +package com.strategyobject.substrateclient.rpc.core.decoders; + +import com.strategyobject.substrateclient.rpc.core.DecoderPair; + +public class IntDecoder extends AbstractDecoder { + @Override + protected Integer decodeNonNull(Object value, DecoderPair[] decoders) { + return ((Double) value).intValue(); + } +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/ListDecoder.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/ListDecoder.java new file mode 100644 index 00000000..91956490 --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/ListDecoder.java @@ -0,0 +1,23 @@ +package com.strategyobject.substrateclient.rpc.core.decoders; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.rpc.core.DecoderPair; +import lombok.val; + +import java.util.List; +import java.util.stream.Collectors; + +public class ListDecoder extends AbstractDecoder> { + @Override + protected List decodeNonNull(Object value, DecoderPair[] decoders) { + val nestedDecoder = decoders[0].getDecoderOrThrow(); + + return ((List) value).stream().map(nestedDecoder::decode).collect(Collectors.toList()); + } + + @Override + protected void checkArguments(Object value, DecoderPair[] decoders) { + Preconditions.checkArgument(decoders != null && decoders.length == 1); + Preconditions.checkNotNull(decoders[0]); + } +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/LongDecoder.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/LongDecoder.java new file mode 100644 index 00000000..eac5ed16 --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/LongDecoder.java @@ -0,0 +1,10 @@ +package com.strategyobject.substrateclient.rpc.core.decoders; + +import com.strategyobject.substrateclient.rpc.core.DecoderPair; + +public class LongDecoder extends AbstractDecoder { + @Override + protected Long decodeNonNull(Object value, DecoderPair[] decoders) { + return ((Double) value).longValue(); + } +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/MapDecoder.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/MapDecoder.java new file mode 100644 index 00000000..60888a4a --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/MapDecoder.java @@ -0,0 +1,31 @@ +package com.strategyobject.substrateclient.rpc.core.decoders; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.rpc.core.DecoderPair; +import lombok.val; + +import java.util.Map; + +import static java.util.stream.Collectors.toMap; + +public class MapDecoder extends AbstractDecoder> { + @Override + @SuppressWarnings("unchecked") + protected Map decodeNonNull(Object value, DecoderPair[] decoders) { + val keyDecoder = decoders[0].getDecoderOrThrow(); + val valueDecoder = decoders[1].getDecoderOrThrow(); + + return ((Map) value) + .entrySet() + .stream() + .collect(toMap(e -> keyDecoder.decode(e.getKey()), + e -> valueDecoder.decode(e.getValue()))); + } + + @Override + protected void checkArguments(Object value, DecoderPair[] decoders) { + Preconditions.checkArgument(decoders != null && decoders.length == 2); + Preconditions.checkNotNull(decoders[0]); + Preconditions.checkNotNull(decoders[1]); + } +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/ShortDecoder.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/ShortDecoder.java new file mode 100644 index 00000000..fc9b213a --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/ShortDecoder.java @@ -0,0 +1,10 @@ +package com.strategyobject.substrateclient.rpc.core.decoders; + +import com.strategyobject.substrateclient.rpc.core.DecoderPair; + +public class ShortDecoder extends AbstractDecoder { + @Override + protected Short decodeNonNull(Object value, DecoderPair[] decoders) { + return ((Double) value).shortValue(); + } +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/StringDecoder.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/StringDecoder.java new file mode 100644 index 00000000..7bb6f1d6 --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/StringDecoder.java @@ -0,0 +1,10 @@ +package com.strategyobject.substrateclient.rpc.core.decoders; + +import com.strategyobject.substrateclient.rpc.core.DecoderPair; + +public class StringDecoder extends AbstractDecoder { + @Override + protected String decodeNonNull(Object value, DecoderPair[] decoders) { + return (String) value; + } +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/VoidDecoder.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/VoidDecoder.java new file mode 100644 index 00000000..49e9af53 --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/decoders/VoidDecoder.java @@ -0,0 +1,14 @@ +package com.strategyobject.substrateclient.rpc.core.decoders; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.rpc.core.DecoderPair; +import com.strategyobject.substrateclient.rpc.core.RpcDecoder; + +public class VoidDecoder implements RpcDecoder { + @Override + public Void decode(Object value, DecoderPair... decoders) { + Preconditions.checkArgument(decoders == null || decoders.length == 0); + + return null; + } +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/encoders/ListEncoder.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/encoders/ListEncoder.java new file mode 100644 index 00000000..e1c3fbaf --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/encoders/ListEncoder.java @@ -0,0 +1,26 @@ +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.List; +import java.util.stream.Collectors; + +public class ListEncoder implements RpcEncoder> { + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + public Object encode(List source, EncoderPair... encoders) { + Preconditions.checkArgument(encoders != null && encoders.length == 1); + + if (source == null) { + return null; + } + + Preconditions.checkNotNull(encoders[0]); + val nestedEncoder = (RpcEncoder) encoders[0].getEncoderOrThrow(); + + return source.stream().map(nestedEncoder::encode).collect(Collectors.toList()); + } +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/encoders/MapEncoder.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/encoders/MapEncoder.java new file mode 100644 index 00000000..862bfb1e --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/encoders/MapEncoder.java @@ -0,0 +1,31 @@ +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.Map; + +import static java.util.stream.Collectors.toMap; + +public class MapEncoder implements RpcEncoder> { + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + public Object encode(Map source, EncoderPair... encoders) { + Preconditions.checkArgument(encoders != null && encoders.length == 2); + if (source == null) { + return null; + } + + Preconditions.checkNotNull(encoders[0]); + Preconditions.checkNotNull(encoders[1]); + val keyEncoder = (RpcEncoder) encoders[0].getEncoderOrThrow(); + val valueEncoder = (RpcEncoder) encoders[1].getEncoderOrThrow(); + + return source.entrySet() + .stream() + .collect(toMap(e -> keyEncoder.encode(e.getKey()), + e -> valueEncoder.encode(e.getValue()))); + } +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/encoders/PlainEncoder.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/encoders/PlainEncoder.java new file mode 100644 index 00000000..50a6f8db --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/encoders/PlainEncoder.java @@ -0,0 +1,14 @@ +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; + +public class PlainEncoder implements RpcEncoder { + @Override + public Object encode(T source, EncoderPair... encoders) { + Preconditions.checkArgument(encoders == null || encoders.length == 0); + + return source; + } +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/registries/RpcDecoderRegistry.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/registries/RpcDecoderRegistry.java new file mode 100644 index 00000000..6ec2137e --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/registries/RpcDecoderRegistry.java @@ -0,0 +1,80 @@ +package com.strategyobject.substrateclient.rpc.core.registries; + +import com.strategyobject.substrateclient.common.reflection.Scanner; +import com.strategyobject.substrateclient.rpc.core.RpcDecoder; +import com.strategyobject.substrateclient.rpc.core.annotations.AutoRegister; +import com.strategyobject.substrateclient.rpc.core.decoders.*; +import lombok.NonNull; +import lombok.val; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public final class RpcDecoderRegistry { + private static final Logger logger = LoggerFactory.getLogger(RpcDecoderRegistry.class); + private static final String ROOT_PREFIX = "com.strategyobject.substrateclient"; + private static volatile RpcDecoderRegistry instance; + private final Map, RpcDecoder> decoders; + + private RpcDecoderRegistry() { + decoders = new ConcurrentHashMap<>(); + register(new ListDecoder(), List.class); + register(new MapDecoder(), Map.class); + register(new VoidDecoder(), Void.class); + register(new BooleanDecoder(), Boolean.class, boolean.class); + register(new ByteDecoder(), Byte.class, byte.class); + register(new DoubleDecoder(), Double.class, double.class); + register(new FloatDecoder(), Float.class, float.class); + register(new IntDecoder(), Integer.class, int.class); + register(new LongDecoder(), Long.class, long.class); + register(new ShortDecoder(), Short.class, short.class); + register(new StringDecoder(), String.class); + + registerAnnotatedFrom(ROOT_PREFIX); + } + + public static RpcDecoderRegistry getInstance() { + if (instance == null) { + synchronized (RpcDecoderRegistry.class) { + if (instance == null) { + instance = new RpcDecoderRegistry(); + } + } + } + + return instance; + } + + public void registerAnnotatedFrom(String... prefixes) { + Scanner.forPrefixes(prefixes) + .getSubTypesOf(RpcDecoder.class).forEach(decoder -> { + val autoRegister = decoder.getAnnotation(AutoRegister.class); + if (autoRegister == null) { + return; + } + + try { + val types = autoRegister.types(); + logger.info("Auto register decoder {} for types: {}", decoder, types); + + final RpcDecoder instance = decoder.newInstance(); + register(instance, types); + } catch (InstantiationException | IllegalAccessException e) { + logger.error("Auto registration error", e); + } + }); + } + + public void register(@NonNull RpcDecoder decoder, @NonNull Class... clazz) { + for (val type : clazz) { + decoders.put(type, decoder); + } + } + + public RpcDecoder resolve(@NonNull Class clazz) { + return decoders.get(clazz); + } +} diff --git a/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/registries/RpcEncoderRegistry.java b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/registries/RpcEncoderRegistry.java new file mode 100644 index 00000000..ca501e49 --- /dev/null +++ b/rpc/core/src/main/java/com/strategyobject/substrateclient/rpc/core/registries/RpcEncoderRegistry.java @@ -0,0 +1,76 @@ +package com.strategyobject.substrateclient.rpc.core.registries; + +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.ListEncoder; +import com.strategyobject.substrateclient.rpc.core.encoders.MapEncoder; +import com.strategyobject.substrateclient.rpc.core.encoders.PlainEncoder; +import lombok.NonNull; +import lombok.val; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public final class RpcEncoderRegistry { + private static final Logger logger = LoggerFactory.getLogger(RpcEncoderRegistry.class); + private static final String ROOT_PREFIX = "com.strategyobject.substrateclient"; + private static volatile RpcEncoderRegistry instance; + private final Map, RpcEncoder> encoders; + + private RpcEncoderRegistry() { + encoders = new ConcurrentHashMap<>(); + 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); + + registerAnnotatedFrom(ROOT_PREFIX); + } + + public static RpcEncoderRegistry getInstance() { + if (instance == null) { + synchronized (RpcEncoderRegistry.class) { + if (instance == null) { + instance = new RpcEncoderRegistry(); + } + } + } + + return instance; + } + + public void registerAnnotatedFrom(String... prefixes) { + Scanner.forPrefixes(prefixes) + .getSubTypesOf(RpcEncoder.class).forEach(encoder -> { + val autoRegister = encoder.getAnnotation(AutoRegister.class); + if (autoRegister == null) { + return; + } + + try { + val types = autoRegister.types(); + logger.info("Auto register encoder {} for types: {}", encoder, types); + + final RpcEncoder instance = encoder.newInstance(); + register(instance, types); + } catch (InstantiationException | IllegalAccessException e) { + logger.error("Auto registration error", e); + } + }); + } + + public void register(@NonNull RpcEncoder encoder, @NonNull Class... clazz) { + for (val type : clazz) { + encoders.put(type, encoder); + } + } + + public RpcEncoder resolve(@NonNull Class clazz) { + return encoders.get(clazz); + } +} diff --git a/rpc/core/src/test/java/com/strategyobject/substrateclient/rpc/core/decoders/KnownDecoderTests.java b/rpc/core/src/test/java/com/strategyobject/substrateclient/rpc/core/decoders/KnownDecoderTests.java new file mode 100644 index 00000000..a0c58dfe --- /dev/null +++ b/rpc/core/src/test/java/com/strategyobject/substrateclient/rpc/core/decoders/KnownDecoderTests.java @@ -0,0 +1,60 @@ +package com.strategyobject.substrateclient.rpc.core.decoders; + +import com.google.gson.Gson; +import com.strategyobject.substrateclient.rpc.core.DecoderPair; +import com.strategyobject.substrateclient.rpc.core.RpcDecoder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class KnownDecoderTests { + private final TestCase[] testCases = { + new TestCase<>(new BooleanDecoder(), "true", true), + new TestCase<>(new ByteDecoder(), "5", (byte) 5), + new TestCase<>(new DoubleDecoder(), "15.25", 15.25), + new TestCase<>(new FloatDecoder(), "19.5", 19.5f), + new TestCase<>(new IntDecoder(), "-5", -5), + new TestCase<>(new LongDecoder(), "2147483648", 2147483648L), + new TestCase<>(new ShortDecoder(), "290", (short) 290), + new TestCase<>(new StringDecoder(), "\"some\"", "some"), + new TestCase<>(new VoidDecoder(), "null", null), + + new TestCase<>( + new ListDecoder().inject(DecoderPair.of(new IntDecoder(), null)), + "[1, 2, 3]", + Arrays.asList(1, 2, 3)), + new TestCase<>( + new MapDecoder().inject( + DecoderPair.of(new StringDecoder(), null), + DecoderPair.of(new IntDecoder(), null)), + "{a: 1, b: 2, c: 3}", + new HashMap() {{ + put("a", 1); + put("b", 2); + put("c", 3); + }}), + }; + + @Test + public void decode() { + Gson gson = new Gson(); + + Arrays.stream(testCases) + .forEach(c -> assertEquals( + c.decoder.decode(gson.fromJson(c.getJson(), Object.class)), + c.getResult())); + } + + @RequiredArgsConstructor + @Getter + static class TestCase { + private final RpcDecoder decoder; + private final String json; + private final T result; + } +} diff --git a/rpc/core/src/test/java/com/strategyobject/substrateclient/rpc/core/decoders/KnownEncoderTests.java b/rpc/core/src/test/java/com/strategyobject/substrateclient/rpc/core/decoders/KnownEncoderTests.java new file mode 100644 index 00000000..154c3c17 --- /dev/null +++ b/rpc/core/src/test/java/com/strategyobject/substrateclient/rpc/core/decoders/KnownEncoderTests.java @@ -0,0 +1,80 @@ +package com.strategyobject.substrateclient.rpc.core.decoders; + +import com.google.gson.Gson; +import com.strategyobject.substrateclient.rpc.core.EncoderPair; +import com.strategyobject.substrateclient.rpc.core.RpcEncoder; +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 lombok.val; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +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(); + result.put("a", 1); + result.put("b", 2); + result.put("c", 3); + + return result; + } + + private static Map getExpectedMap() { + val result = new HashMap(); + result.put("a", "1"); + result.put("b", "2"); + result.put("c", "3"); + + 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)))); + } + + @RequiredArgsConstructor + @Getter + static class TestCase { + private final RpcEncoder encoder; + private final T source; + private final Object expected; + } +} diff --git a/rpc/rpc-types/build.gradle b/rpc/rpc-types/build.gradle index 5f509aed..cd17727b 100644 --- a/rpc/rpc-types/build.gradle +++ b/rpc/rpc-types/build.gradle @@ -2,4 +2,7 @@ dependencies { implementation project(':types') implementation project(':scale') implementation project(':rpc:core') + implementation project(':common') + annotationProcessor project(':rpc:codegen') + annotationProcessor project(':scale:scale-codegen') } \ No newline at end of file diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/AccountId.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/AccountId.java index 42c1fba3..99e433de 100644 --- a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/AccountId.java +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/AccountId.java @@ -1,6 +1,5 @@ package com.strategyobject.substrateclient.rpc.types; -import com.strategyobject.substrateclient.rpc.core.RpcEncoded; import com.strategyobject.substrateclient.scale.ScaleSelfWritable; import com.strategyobject.substrateclient.types.FixedBytes; import com.strategyobject.substrateclient.types.Size; @@ -8,8 +7,7 @@ public class AccountId extends FixedBytes - implements RpcEncoded, ScaleSelfWritable { - private static final int KEY_LENGTH = 32; + implements ScaleSelfWritable { protected AccountId(byte[] data) { super(data, Size.of32); diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/AddressId.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/AddressId.java index d06fd9c7..1e82b8fe 100644 --- a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/AddressId.java +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/AddressId.java @@ -1,9 +1,11 @@ package com.strategyobject.substrateclient.rpc.types; +import com.strategyobject.substrateclient.scale.annotations.ScaleWriter; import lombok.Getter; import lombok.NonNull; @Getter +@ScaleWriter public class AddressId implements Address { private final AddressKind kind = AddressKind.ID; private final AccountId address; diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/AddressKindScaleWriter.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/AddressKindScaleWriter.java new file mode 100644 index 00000000..3682f416 --- /dev/null +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/AddressKindScaleWriter.java @@ -0,0 +1,21 @@ +package com.strategyobject.substrateclient.rpc.types; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.scale.ScaleWriter; +import com.strategyobject.substrateclient.scale.annotations.AutoRegister; +import com.strategyobject.substrateclient.scale.registries.ScaleWriterRegistry; +import lombok.NonNull; + +import java.io.IOException; +import java.io.OutputStream; + +@AutoRegister(types = AddressKind.class) +public class AddressKindScaleWriter implements ScaleWriter { + @Override + @SuppressWarnings("unchecked") + public void write(@NonNull AddressKind value, @NonNull OutputStream stream, ScaleWriter... writers) throws IOException { + Preconditions.checkArgument(writers == null || writers.length == 0); + + ((ScaleWriter) ScaleWriterRegistry.getInstance().resolve(byte.class)).write(value.getValue(), stream); + } +} diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/BlockHash.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/BlockHash.java index 52d81a1d..2283d23e 100644 --- a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/BlockHash.java +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/BlockHash.java @@ -1,6 +1,5 @@ package com.strategyobject.substrateclient.rpc.types; -import com.strategyobject.substrateclient.rpc.core.RpcEncoded; import com.strategyobject.substrateclient.scale.ScaleSelfWritable; import com.strategyobject.substrateclient.types.FixedBytes; import com.strategyobject.substrateclient.types.Size; @@ -8,7 +7,7 @@ public class BlockHash extends FixedBytes - implements ScaleSelfWritable, RpcEncoded { + implements ScaleSelfWritable { protected BlockHash(byte[] data) { super(data, Size.of32); } diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/BlockHashScaleReader.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/BlockHashScaleReader.java new file mode 100644 index 00000000..40bb6b1f --- /dev/null +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/BlockHashScaleReader.java @@ -0,0 +1,21 @@ +package com.strategyobject.substrateclient.rpc.types; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.common.io.Streamer; +import com.strategyobject.substrateclient.scale.ScaleReader; +import com.strategyobject.substrateclient.scale.annotations.AutoRegister; +import com.strategyobject.substrateclient.types.Size; +import lombok.NonNull; + +import java.io.IOException; +import java.io.InputStream; + +@AutoRegister(types = BlockHash.class) +public class BlockHashScaleReader implements ScaleReader { + @Override + public BlockHash read(@NonNull InputStream stream, ScaleReader... readers) throws IOException { + Preconditions.checkArgument(readers == null || readers.length == 0); + + return BlockHash.fromBytes(Streamer.readBytes(Size.of32.getValue(), stream)); + } +} diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/Extrinsic.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/Extrinsic.java index 55cefff9..7675f54f 100644 --- a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/Extrinsic.java +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/Extrinsic.java @@ -1,15 +1,14 @@ package com.strategyobject.substrateclient.rpc.types; -import com.strategyobject.substrateclient.rpc.core.RpcEncoded; import com.strategyobject.substrateclient.scale.ScaleSelfWritable; import lombok.Getter; import java.util.Optional; -// RpcEncoder must be overridden. It must be represented in hex string as a byte array of scale. +// ScaleWriter must be overridden. It must be represented in hex string as a byte array of scale. @Getter public class Extrinsic - implements RpcEncoded>, ScaleSelfWritable> { + implements ScaleSelfWritable> { private final Optional> signature; private final C call; diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/ExtrinsicScaleWriter.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/ExtrinsicScaleWriter.java new file mode 100644 index 00000000..04bb277a --- /dev/null +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/ExtrinsicScaleWriter.java @@ -0,0 +1,50 @@ +package com.strategyobject.substrateclient.rpc.types; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.scale.ScaleType; +import com.strategyobject.substrateclient.scale.ScaleWriter; +import com.strategyobject.substrateclient.scale.annotations.AutoRegister; +import com.strategyobject.substrateclient.scale.registries.ScaleWriterRegistry; +import lombok.NonNull; +import lombok.val; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +@AutoRegister(types = Extrinsic.class) +public class ExtrinsicScaleWriter implements ScaleWriter> { + private static final int VERSION = 4; + + @Override + public void write(@NonNull Extrinsic value, @NonNull OutputStream stream, ScaleWriter... writers) throws IOException { + Preconditions.checkArgument(writers == null || writers.length == 0); + + val encodedExtrinsic = new ByteArrayOutputStream(); + writeExtrinsic(value, encodedExtrinsic); + + wrapToVec(encodedExtrinsic, stream); + } + + @SuppressWarnings("unchecked") + private void wrapToVec(ByteArrayOutputStream encodedExtrinsic, OutputStream stream) throws IOException { + ((ScaleWriter)ScaleWriterRegistry.getInstance().resolve(ScaleType.CompactInteger.class)) + .write(encodedExtrinsic.size(), stream); + + stream.write(encodedExtrinsic.toByteArray()); + } + + @SuppressWarnings("unchecked") + private void writeExtrinsic(Extrinsic value, ByteArrayOutputStream stream) throws IOException { + val u8Writer = (ScaleWriter) ScaleWriterRegistry.getInstance().resolve(ScaleType.U8.class); + if (value.getSignature().isPresent()) { + u8Writer.write(VERSION | 0b1000_0000, stream); + val signature = (SignaturePayload) value.getSignature().get(); + signature.write(stream); + } else { + u8Writer.write(VERSION & 0b0111_1111, stream); + } + + value.getCall().write(stream); + } +} diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/ExtrinsicStatus.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/ExtrinsicStatus.java index 81defc41..fd221449 100644 --- a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/ExtrinsicStatus.java +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/ExtrinsicStatus.java @@ -1,4 +1,143 @@ package com.strategyobject.substrateclient.rpc.types; -public enum ExtrinsicStatus { +import com.strategyobject.substrateclient.rpc.core.annotations.RpcDecoder; +import com.strategyobject.substrateclient.scale.annotations.Scale; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +public interface ExtrinsicStatus { + ExtrinsicStatus Ready = new ExtrinsicStatus.Ready(); + ExtrinsicStatus Future = new ExtrinsicStatus.Future(); + ExtrinsicStatus Dropped = new ExtrinsicStatus.Dropped(); + ExtrinsicStatus Invalid = new ExtrinsicStatus.Invalid(); + + Status getStatus(); + + enum Status { + Future, + Ready, + Broadcast, + InBlock, + Retracted, + FinalityTimeout, + Finalized, + Usurped, + Dropped, + Invalid + } + + class Future implements ExtrinsicStatus { + @Override + public Status getStatus() { + return Status.Future; + } + } + + class Ready implements ExtrinsicStatus { + @Override + public Status getStatus() { + return Status.Ready; + } + } + + @NoArgsConstructor + @Getter + @Setter + @RpcDecoder + class Broadcast implements ExtrinsicStatus { + @Scale + private List broadcast; + + @Override + public Status getStatus() { + return Status.Broadcast; + } + } + + @NoArgsConstructor + @Getter + @Setter + @RpcDecoder + class InBlock implements ExtrinsicStatus { + @Scale + private BlockHash inBlock; + + @Override + public Status getStatus() { + return Status.InBlock; + } + } + + @NoArgsConstructor + @Getter + @Setter + @RpcDecoder + class Retracted implements ExtrinsicStatus { + @Scale + private BlockHash retracted; + + @Override + public Status getStatus() { + return Status.Retracted; + } + } + + @NoArgsConstructor + @Getter + @Setter + @RpcDecoder + class FinalityTimeout implements ExtrinsicStatus { + @Scale + private BlockHash finalityTimeout; + + @Override + public Status getStatus() { + return Status.FinalityTimeout; + } + } + + @NoArgsConstructor + @Getter + @Setter + @RpcDecoder + class Finalized implements ExtrinsicStatus { + @Scale + private BlockHash finalized; + + @Override + public Status getStatus() { + return Status.Finalized; + } + } + + @NoArgsConstructor + @Getter + @Setter + @RpcDecoder + class Usurped implements ExtrinsicStatus { + @Scale + private BlockHash usurped; + + @Override + public Status getStatus() { + return Status.Usurped; + } + } + + class Dropped implements ExtrinsicStatus { + @Override + public Status getStatus() { + return Status.Dropped; + } + } + + class Invalid implements ExtrinsicStatus { + @Override + public Status getStatus() { + return Status.Invalid; + } + } } diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/ExtrinsicStatusRpcDecoder.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/ExtrinsicStatusRpcDecoder.java new file mode 100644 index 00000000..4ca5a1d8 --- /dev/null +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/ExtrinsicStatusRpcDecoder.java @@ -0,0 +1,56 @@ +package com.strategyobject.substrateclient.rpc.types; + +import com.strategyobject.substrateclient.rpc.core.DecoderPair; +import com.strategyobject.substrateclient.rpc.core.annotations.AutoRegister; +import com.strategyobject.substrateclient.rpc.core.decoders.AbstractDecoder; +import com.strategyobject.substrateclient.rpc.core.registries.RpcDecoderRegistry; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +@AutoRegister(types = ExtrinsicStatus.class) +public class ExtrinsicStatusRpcDecoder extends AbstractDecoder { + private static final Map STATUS_TO_VALUE = new HashMap<>(); + private static final Map> STATUS_TO_CLASS = new HashMap<>(); + + static { + STATUS_TO_VALUE.put("future", ExtrinsicStatus.Future); + STATUS_TO_VALUE.put("ready", ExtrinsicStatus.Ready); + STATUS_TO_VALUE.put("dropped", ExtrinsicStatus.Dropped); + STATUS_TO_VALUE.put("invalid", ExtrinsicStatus.Invalid); + + STATUS_TO_CLASS.put("broadcast", ExtrinsicStatus.Broadcast.class); + STATUS_TO_CLASS.put("inBlock", ExtrinsicStatus.InBlock.class); + STATUS_TO_CLASS.put("retracted", ExtrinsicStatus.Retracted.class); + STATUS_TO_CLASS.put("finalityTimeout", ExtrinsicStatus.FinalityTimeout.class); + STATUS_TO_CLASS.put("finalized", ExtrinsicStatus.Finalized.class); + STATUS_TO_CLASS.put("usurped", ExtrinsicStatus.Usurped.class); + } + + @Override + @SuppressWarnings("unchecked") + protected ExtrinsicStatus decodeNonNull(Object value, DecoderPair[] decoders) { + Optional decoded; + if (value instanceof String) { + decoded = Optional.ofNullable(STATUS_TO_VALUE.get((String) value)); + } else if (value instanceof Map) { + decoded = ((Map) value).entrySet().stream() + .filter(e -> STATUS_TO_CLASS.containsKey(e.getKey())) + .findFirst() + .map(e -> + (ExtrinsicStatus) RpcDecoderRegistry.getInstance() + .resolve(STATUS_TO_CLASS.get(e.getKey())) + .decode(value) + ); + } else { + decoded = Optional.empty(); + } + + return decoded.orElseThrow(this::unknownStatus); + } + + private RuntimeException unknownStatus() { + return new RuntimeException("Unknown extrinsic status."); + } +} diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/FixedBytesScaleWriter.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/FixedBytesScaleWriter.java new file mode 100644 index 00000000..fc41632b --- /dev/null +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/FixedBytesScaleWriter.java @@ -0,0 +1,22 @@ +package com.strategyobject.substrateclient.rpc.types; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.common.io.Streamer; +import com.strategyobject.substrateclient.scale.ScaleWriter; +import com.strategyobject.substrateclient.scale.annotations.AutoRegister; +import com.strategyobject.substrateclient.types.FixedBytes; +import com.strategyobject.substrateclient.types.Size; +import lombok.NonNull; + +import java.io.IOException; +import java.io.OutputStream; + +@AutoRegister(types = {AccountId.class, BlockHash.class}) +public class FixedBytesScaleWriter implements ScaleWriter> { + @Override + public void write(@NonNull FixedBytes value, @NonNull OutputStream stream, ScaleWriter... writers) throws IOException { + Preconditions.checkArgument(writers == null || writers.length == 0); + + Streamer.writeBytes(value.getData(), stream); + } +} diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/Hash.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/Hash.java index 69f80a7e..418afb73 100644 --- a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/Hash.java +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/Hash.java @@ -1,7 +1,16 @@ package com.strategyobject.substrateclient.rpc.types; -import com.strategyobject.substrateclient.rpc.core.RpcEncoded; import com.strategyobject.substrateclient.scale.ScaleSelfWritable; +import com.strategyobject.substrateclient.types.FixedBytes; +import com.strategyobject.substrateclient.types.Size; +import lombok.NonNull; -public class Hash implements ScaleSelfWritable, RpcEncoded { +public class Hash extends FixedBytes implements ScaleSelfWritable { + private Hash(byte[] data) { + super(data, Size.of32); + } + + public static Hash fromBytes(byte @NonNull [] data) { + return new Hash(data); + } } diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/HashScaleReader.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/HashScaleReader.java new file mode 100644 index 00000000..2f1f7723 --- /dev/null +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/HashScaleReader.java @@ -0,0 +1,21 @@ +package com.strategyobject.substrateclient.rpc.types; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.common.io.Streamer; +import com.strategyobject.substrateclient.scale.ScaleReader; +import com.strategyobject.substrateclient.scale.annotations.AutoRegister; +import com.strategyobject.substrateclient.types.Size; +import lombok.NonNull; + +import java.io.IOException; +import java.io.InputStream; + +@AutoRegister(types = Hash.class) +public class HashScaleReader implements ScaleReader { + @Override + public Hash read(@NonNull InputStream stream, ScaleReader... readers) throws IOException { + Preconditions.checkArgument(readers == null || readers.length == 0); + + return Hash.fromBytes(Streamer.readBytes(Size.of32.getValue(), stream)); + } +} diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/Header.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/Header.java index 4099fb27..bdb99163 100644 --- a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/Header.java +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/Header.java @@ -1,11 +1,14 @@ package com.strategyobject.substrateclient.rpc.types; -import com.strategyobject.substrateclient.rpc.core.RpcEncoded; +import com.strategyobject.substrateclient.rpc.core.annotations.RpcDecoder; +import com.strategyobject.substrateclient.scale.annotations.Scale; import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.Setter; -@RequiredArgsConstructor @Getter -public class Header implements RpcEncoded
{ - private final BlockHash parentHash; +@Setter +@RpcDecoder +public class Header { + @Scale + private BlockHash parentHash; } diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/ImmortalEra.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/ImmortalEra.java index 2c941881..3a16bc5e 100644 --- a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/ImmortalEra.java +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/ImmortalEra.java @@ -1,8 +1,13 @@ package com.strategyobject.substrateclient.rpc.types; +import com.strategyobject.substrateclient.scale.ScaleType; +import com.strategyobject.substrateclient.scale.annotations.Scale; +import com.strategyobject.substrateclient.scale.annotations.ScaleWriter; import lombok.Getter; @Getter +@ScaleWriter public class ImmortalEra implements Era { + @Scale(ScaleType.CompactInteger.class) private final int encoded = 0; } diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/Metadata.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/Metadata.java index 38caf759..638e36d1 100644 --- a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/Metadata.java +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/Metadata.java @@ -1,7 +1,8 @@ package com.strategyobject.substrateclient.rpc.types; -import com.strategyobject.substrateclient.rpc.core.RpcEncoded; import com.strategyobject.substrateclient.scale.ScaleSelfWritable; +import com.strategyobject.substrateclient.scale.annotations.ScaleReader; -public class Metadata implements ScaleSelfWritable, RpcEncoded { +@ScaleReader +public class Metadata implements ScaleSelfWritable { } diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/MortalEra.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/MortalEra.java index 51230cdc..18972730 100644 --- a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/MortalEra.java +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/MortalEra.java @@ -1,9 +1,14 @@ package com.strategyobject.substrateclient.rpc.types; +import com.strategyobject.substrateclient.scale.ScaleType; +import com.strategyobject.substrateclient.scale.annotations.Scale; +import com.strategyobject.substrateclient.scale.annotations.ScaleWriter; import lombok.Getter; @Getter +@ScaleWriter public class MortalEra implements Era { + @Scale(ScaleType.CompactInteger.class) private final int encoded; MortalEra(int encoded) { diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/RuntimeVersion.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/RuntimeVersion.java index 40574cca..2e9647b2 100644 --- a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/RuntimeVersion.java +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/RuntimeVersion.java @@ -1,6 +1,7 @@ package com.strategyobject.substrateclient.rpc.types; -import com.strategyobject.substrateclient.rpc.core.RpcEncoded; +import com.strategyobject.substrateclient.rpc.core.annotations.RpcDecoder; -public class RuntimeVersion implements RpcEncoded { +@RpcDecoder +public class RuntimeVersion { } diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/SignatureKindScaleWriter.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/SignatureKindScaleWriter.java new file mode 100644 index 00000000..46e5c0db --- /dev/null +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/SignatureKindScaleWriter.java @@ -0,0 +1,21 @@ +package com.strategyobject.substrateclient.rpc.types; + +import com.google.common.base.Preconditions; +import com.strategyobject.substrateclient.scale.ScaleWriter; +import com.strategyobject.substrateclient.scale.annotations.AutoRegister; +import com.strategyobject.substrateclient.scale.registries.ScaleWriterRegistry; +import lombok.NonNull; + +import java.io.IOException; +import java.io.OutputStream; + +@AutoRegister(types = SignatureKind.class) +public class SignatureKindScaleWriter implements ScaleWriter { + @Override + @SuppressWarnings("unchecked") + public void write(@NonNull SignatureKind value, @NonNull OutputStream stream, ScaleWriter... writers) throws IOException { + Preconditions.checkArgument(writers == null || writers.length == 0); + + ((ScaleWriter) ScaleWriterRegistry.getInstance().resolve(byte.class)).write(value.getValue(), stream); + } +} diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/SignaturePayload.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/SignaturePayload.java index 3667de32..fc69d962 100644 --- a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/SignaturePayload.java +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/SignaturePayload.java @@ -1,11 +1,13 @@ package com.strategyobject.substrateclient.rpc.types; import com.strategyobject.substrateclient.scale.ScaleSelfWritable; +import com.strategyobject.substrateclient.scale.annotations.ScaleWriter; import lombok.Getter; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @Getter +@ScaleWriter public class SignaturePayload implements ScaleSelfWritable> { private final A address; private final S signature; diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/SignedAdditionalExtra.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/SignedAdditionalExtra.java index 3a67f230..7bac64aa 100644 --- a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/SignedAdditionalExtra.java +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/SignedAdditionalExtra.java @@ -1,18 +1,24 @@ package com.strategyobject.substrateclient.rpc.types; +import com.strategyobject.substrateclient.scale.ScaleType; +import com.strategyobject.substrateclient.scale.annotations.Scale; +import com.strategyobject.substrateclient.scale.annotations.ScaleWriter; import lombok.Getter; import lombok.NonNull; @Getter +@ScaleWriter public class SignedAdditionalExtra implements AdditionalExtra { - private final long specVersion; // as u32 - private final long txVersion; // as u32 + @Scale(ScaleType.U32.class) + private final long specVersion; + @Scale(ScaleType.U32.class) + private final long txVersion; private final BlockHash genesis; private final BlockHash eraBlock; SignedAdditionalExtra(long specVersion, long txVersion, @NonNull BlockHash genesis, @NonNull BlockHash eraBlock) { - this.specVersion = specVersion; - this.txVersion = txVersion; + this.specVersion = (int) specVersion; + this.txVersion = (int) txVersion; this.genesis = genesis; this.eraBlock = eraBlock; } diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/SignedExtra.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/SignedExtra.java index 4faf2835..04ad7c3b 100644 --- a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/SignedExtra.java +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/SignedExtra.java @@ -1,18 +1,31 @@ package com.strategyobject.substrateclient.rpc.types; +import com.strategyobject.substrateclient.scale.ScaleType; +import com.strategyobject.substrateclient.scale.annotations.Ignore; +import com.strategyobject.substrateclient.scale.annotations.Scale; +import com.strategyobject.substrateclient.scale.annotations.ScaleWriter; import lombok.Getter; import lombok.RequiredArgsConstructor; +import java.math.BigInteger; + @RequiredArgsConstructor @Getter +@ScaleWriter public class SignedExtra implements Extra, SignedExtension { - private final long specVersion; // ignore - private final long txVersion; // ignore - private final BlockHash genesis; // ignore - private final BlockHash eraBlock; // ignore + @Ignore + private final long specVersion; + @Ignore + private final long txVersion; + @Ignore + private final BlockHash genesis; + @Ignore + private final BlockHash eraBlock; private final E era; - private final long nonce; - private final long tip; + @Scale(ScaleType.CompactBigInteger.class) + private final BigInteger nonce; + @Scale(ScaleType.CompactBigInteger.class) + private final BigInteger tip; @Override public AdditionalExtra getAdditionalExtra() { diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/SignedPayload.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/SignedPayload.java index db4c2386..de2ceeb7 100644 --- a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/SignedPayload.java +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/SignedPayload.java @@ -1,6 +1,5 @@ package com.strategyobject.substrateclient.rpc.types; -import com.strategyobject.substrateclient.scale.registry.ScaleWriterNotFoundException; import com.strategyobject.substrateclient.types.Signable; import lombok.Getter; import lombok.NonNull; @@ -26,7 +25,7 @@ public class SignedPayload extra.getAdditionalExtra().write(buf); return buf.toByteArray(); - } catch (ScaleWriterNotFoundException | IOException e) { + } catch (IOException e) { throw new RuntimeException(e); } } diff --git a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/Sr25519Signature.java b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/Sr25519Signature.java index 0d10a672..3d8039a6 100644 --- a/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/Sr25519Signature.java +++ b/rpc/rpc-types/src/main/java/com/strategyobject/substrateclient/rpc/types/Sr25519Signature.java @@ -1,10 +1,12 @@ package com.strategyobject.substrateclient.rpc.types; +import com.strategyobject.substrateclient.scale.annotations.ScaleWriter; import com.strategyobject.substrateclient.types.SignatureData; import lombok.Getter; import lombok.NonNull; @Getter +@ScaleWriter public class Sr25519Signature implements Signature { private final SignatureKind kind = SignatureKind.SR25519; private final SignatureData data; diff --git a/rpc/sections/build.gradle b/rpc/sections/build.gradle index 0252c28d..8246bc0a 100644 --- a/rpc/sections/build.gradle +++ b/rpc/sections/build.gradle @@ -13,6 +13,7 @@ dependencies { testCompileOnly project(':common') testCompileOnly project(':transport') testImplementation project(':crypto') + testAnnotationProcessor project(':scale:scale-codegen') testImplementation 'org.testcontainers:testcontainers:1.16.0' testImplementation 'org.testcontainers:junit-jupiter:1.16.0' diff --git a/rpc/sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/Author.java b/rpc/sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/Author.java index a2b78853..017203fe 100644 --- a/rpc/sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/Author.java +++ b/rpc/sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/Author.java @@ -6,6 +6,7 @@ import com.strategyobject.substrateclient.rpc.types.Extrinsic; import com.strategyobject.substrateclient.rpc.types.ExtrinsicStatus; import com.strategyobject.substrateclient.rpc.types.Hash; +import com.strategyobject.substrateclient.scale.annotations.Scale; import com.strategyobject.substrateclient.types.PublicKey; import java.util.concurrent.CompletableFuture; @@ -15,15 +16,16 @@ @RpcInterface(section = "author") public interface Author { @RpcCall(method = "hasKey") - CompletableFuture hasKey(PublicKey publicKey, String keyType); + CompletableFuture hasKey(@Scale PublicKey publicKey, String keyType); @RpcCall(method = "insertKey") - CompletableFuture insertKey(String keyType, String secretUri, PublicKey publicKey); + CompletableFuture insertKey(String keyType, String secretUri, @Scale PublicKey publicKey); @RpcCall(method = "submitExtrinsic") - CompletableFuture submitExtrinsic(Extrinsic extrinsic); + @Scale + CompletableFuture submitExtrinsic(@Scale Extrinsic extrinsic); @RpcSubscription(type = "extrinsicUpdate", subscribeMethod = "submitAndWatchExtrinsic", unsubscribeMethod = "unwatchExtrinsic") - CompletableFuture>> submitAndWatchExtrinsic(Extrinsic extrinsic, + CompletableFuture>> submitAndWatchExtrinsic(@Scale Extrinsic extrinsic, BiConsumer callback); } diff --git a/rpc/sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/Chain.java b/rpc/sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/Chain.java index 33e6b2af..0f530712 100644 --- a/rpc/sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/Chain.java +++ b/rpc/sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/Chain.java @@ -5,6 +5,7 @@ import com.strategyobject.substrateclient.rpc.core.annotations.RpcSubscription; import com.strategyobject.substrateclient.rpc.types.BlockHash; import com.strategyobject.substrateclient.rpc.types.Header; +import com.strategyobject.substrateclient.scale.annotations.Scale; import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; @@ -13,11 +14,13 @@ @RpcInterface(section = "chain") public interface Chain { @RpcCall(method = "getFinalizedHead") + @Scale CompletableFuture getFinalizedHead(); @RpcSubscription(type = "newHead", subscribeMethod = "subscribeNewHead", unsubscribeMethod = "unsubscribeNewHead") CompletableFuture>> subscribeNewHeads(BiConsumer callback); @RpcCall(method = "getBlockHash") + @Scale CompletableFuture getBlockHash(long number); } diff --git a/rpc/sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/State.java b/rpc/sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/State.java index 5612efb5..2f4a00ad 100644 --- a/rpc/sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/State.java +++ b/rpc/sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/State.java @@ -4,6 +4,7 @@ import com.strategyobject.substrateclient.rpc.core.annotations.RpcInterface; import com.strategyobject.substrateclient.rpc.types.Metadata; import com.strategyobject.substrateclient.rpc.types.RuntimeVersion; +import com.strategyobject.substrateclient.scale.annotations.Scale; import java.util.concurrent.CompletableFuture; @@ -13,5 +14,6 @@ public interface State { CompletableFuture getRuntimeVersion(); @RpcCall(method = "getMetadata") + @Scale CompletableFuture getMetadata(); } diff --git a/rpc/sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/AuthorTests.java b/rpc/sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/AuthorTests.java index 4181405f..a898bbfb 100644 --- a/rpc/sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/AuthorTests.java +++ b/rpc/sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/AuthorTests.java @@ -2,28 +2,16 @@ import com.strategyobject.substrateclient.common.utils.HexConverter; import com.strategyobject.substrateclient.crypto.KeyRing; -import com.strategyobject.substrateclient.rpc.codegen.RpcGeneratedSectionFactory; -import com.strategyobject.substrateclient.rpc.codegen.RpcInterfaceInitializationException; -import com.strategyobject.substrateclient.rpc.core.ParameterConverter; -import com.strategyobject.substrateclient.rpc.core.ResultConverter; -import com.strategyobject.substrateclient.rpc.core.RpcEncoder; -import com.strategyobject.substrateclient.rpc.core.RpcEncoderRegistry; +import com.strategyobject.substrateclient.rpc.codegen.sections.RpcGeneratedSectionFactory; +import com.strategyobject.substrateclient.rpc.codegen.sections.RpcInterfaceInitializationException; import com.strategyobject.substrateclient.rpc.sections.substitutes.BalanceTransfer; import com.strategyobject.substrateclient.rpc.types.*; -import com.strategyobject.substrateclient.scale.ScaleWriter; -import com.strategyobject.substrateclient.scale.registry.ScaleWriterNotFoundException; -import com.strategyobject.substrateclient.scale.registry.ScaleWriterRegistry; -import com.strategyobject.substrateclient.scale.writers.CompactIntegerWriter; -import com.strategyobject.substrateclient.scale.writers.U32Writer; -import com.strategyobject.substrateclient.scale.writers.U8Writer; import com.strategyobject.substrateclient.tests.containers.SubstrateVersion; import com.strategyobject.substrateclient.tests.containers.TestSubstrateContainer; import com.strategyobject.substrateclient.transport.ws.WsProvider; import com.strategyobject.substrateclient.types.KeyPair; import com.strategyobject.substrateclient.types.PublicKey; import com.strategyobject.substrateclient.types.Signable; -import lombok.NonNull; -import lombok.SneakyThrows; import lombok.val; import lombok.var; import org.bouncycastle.crypto.digests.Blake2bDigest; @@ -32,20 +20,22 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; +import java.math.BigInteger; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.Matchers.greaterThan; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; @Testcontainers public class AuthorTests { private static final int WAIT_TIMEOUT = 10; private static final Network network = Network.newNetwork(); + private static final AtomicInteger NONCE = new AtomicInteger(0); @Container static final TestSubstrateContainer substrate = new TestSubstrateContainer(SubstrateVersion.V3_0_0) @@ -69,20 +59,7 @@ void hasKey() throws ExecutionException, InterruptedException, TimeoutException, wsProvider.connect().get(WAIT_TIMEOUT, TimeUnit.SECONDS); val sectionFactory = new RpcGeneratedSectionFactory(); - - // TO DO use real converter - ParameterConverter parameterConverter = mock(ParameterConverter.class); - when(parameterConverter.convert(any())) - .thenAnswer(invocation -> "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"); - when(parameterConverter.convert(anyString())) - .thenAnswer(invocation -> invocation.getArgument(0)); - - // TO DO use real converter - ResultConverter resultConverter = mock(ResultConverter.class); - when(resultConverter.convert(any())) - .thenAnswer(invocation -> invocation.getArgument(0)); - - Author rpcSection = sectionFactory.create(Author.class, wsProvider, parameterConverter, resultConverter); + Author rpcSection = sectionFactory.create(Author.class, wsProvider); val publicKey = PublicKey.fromBytes( HexConverter.toBytes("0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d")); @@ -107,20 +84,7 @@ void insertKey() throws ExecutionException, InterruptedException, TimeoutExcepti wsProvider.connect().get(WAIT_TIMEOUT, TimeUnit.SECONDS); val sectionFactory = new RpcGeneratedSectionFactory(); - - // TO DO use real converter - ParameterConverter parameterConverter = mock(ParameterConverter.class); - when(parameterConverter.convert(any())) - .thenAnswer(invocation -> "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"); - when(parameterConverter.convert(anyString())) - .thenAnswer(invocation -> invocation.getArgument(0)); - - // TO DO use real converter - ResultConverter resultConverter = mock(ResultConverter.class); - when(resultConverter.convert(any())) - .thenAnswer(invocation -> invocation.getArgument(0)); - - Author rpcSection = sectionFactory.create(Author.class, wsProvider, parameterConverter, resultConverter); + Author rpcSection = sectionFactory.create(Author.class, wsProvider); assertDoesNotThrow(() -> rpcSection.insertKey("aura", "alice", @@ -132,8 +96,6 @@ void insertKey() throws ExecutionException, InterruptedException, TimeoutExcepti @Test void submitExtrinsic() throws ExecutionException, InterruptedException, TimeoutException, RpcInterfaceInitializationException { - mockRegistries(); - try (WsProvider wsProvider = WsProvider.builder() .setEndpoint(substrate.getWsAddress()) .disableAutoConnect() @@ -141,218 +103,61 @@ void submitExtrinsic() throws ExecutionException, InterruptedException, TimeoutE wsProvider.connect().get(WAIT_TIMEOUT, TimeUnit.SECONDS); val sectionFactory = new RpcGeneratedSectionFactory(); - - // TO DO use real converter - ParameterConverter parameterConverter = mock(ParameterConverter.class); - when(parameterConverter.convert(any())) - .thenAnswer(invocation -> invocation.getArgument(0)); - - // TO DO use real converter - ResultConverter resultConverter = mock(ResultConverter.class); - when(resultConverter.convert(anyString())) - .thenAnswer(invocation -> BlockHash.fromBytes(HexConverter.toBytes(invocation.getArgument(0)))); - - Chain chainSection = sectionFactory.create(Chain.class, wsProvider, parameterConverter, resultConverter); + Chain chainSection = sectionFactory.create(Chain.class, wsProvider); val genesis = chainSection.getBlockHash(0).get(WAIT_TIMEOUT, TimeUnit.SECONDS); + Author authorSection = sectionFactory.create(Author.class, wsProvider); - // TO DO use real converter - parameterConverter = mock(ParameterConverter.class); - when(parameterConverter.convert(any())) - .thenAnswer(invocation -> ((Extrinsic) (invocation.getArgument(0))).encode()); - - // TO DO use real converter - resultConverter = mock(ResultConverter.class); - when(resultConverter.convert(any())) - .thenAnswer(invocation -> new Hash()); - - Author authorSection = sectionFactory.create(Author.class, wsProvider, parameterConverter, resultConverter); - - assertDoesNotThrow(() -> authorSection.submitExtrinsic(createBalanceTransferExtrinsic(genesis)) + assertDoesNotThrow(() -> authorSection.submitExtrinsic(createBalanceTransferExtrinsic(genesis, NONCE.getAndIncrement())) .get(WAIT_TIMEOUT, TimeUnit.SECONDS)); } } - @SneakyThrows - private void mockRegistries() { - ScaleWriterRegistry scaleWriterRegistry = mockScaleWriterRegistry(); - mockStatic(ScaleWriterRegistry.class) - .when(ScaleWriterRegistry::getInstance) - .thenReturn(scaleWriterRegistry); - - RpcEncoderRegistry rpcEncoderRegistry = mockRpcEncoderRegistry(); - mockStatic(RpcEncoderRegistry.class) - .when(RpcEncoderRegistry::getInstance) - .thenReturn(rpcEncoderRegistry); - } - - @SneakyThrows - @SuppressWarnings("rawtypes") - private RpcEncoderRegistry mockRpcEncoderRegistry() { - RpcEncoderRegistry rpcEncoderRegistry = mock(RpcEncoderRegistry.class); - when(rpcEncoderRegistry.resolve(Extrinsic.class)) - .thenReturn(new RpcEncoder() { - @SneakyThrows - @Override - public Object encode(Extrinsic obj) { - val encodedExtrinsic = new ByteArrayOutputStream(); - obj.write(encodedExtrinsic); - - val targetBuf = new ByteArrayOutputStream(); - new CompactIntegerWriter().write(encodedExtrinsic.size(), targetBuf); - targetBuf.write(encodedExtrinsic.toByteArray()); - - return HexConverter.toHex(targetBuf.toByteArray()); - } - }); - - return rpcEncoderRegistry; - } - - @SuppressWarnings({"rawtypes", "unchecked"}) - private ScaleWriterRegistry mockScaleWriterRegistry() throws ScaleWriterNotFoundException { - ScaleWriterRegistry registry = mock(ScaleWriterRegistry.class); - - when(registry.resolve(AddressId.class)).thenReturn((ScaleWriter) getAddressIdScaleWriter()); - - when(registry.resolve(BalanceTransfer.class)).thenReturn((ScaleWriter) getBalanceTransferScaleWriter()); - - when(registry.resolve(ImmortalEra.class)).thenReturn((ScaleWriter) getImmortalEraScaleWriter()); - - when(registry.resolve(SignedExtra.class)).thenReturn((ScaleWriter) getSignedExtraScaleWriter()); - - when(registry.resolve(SignedAdditionalExtra.class)).thenReturn((ScaleWriter) getSignedAdditionalExtraScaleWriter()); - - when(registry.resolve(BlockHash.class)).thenReturn((ScaleWriter) getBlockHashScaleWriter()); - - when(registry.resolve(Extrinsic.class)).thenReturn((ScaleWriter) getExtrinsicScaleWriter()); - - when(registry.resolve(Sr25519Signature.class)).thenReturn((ScaleWriter) getSr25519SignatureScaleWriter()); - - when(registry.resolve(SignaturePayload.class)).thenReturn((ScaleWriter) getSignaturePayloadScaleWriter()); - - return registry; - } - - private ScaleWriter getSr25519SignatureScaleWriter() { - return (value, stream, writers) -> { - new U8Writer().write((int) value.getKind().getValue(), stream); - newByteArrayWriter().write(value.getData().getData(), stream); - }; - } - - @SuppressWarnings("rawtypes") - private ScaleWriter getExtrinsicScaleWriter() { - return new ScaleWriter() { - @SneakyThrows - @Override - public void write(@NonNull Extrinsic value, @NonNull OutputStream stream, ScaleWriter... writers) { - var version = 4; - version = value.getSignature().isPresent() ? version | 0b1000_0000 : version & 0b0111_1111; - new U8Writer().write(version, stream); - - if (value.getSignature().isPresent()) { - val signature = (SignaturePayload) value.getSignature().get(); - signature.write(stream); - } - - value.getCall().write(stream); - } - }; - } - - private ScaleWriter getBlockHashScaleWriter() { - return (value, stream, writers) -> newByteArrayWriter().write(value.getData(), stream); - } - - private ScaleWriter getSignedAdditionalExtraScaleWriter() { - return new ScaleWriter() { - @SneakyThrows - @Override - public void write(@NonNull SignedAdditionalExtra value, @NonNull OutputStream stream, ScaleWriter... writers) { - val writer = new U32Writer(); - - writer.write(value.getSpecVersion(), stream); - writer.write(value.getTxVersion(), stream); - value.getGenesis().write(stream); - value.getEraBlock().write(stream); - } - }; - } + @Test + void submitAndWatchExtrinsic() throws ExecutionException, InterruptedException, TimeoutException, RpcInterfaceInitializationException { + try (WsProvider wsProvider = WsProvider.builder() + .setEndpoint(substrate.getWsAddress()) + .disableAutoConnect() + .build()) { + wsProvider.connect().get(WAIT_TIMEOUT, TimeUnit.SECONDS); - @SuppressWarnings("rawtypes") - private ScaleWriter getSignedExtraScaleWriter() { - return new ScaleWriter() { - @SneakyThrows - @Override - public void write(@NonNull SignedExtra value, @NonNull OutputStream stream, ScaleWriter... writers) { - val writer = new CompactIntegerWriter(); - value.getEra().write(stream); - writer.write((int) value.getNonce(), stream); - writer.write((int) value.getTip(), stream); - } - }; - } + val sectionFactory = new RpcGeneratedSectionFactory(); + Chain chainSection = sectionFactory.create(Chain.class, wsProvider); - private ScaleWriter getImmortalEraScaleWriter() { - return (value, stream, writers) -> new U8Writer().write(value.getEncoded(), stream); - } + val genesis = chainSection.getBlockHash(0).get(WAIT_TIMEOUT, TimeUnit.SECONDS); + Author authorSection = sectionFactory.create(Author.class, wsProvider); - private ScaleWriter getBalanceTransferScaleWriter() { - return new ScaleWriter() { - @SneakyThrows - @Override - public void write(@NonNull BalanceTransfer value, @NonNull OutputStream stream, ScaleWriter... writers) { - val u8writer = new U8Writer(); - val compactWriter = new CompactIntegerWriter(); - - u8writer.write(value.getModuleIndex(), stream); - u8writer.write(value.getCallIndex(), stream); - value.getDestination().write(stream); - compactWriter.write((int) value.getAmount(), stream); - } - }; - } + val updateCount = new AtomicInteger(0); + val status = new AtomicReference(); + val unsubscribe = authorSection.submitAndWatchExtrinsic( + createBalanceTransferExtrinsic(genesis, NONCE.getAndIncrement()), + (exception, extrinsicStatus) -> { + updateCount.incrementAndGet(); + status.set(extrinsicStatus); + }) + .get(WAIT_TIMEOUT, TimeUnit.SECONDS); - private ScaleWriter getAddressIdScaleWriter() { - return new ScaleWriter() { - @Override - public void write(@NonNull AddressId value, @NonNull OutputStream stream, ScaleWriter... writers) throws IOException { - val u8writer = new U8Writer(); + await() + .atMost(WAIT_TIMEOUT * 2, TimeUnit.SECONDS) + .untilAtomic(updateCount, greaterThan(0)); - u8writer.write((int) value.getKind().getValue(), stream); - newByteArrayWriter().write(value.getAddress().getData(), stream); - } - }; - } + assertNotNull(status.get()); - @SuppressWarnings("rawtypes") - private ScaleWriter getSignaturePayloadScaleWriter() { - return new ScaleWriter() { - @SneakyThrows - @Override - public void write(@NonNull SignaturePayload value, @NonNull OutputStream stream, ScaleWriter... writers) { - value.getAddress().write(stream); - value.getSignature().write(stream); - value.getExtra().write(stream); - } - }; - } + val result = unsubscribe.get().get(WAIT_TIMEOUT, TimeUnit.SECONDS); - private ScaleWriter newByteArrayWriter() { - return (value, stream, writers) -> stream.write(value); + assertTrue(result); + } } - private Extrinsic createBalanceTransferExtrinsic(BlockHash genesis) { + private Extrinsic createBalanceTransferExtrinsic(BlockHash genesis, int nonce) { val specVersion = 264; val txVersion = 2; - val moduleIndex = 6; - val callIndex = 0; - val nonce = 0; + val moduleIndex = (byte) 6; + val callIndex = (byte) 0; val tip = 0; - val call = new BalanceTransfer(moduleIndex, callIndex, AddressId.fromBytes(bobKeyPair().asPublicKey().getData()), 10); + val call = new BalanceTransfer(moduleIndex, callIndex, AddressId.fromBytes(bobKeyPair().asPublicKey().getData()), BigInteger.valueOf(10)); - val extra = new SignedExtra<>(specVersion, txVersion, genesis, genesis, new ImmortalEra(), nonce, tip); + val extra = new SignedExtra<>(specVersion, txVersion, genesis, genesis, new ImmortalEra(), BigInteger.valueOf(nonce), BigInteger.valueOf(tip)); val signedPayload = new SignedPayload<>(call, extra); val keyRing = KeyRing.fromKeyPair(aliceKeyPair()); diff --git a/rpc/sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/ChainTests.java b/rpc/sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/ChainTests.java index 3ce5a61b..f3fd0897 100644 --- a/rpc/sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/ChainTests.java +++ b/rpc/sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/ChainTests.java @@ -1,12 +1,8 @@ package com.strategyobject.substrateclient.rpc.sections; -import com.strategyobject.substrateclient.common.utils.HexConverter; -import com.strategyobject.substrateclient.rpc.codegen.RpcInterfaceInitializationException; -import com.strategyobject.substrateclient.rpc.codegen.RpcGeneratedSectionFactory; -import com.strategyobject.substrateclient.rpc.core.ParameterConverter; -import com.strategyobject.substrateclient.rpc.core.ResultConverter; +import com.strategyobject.substrateclient.rpc.codegen.sections.RpcGeneratedSectionFactory; +import com.strategyobject.substrateclient.rpc.codegen.sections.RpcInterfaceInitializationException; import com.strategyobject.substrateclient.rpc.types.BlockHash; -import com.strategyobject.substrateclient.rpc.types.Header; import com.strategyobject.substrateclient.tests.containers.SubstrateVersion; import com.strategyobject.substrateclient.tests.containers.TestSubstrateContainer; import com.strategyobject.substrateclient.transport.ws.WsProvider; @@ -17,7 +13,6 @@ import org.testcontainers.junit.jupiter.Testcontainers; import java.math.BigInteger; -import java.util.AbstractMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -28,10 +23,6 @@ import static org.hamcrest.Matchers.greaterThan; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; @Testcontainers public class ChainTests { @@ -51,18 +42,7 @@ void getFinalizedHead() throws ExecutionException, InterruptedException, Timeout wsProvider.connect().get(WAIT_TIMEOUT, TimeUnit.SECONDS); val sectionFactory = new RpcGeneratedSectionFactory(); - - // TO DO use real converter - ParameterConverter parameterConverter = mock(ParameterConverter.class); - when(parameterConverter.convert(anyString())) - .thenAnswer(invocation -> invocation); - - // TO DO use real converter - ResultConverter resultConverter = mock(ResultConverter.class); - when(resultConverter.convert(anyString())) - .thenAnswer(invocation -> BlockHash.fromBytes(HexConverter.toBytes(invocation.getArgument(0)))); - - Chain rpcSection = sectionFactory.create(Chain.class, wsProvider, parameterConverter, resultConverter); + Chain rpcSection = sectionFactory.create(Chain.class, wsProvider); BlockHash result = rpcSection.getFinalizedHead().get(WAIT_TIMEOUT, TimeUnit.SECONDS); @@ -79,22 +59,7 @@ void subscribeNewHeads() throws ExecutionException, InterruptedException, Timeou wsProvider.connect().get(WAIT_TIMEOUT, TimeUnit.SECONDS); val sectionFactory = new RpcGeneratedSectionFactory(); - - ParameterConverter parameterConverter = mock(ParameterConverter.class); - when(parameterConverter.convert(anyString())) - .thenAnswer(invocation -> { - throw new RuntimeException("Mustn't be called."); - }); - - // TO DO use real converter - ResultConverter resultConverter = mock(ResultConverter.class); - when(resultConverter., Header>convert(any())) - .thenAnswer(invocation -> new Header( - BlockHash.fromBytes( - HexConverter.toBytes((String) ((AbstractMap) invocation.getArgument(0)).get("parentHash")) - ))); - - Chain rpcSection = sectionFactory.create(Chain.class, wsProvider, parameterConverter, resultConverter); + Chain rpcSection = sectionFactory.create(Chain.class, wsProvider); val blockCount = new AtomicInteger(0); val blockHash = new AtomicReference(null); @@ -128,18 +93,7 @@ void getBlockHash() throws ExecutionException, InterruptedException, TimeoutExce wsProvider.connect().get(WAIT_TIMEOUT, TimeUnit.SECONDS); val sectionFactory = new RpcGeneratedSectionFactory(); - - // TO DO use real converter - ParameterConverter parameterConverter = mock(ParameterConverter.class); - when(parameterConverter.convert(any())) - .thenAnswer(invocation -> invocation.getArgument(0)); - - // TO DO use real converter - ResultConverter resultConverter = mock(ResultConverter.class); - when(resultConverter.convert(anyString())) - .thenAnswer(invocation -> BlockHash.fromBytes(HexConverter.toBytes(invocation.getArgument(0)))); - - Chain rpcSection = sectionFactory.create(Chain.class, wsProvider, parameterConverter, resultConverter); + Chain rpcSection = sectionFactory.create(Chain.class, wsProvider); BlockHash result = rpcSection.getBlockHash(0).get(WAIT_TIMEOUT, TimeUnit.SECONDS); diff --git a/rpc/sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/StateTests.java b/rpc/sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/StateTests.java index fe6ffc89..9706b904 100644 --- a/rpc/sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/StateTests.java +++ b/rpc/sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/StateTests.java @@ -1,11 +1,7 @@ package com.strategyobject.substrateclient.rpc.sections; -import com.strategyobject.substrateclient.rpc.codegen.RpcInterfaceInitializationException; -import com.strategyobject.substrateclient.rpc.codegen.RpcGeneratedSectionFactory; -import com.strategyobject.substrateclient.rpc.core.ParameterConverter; -import com.strategyobject.substrateclient.rpc.core.ResultConverter; -import com.strategyobject.substrateclient.rpc.types.Metadata; -import com.strategyobject.substrateclient.rpc.types.RuntimeVersion; +import com.strategyobject.substrateclient.rpc.codegen.sections.RpcGeneratedSectionFactory; +import com.strategyobject.substrateclient.rpc.codegen.sections.RpcInterfaceInitializationException; import com.strategyobject.substrateclient.tests.containers.SubstrateVersion; import com.strategyobject.substrateclient.tests.containers.TestSubstrateContainer; import com.strategyobject.substrateclient.transport.ws.WsProvider; @@ -20,9 +16,6 @@ import java.util.concurrent.TimeoutException; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; @Testcontainers public class StateTests { @@ -42,18 +35,7 @@ void getRuntimeVersion() throws ExecutionException, InterruptedException, Timeou wsProvider.connect().get(WAIT_TIMEOUT, TimeUnit.SECONDS); val sectionFactory = new RpcGeneratedSectionFactory(); - - // TO DO use real converter - ParameterConverter parameterConverter = mock(ParameterConverter.class); - when(parameterConverter.convert(any())) - .thenAnswer(invocation -> invocation); - - // TO DO use real converter - ResultConverter resultConverter = mock(ResultConverter.class); - when(resultConverter.convert(any())) - .thenAnswer(invocation -> new RuntimeVersion()); - - State rpcSection = sectionFactory.create(State.class, wsProvider, parameterConverter, resultConverter); + State rpcSection = sectionFactory.create(State.class, wsProvider); assertDoesNotThrow(() -> { rpcSection.getRuntimeVersion().get(WAIT_TIMEOUT, TimeUnit.SECONDS); @@ -70,18 +52,7 @@ void getMetadata() throws ExecutionException, InterruptedException, TimeoutExcep wsProvider.connect().get(WAIT_TIMEOUT, TimeUnit.SECONDS); val sectionFactory = new RpcGeneratedSectionFactory(); - - // TO DO use real converter - ParameterConverter parameterConverter = mock(ParameterConverter.class); - when(parameterConverter.convert(any())) - .thenAnswer(invocation -> invocation); - - // TO DO use real converter - ResultConverter resultConverter = mock(ResultConverter.class); - when(resultConverter.convert(any())) - .thenAnswer(invocation -> new Metadata()); - - State rpcSection = sectionFactory.create(State.class, wsProvider, parameterConverter, resultConverter); + State rpcSection = sectionFactory.create(State.class, wsProvider); assertDoesNotThrow(() -> { rpcSection.getMetadata().get(WAIT_TIMEOUT, TimeUnit.SECONDS); diff --git a/rpc/sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/SystemTests.java b/rpc/sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/SystemTests.java index 47091500..a3802ea1 100644 --- a/rpc/sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/SystemTests.java +++ b/rpc/sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/SystemTests.java @@ -1,9 +1,8 @@ package com.strategyobject.substrateclient.rpc.sections; -import com.strategyobject.substrateclient.rpc.codegen.RpcInterfaceInitializationException; -import com.strategyobject.substrateclient.rpc.codegen.RpcGeneratedSectionFactory; -import com.strategyobject.substrateclient.rpc.core.ParameterConverter; -import com.strategyobject.substrateclient.rpc.core.ResultConverter; +import com.strategyobject.substrateclient.rpc.codegen.sections.RpcGeneratedSectionFactory; +import com.strategyobject.substrateclient.rpc.codegen.sections.RpcInterfaceInitializationException; +import com.strategyobject.substrateclient.rpc.core.registries.RpcEncoderRegistry; import com.strategyobject.substrateclient.rpc.types.AccountId; import com.strategyobject.substrateclient.tests.containers.SubstrateVersion; import com.strategyobject.substrateclient.tests.containers.TestSubstrateContainer; @@ -19,9 +18,7 @@ import java.util.concurrent.TimeoutException; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; @Testcontainers public class SystemTests { @@ -41,24 +38,23 @@ void accountNextIndex() throws ExecutionException, InterruptedException, Timeout wsProvider.connect().get(WAIT_TIMEOUT, TimeUnit.SECONDS); val sectionFactory = new RpcGeneratedSectionFactory(); + System rpcSection = sectionFactory.create(System.class, wsProvider); - // TO DO use real converter - ParameterConverter parameterConverter = mock(ParameterConverter.class); - when(parameterConverter.convert(any())) - .thenAnswer(invocation -> "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"); + // TO DO use registered converter + RpcEncoderRegistry encoderRegistry = mock(RpcEncoderRegistry.class); + when(encoderRegistry.resolve(AccountId.class)) + .thenReturn((source, encoders) -> "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"); + mockStatic(RpcEncoderRegistry.class) + .when(RpcEncoderRegistry::getInstance) + .thenReturn(encoderRegistry); - // TO DO use real converter - ResultConverter resultConverter = mock(ResultConverter.class); - when(resultConverter.convert(any())) - .thenAnswer(invocation -> ((Double) invocation.getArgument(0)).intValue()); - System rpcSection = sectionFactory.create(System.class, wsProvider, parameterConverter, resultConverter); - - val result = rpcSection.accountNextIndex(AccountId.fromBytes(new byte[]{ + val result = rpcSection.accountNextIndex(AccountId.fromBytes( + new byte[]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 })).get(WAIT_TIMEOUT, TimeUnit.SECONDS); - assertEquals(result, 0); + assertEquals(0, result); } } } diff --git a/rpc/sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/substitutes/BalanceTransfer.java b/rpc/sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/substitutes/BalanceTransfer.java index 84019f01..1c27ee97 100644 --- a/rpc/sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/substitutes/BalanceTransfer.java +++ b/rpc/sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/substitutes/BalanceTransfer.java @@ -2,14 +2,21 @@ import com.strategyobject.substrateclient.rpc.types.AddressId; import com.strategyobject.substrateclient.rpc.types.Call; +import com.strategyobject.substrateclient.scale.ScaleType; +import com.strategyobject.substrateclient.scale.annotations.Scale; +import com.strategyobject.substrateclient.scale.annotations.ScaleWriter; import lombok.Getter; import lombok.RequiredArgsConstructor; +import java.math.BigInteger; + @RequiredArgsConstructor @Getter +@ScaleWriter public class BalanceTransfer implements Call { - private final int moduleIndex; - private final int callIndex; + private final byte moduleIndex; + private final byte callIndex; private final AddressId destination; - private final long amount; + @Scale(ScaleType.CompactBigInteger.class) + private final BigInteger amount; } diff --git a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/ProcessorContext.java b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/ProcessorContext.java deleted file mode 100644 index 73aa79ef..00000000 --- a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/ProcessorContext.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.strategyobject.substrateclient.scale.codegen; - -import com.strategyobject.substrateclient.scale.ScaleSelfWritable; -import com.strategyobject.substrateclient.scale.annotations.Default; -import lombok.Getter; - -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; - -@Getter -public class ProcessorContext { - public static final String SCALE_SELF_WRITABLE = ScaleSelfWritable.class.getCanonicalName(); - public static final String SCALE_ANNOTATIONS_DEFAULT = Default.class.getCanonicalName(); - private static final String READER_NAME_TEMPLATE = "%sReader"; - private static final String WRITER_NAME_TEMPLATE = "%sWriter"; - - private final Types typeUtils; - private final Elements elementUtils; - private final TypeMirror scaleSelfWritableRawType; - private final TypeMirror scaleAnnotationsDefaultType; - - public ProcessorContext(Types typeUtils, Elements elementUtils) { - this.typeUtils = typeUtils; - this.elementUtils = elementUtils; - this.scaleSelfWritableRawType = typeUtils.erasure(elementUtils.getTypeElement(SCALE_SELF_WRITABLE).asType()); - this.scaleAnnotationsDefaultType = elementUtils.getTypeElement(SCALE_ANNOTATIONS_DEFAULT).asType(); - } - - public String getPackageName(TypeElement classElement) { - return elementUtils.getPackageOf(classElement).getQualifiedName().toString(); - } - - public String getReaderName(String className) { - return String.format(READER_NAME_TEMPLATE, className); - } - - public String getWriterName(String className) { - return String.format(WRITER_NAME_TEMPLATE, className); - } - - public boolean isSubtypeOf(TypeMirror candidate, TypeMirror supertype) { - return typeUtils.isAssignable(candidate, supertype); - } - - public boolean isSubtypeOfScaleAnnotationsDefault(TypeMirror type) { - return isSubtypeOf(type, scaleAnnotationsDefaultType); - } - - public boolean isSubtypeOfScaleSelfWritable(TypeMirror type) { - return isSubtypeOf(type, scaleSelfWritableRawType); - } - - public boolean isGeneric(TypeMirror type) { - return ((TypeElement) typeUtils.asElement(type)) - .getTypeParameters() - .size() > 0; - } -} 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 5cc5a539..6ee8fe64 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 @@ -2,6 +2,7 @@ import com.google.common.base.Strings; 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.utils.StringUtils; import com.strategyobject.substrateclient.scale.annotations.Scale; @@ -10,8 +11,8 @@ import lombok.val; import lombok.var; +import javax.lang.model.AnnotatedConstruct; import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import java.util.HashMap; @@ -19,20 +20,22 @@ import java.util.Map; import java.util.Objects; +import static com.strategyobject.substrateclient.scale.codegen.ScaleProcessorHelper.SCALE_ANNOTATIONS_DEFAULT; + public class ScaleAnnotationParser { private final ProcessorContext context; - public ScaleAnnotationParser(ProcessorContext context) { + public ScaleAnnotationParser(@NonNull ProcessorContext context) { this.context = context; } - public TypeTraverser.TypeTreeNode parse(@NonNull VariableElement field) { - val scaleType = AnnotationUtils.getValueFromAnnotation(field, Scale.class, "value"); + public TypeTraverser.TypeTreeNode parse(@NonNull AnnotatedConstruct annotated) { + val scaleType = AnnotationUtils.getValueFromAnnotation(annotated, Scale.class, "value"); if (scaleType != null) { return new TypeTraverser.TypeTreeNode(scaleType); } - val scaleGeneric = AnnotationUtils.getAnnotationMirror(field, ScaleGeneric.class); + val scaleGeneric = AnnotationUtils.getAnnotationMirror(annotated, ScaleGeneric.class); if (scaleGeneric != null) { val template = AnnotationUtils.getValueFromAnnotation(scaleGeneric, "template"); val typesMap = getTypesMap(scaleGeneric); @@ -89,7 +92,7 @@ private TypeTraverser.TypeTreeNode parseTemplate(String template, Map typesMap, String name) { val type = typesMap.get(name); - return type == null || context.isSubtypeOfScaleAnnotationsDefault(type) ? null : type; + return type == null || context.isSubtypeOf(type, context.getType(SCALE_ANNOTATIONS_DEFAULT)) ? null : type; } private Map getTypesMap(AnnotationMirror scaleGeneric) { @@ -101,7 +104,7 @@ private Map getTypesMap(AnnotationMirror scaleGeneric) { validateScaleAnnotationIsNotEmpty(name, type); if (type == null) { - type = context.getScaleAnnotationsDefaultType(); + type = context.getType(SCALE_ANNOTATIONS_DEFAULT); } if (Strings.isNullOrEmpty(name)) { diff --git a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/ScaleProcessorHelper.java b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/ScaleProcessorHelper.java new file mode 100644 index 00000000..17af269a --- /dev/null +++ b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/ScaleProcessorHelper.java @@ -0,0 +1,19 @@ +package com.strategyobject.substrateclient.scale.codegen; + +import com.strategyobject.substrateclient.scale.ScaleSelfWritable; +import com.strategyobject.substrateclient.scale.annotations.Default; + +public class ScaleProcessorHelper { + public static final Class SCALE_SELF_WRITABLE = ScaleSelfWritable.class; + public static final Class SCALE_ANNOTATIONS_DEFAULT = Default.class; + private static final String READER_NAME_TEMPLATE = "%sReader"; + private static final String WRITER_NAME_TEMPLATE = "%sWriter"; + + public static String getReaderName(String className) { + return String.format(READER_NAME_TEMPLATE, className); + } + + public static String getWriterName(String className) { + return String.format(WRITER_NAME_TEMPLATE, className); + } +} diff --git a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/TypeReadGenerator.java b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/ReaderCompositor.java similarity index 51% rename from scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/TypeReadGenerator.java rename to scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/ReaderCompositor.java index c95eebb2..8a795d5a 100644 --- a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/TypeReadGenerator.java +++ b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/ReaderCompositor.java @@ -1,37 +1,54 @@ package com.strategyobject.substrateclient.scale.codegen.reader; import com.squareup.javapoet.CodeBlock; +import com.strategyobject.substrateclient.common.codegen.ProcessorContext; import com.strategyobject.substrateclient.common.codegen.TypeTraverser; -import com.strategyobject.substrateclient.scale.codegen.ProcessorContext; 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 java.util.Map; -public class TypeReadGenerator extends TypeTraverser { +public class ReaderCompositor extends TypeTraverser { private final ProcessorContext context; private final Map typeVarMap; + private final String readerAccessor; + private final String registryVarName; - public TypeReadGenerator(Class clazz, ProcessorContext context, Map typeVarMap) { - super(clazz); + public ReaderCompositor(@NonNull ProcessorContext context, + @NonNull Map typeVarMap, + @NonNull String readerAccessor, + @NonNull String registryVarName) { + super(CodeBlock.class); this.context = context; this.typeVarMap = typeVarMap; + this.readerAccessor = readerAccessor; + this.registryVarName = registryVarName; } @Override protected CodeBlock whenTypeVar(@NonNull TypeVariable type, TypeMirror _override) { return CodeBlock.builder() - .add("readers[$L]", typeVarMap.get(type.toString())) + .add(readerAccessor, typeVarMap.get(type.toString())) .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("registry.resolve($T.class)", override != null ? override : type) + .add("$L.resolve($T.class)", registryVarName, override != null ? override : type) .build(); } @@ -41,16 +58,16 @@ protected CodeBlock whenGenericType(@NonNull DeclaredType type, TypeMirror overr if (override != null) { if (!context.isGeneric(override)) { return CodeBlock.builder() - .add("registry.resolve($T.class)", override) + .add("$L.resolve($T.class)", registryVarName, override) .build(); } resolveType = override; } else { - resolveType = context.getTypeUtils().erasure(type); + resolveType = context.erasure(type); } - var builder = CodeBlock.builder().add("registry.resolve($T.class).inject(", resolveType); + 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]); diff --git a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/ScaleReaderAnnotatedClass.java b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/ScaleReaderAnnotatedClass.java index f32ea2e9..de1d5664 100644 --- a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/ScaleReaderAnnotatedClass.java +++ b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/ScaleReaderAnnotatedClass.java @@ -2,18 +2,18 @@ import com.squareup.javapoet.*; import com.strategyobject.substrateclient.common.codegen.JavaPoet; +import com.strategyobject.substrateclient.common.codegen.ProcessingException; +import com.strategyobject.substrateclient.common.codegen.ProcessorContext; import com.strategyobject.substrateclient.scale.ScaleReader; import com.strategyobject.substrateclient.scale.annotations.AutoRegister; import com.strategyobject.substrateclient.scale.annotations.Ignore; -import com.strategyobject.substrateclient.scale.codegen.ProcessingException; -import com.strategyobject.substrateclient.scale.codegen.ProcessorContext; +import com.strategyobject.substrateclient.scale.codegen.ScaleProcessorHelper; import com.strategyobject.substrateclient.scale.codegen.ScaleAnnotationParser; -import com.strategyobject.substrateclient.scale.registry.ScaleReaderRegistry; +import com.strategyobject.substrateclient.scale.registries.ScaleReaderRegistry; import lombok.NonNull; import lombok.val; import lombok.var; -import javax.annotation.processing.Filer; import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; @@ -26,14 +26,18 @@ import java.util.function.Function; import java.util.stream.IntStream; +import static com.strategyobject.substrateclient.common.codegen.AnnotationUtils.suppressWarnings; +import static com.strategyobject.substrateclient.common.codegen.TypeUtils.getSetterName; import static java.util.stream.Collectors.toMap; public class ScaleReaderAnnotatedClass { + private static final String READERS_ARG = "readers"; + private static final String REGISTRY = "registry"; private final TypeElement classElement; private final Map typeVarMap; - public ScaleReaderAnnotatedClass(TypeElement classElement) { + public ScaleReaderAnnotatedClass(@NonNull TypeElement classElement) { this.classElement = classElement; val typeParameters = classElement.getTypeParameters(); this.typeVarMap = IntStream.range(0, typeParameters.size()) @@ -41,15 +45,13 @@ public ScaleReaderAnnotatedClass(TypeElement classElement) { .collect(toMap(i -> typeParameters.get(i).toString(), Function.identity())); } - public void generateReader(@NonNull ProcessorContext context, - @NonNull Filer filer) throws IOException, ProcessingException { - val className = classElement.getSimpleName().toString(); - val readerName = context.getReaderName(className); + public void generateReader(@NonNull ProcessorContext context) throws IOException, ProcessingException { + val readerName = ScaleProcessorHelper.getReaderName(classElement.getSimpleName().toString()); val classWildcardTyped = JavaPoet.setEachGenericParameterAsWildcard(classElement); val typeSpecBuilder = TypeSpec.classBuilder(readerName) .addAnnotation(AnnotationSpec.builder(AutoRegister.class) - .addMember("types", "{$L.class}", className) + .addMember("types", "{$L.class}", classElement.getQualifiedName().toString()) .build()) .addModifiers(Modifier.PUBLIC) .addSuperinterface(ParameterizedTypeName.get(ClassName.get(ScaleReader.class), classWildcardTyped)) @@ -58,13 +60,14 @@ public void generateReader(@NonNull ProcessorContext context, JavaFile.builder( context.getPackageName(classElement), typeSpecBuilder.build() - ).build().writeTo(filer); + ).build().writeTo(context.getFiler()); } private MethodSpec generateReadMethod(TypeName classWildcardTyped, ProcessorContext context) throws ProcessingException { val methodSpec = MethodSpec.methodBuilder("read") .addAnnotation(Override.class) + .addAnnotation(suppressWarnings("unchecked")) .addModifiers(Modifier.PUBLIC) .returns(classWildcardTyped) .addParameter(InputStream.class, "stream") @@ -72,7 +75,7 @@ private MethodSpec generateReadMethod(TypeName classWildcardTyped, ParameterizedTypeName.get( ClassName.get(ScaleReader.class), WildcardTypeName.subtypeOf(Object.class))), - "readers") + READERS_ARG) .varargs(true) .addException(IOException.class); @@ -101,17 +104,17 @@ private void addMethodBody(MethodSpec.Builder methodSpec, ProcessorContext context) throws ProcessingException { val resultType = JavaPoet.setEachGenericParameterAs(classElement, TypeName.OBJECT); methodSpec - .addStatement("$1T registry = $1T.getInstance()", ScaleReaderRegistry.class) + .addStatement("$1T $2L = $1T.getInstance()", ScaleReaderRegistry.class, REGISTRY) .addStatement("$1T result = new $1T()", resultType) .beginControlFlow("try"); val scaleAnnotationParser = new ScaleAnnotationParser(context); - val generator = new TypeReadGenerator(CodeBlock.class, context, typeVarMap); + val compositor = new ReaderCompositor(context, typeVarMap, String.format("%s[$L]", READERS_ARG), REGISTRY); for (Element element : classElement.getEnclosedElements()) { if (element instanceof VariableElement) { val field = (VariableElement) element; if (field.getAnnotation(Ignore.class) == null) { - setField(methodSpec, field, context.getTypeUtils(), scaleAnnotationParser, generator); + setField(methodSpec, field, context.getTypeUtils(), scaleAnnotationParser, compositor); } } } @@ -127,21 +130,22 @@ private void setField(MethodSpec.Builder methodSpec, VariableElement field, Types typeUtils, ScaleAnnotationParser scaleAnnotationParser, - TypeReadGenerator generator) throws ProcessingException { + ReaderCompositor compositor) throws ProcessingException { try { val fieldType = field.asType(); val code = CodeBlock.builder(); + val setterName = getSetterName(field.getSimpleName().toString()); if (fieldType instanceof TypeVariable) { - code.add("result.$L = ", field); + code.add("result.$L(", setterName); } else { - code.add("result.$L = ($T) ", field, typeUtils.erasure(fieldType)); + code.add("result.$L(($T) ", setterName, typeUtils.erasure(fieldType)); } val typeOverride = scaleAnnotationParser.parse(field); val readerCode = typeOverride != null ? - generator.traverse(fieldType, typeOverride) : - generator.traverse(fieldType); - methodSpec.addStatement(code.add(readerCode).add(".read(stream)").build()); + compositor.traverse(fieldType, typeOverride) : + compositor.traverse(fieldType); + methodSpec.addStatement(code.add(readerCode).add(".read(stream))").build()); } catch (Exception e) { throw new ProcessingException(e, field, e.getMessage()); } diff --git a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/ScaleReaderProcessor.java b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/ScaleReaderProcessor.java index 7c869ab2..4222f718 100644 --- a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/ScaleReaderProcessor.java +++ b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/ScaleReaderProcessor.java @@ -1,17 +1,15 @@ package com.strategyobject.substrateclient.scale.codegen.reader; import com.google.auto.service.AutoService; +import com.strategyobject.substrateclient.common.codegen.ProcessingException; +import com.strategyobject.substrateclient.common.codegen.ProcessorContext; import com.strategyobject.substrateclient.scale.annotations.ScaleReader; -import com.strategyobject.substrateclient.scale.codegen.ProcessingException; -import com.strategyobject.substrateclient.scale.codegen.ProcessorContext; import lombok.val; import javax.annotation.processing.*; import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; -import javax.tools.Diagnostic; import java.io.IOException; import java.util.Set; @@ -20,17 +18,14 @@ @AutoService(Processor.class) public class ScaleReaderProcessor extends AbstractProcessor { private ProcessorContext context; - private Filer filer; - private Messager messager; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); - filer = processingEnv.getFiler(); - messager = processingEnv.getMessager(); - context = new ProcessorContext( - processingEnv.getTypeUtils(), - processingEnv.getElementUtils()); + context = new ProcessorContext(processingEnv.getTypeUtils(), + processingEnv.getElementUtils(), + processingEnv.getFiler(), + processingEnv.getMessager()); } @Override @@ -41,7 +36,7 @@ public boolean process(Set annotations, RoundEnvironment for (val annotatedElement : roundEnv.getElementsAnnotatedWith(ScaleReader.class)) { if (annotatedElement.getKind() != ElementKind.CLASS) { - error( + context.error( annotatedElement, "Only classes can be annotated with `@%s`.", ScaleReader.class.getSimpleName()); @@ -51,24 +46,16 @@ public boolean process(Set annotations, RoundEnvironment val typeElement = (TypeElement) annotatedElement; try { - new ScaleReaderAnnotatedClass(typeElement).generateReader(context, filer); + new ScaleReaderAnnotatedClass(typeElement).generateReader(context); } catch (ProcessingException e) { - error(typeElement, e.getMessage()); + context.error(typeElement, e); return true; } catch (IOException e) { - error(null, e.getMessage()); + context.error(e); return true; } } return true; } - - private void error(Element e, String message, Object... args) { - messager.printMessage( - Diagnostic.Kind.ERROR, - String.format(message, args), - e - ); - } } diff --git a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/ScaleWriterAnnotatedClass.java b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/ScaleWriterAnnotatedClass.java index efbcf95f..57d251a0 100644 --- a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/ScaleWriterAnnotatedClass.java +++ b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/ScaleWriterAnnotatedClass.java @@ -2,18 +2,18 @@ import com.squareup.javapoet.*; import com.strategyobject.substrateclient.common.codegen.JavaPoet; +import com.strategyobject.substrateclient.common.codegen.ProcessingException; +import com.strategyobject.substrateclient.common.codegen.ProcessorContext; import com.strategyobject.substrateclient.scale.ScaleWriter; import com.strategyobject.substrateclient.scale.annotations.AutoRegister; import com.strategyobject.substrateclient.scale.annotations.Ignore; -import com.strategyobject.substrateclient.scale.codegen.ProcessingException; -import com.strategyobject.substrateclient.scale.codegen.ProcessorContext; import com.strategyobject.substrateclient.scale.codegen.ScaleAnnotationParser; -import com.strategyobject.substrateclient.scale.registry.ScaleWriterRegistry; +import com.strategyobject.substrateclient.scale.codegen.ScaleProcessorHelper; +import com.strategyobject.substrateclient.scale.registries.ScaleWriterRegistry; import lombok.NonNull; import lombok.val; import lombok.var; -import javax.annotation.processing.Filer; import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; @@ -24,9 +24,14 @@ import java.util.function.Function; import java.util.stream.IntStream; +import static com.strategyobject.substrateclient.common.codegen.AnnotationUtils.suppressWarnings; +import static com.strategyobject.substrateclient.common.codegen.TypeUtils.getGetterName; +import static com.strategyobject.substrateclient.scale.codegen.ScaleProcessorHelper.SCALE_SELF_WRITABLE; import static java.util.stream.Collectors.toMap; public class ScaleWriterAnnotatedClass { + private static final String WRITERS_ARG = "writers"; + private static final String REGISTRY = "registry"; private final TypeElement classElement; private final Map typeVarMap; @@ -39,15 +44,13 @@ public ScaleWriterAnnotatedClass(TypeElement classElement) { .collect(toMap(i -> typeParameters.get(i).toString(), Function.identity())); } - public void generateWriter(@NonNull ProcessorContext context, - @NonNull Filer filer) throws IOException, ProcessingException { - val className = classElement.getSimpleName().toString(); - val writerName = context.getWriterName(className); + public void generateWriter(@NonNull ProcessorContext context) throws IOException, ProcessingException { + val writerName = ScaleProcessorHelper.getWriterName(classElement.getSimpleName().toString()); val classWildcardTyped = JavaPoet.setEachGenericParameterAsWildcard(classElement); val typeSpecBuilder = TypeSpec.classBuilder(writerName) .addAnnotation(AnnotationSpec.builder(AutoRegister.class) - .addMember("types", "{$L.class}", className) + .addMember("types", "{$L.class}", classElement.getQualifiedName().toString()) .build()) .addModifiers(Modifier.PUBLIC) .addSuperinterface(ParameterizedTypeName.get(ClassName.get(ScaleWriter.class), classWildcardTyped)) @@ -56,13 +59,14 @@ public void generateWriter(@NonNull ProcessorContext context, JavaFile.builder( context.getPackageName(classElement), typeSpecBuilder.build() - ).build().writeTo(filer); + ).build().writeTo(context.getFiler()); } private MethodSpec generateWriteMethod(TypeName classWildcardTyped, ProcessorContext context) throws ProcessingException { val methodSpec = MethodSpec.methodBuilder("write") .addAnnotation(Override.class) + .addAnnotation(suppressWarnings("unchecked")) .addModifiers(Modifier.PUBLIC) .returns(TypeName.VOID) .addParameter(classWildcardTyped, "value") @@ -86,7 +90,7 @@ private void addValidationRules(MethodSpec.Builder methodSpec, methodSpec.addStatement("if (value == null) throw new IllegalArgumentException(\"value is null\")"); val classTypeParametersSize = classElement.getTypeParameters().size(); - if (classTypeParametersSize == 0 || context.isSubtypeOfScaleSelfWritable(classElement.asType())) { + if (classTypeParametersSize == 0 || context.isSubtypeOf(classElement.asType(), context.erasure(context.getType(SCALE_SELF_WRITABLE)))) { methodSpec.addStatement("if (writers != null && writers.length > 0) throw new IllegalArgumentException()"); } else { methodSpec @@ -105,12 +109,12 @@ private void addMethodBody(MethodSpec.Builder methodSpec, .beginControlFlow("try"); val scaleAnnotationParser = new ScaleAnnotationParser(context); - val generator = new TypeWriteGenerator(CodeBlock.class, context, typeVarMap); + val compositor = new WriterCompositor(context, typeVarMap, String.format("%s[$L]", WRITERS_ARG), REGISTRY); for (Element element : classElement.getEnclosedElements()) { if (element instanceof VariableElement) { val field = (VariableElement) element; if (field.getAnnotation(Ignore.class) == null) { - setField(methodSpec, field, scaleAnnotationParser, generator); + setField(methodSpec, field, scaleAnnotationParser, compositor); } } } @@ -124,19 +128,19 @@ private void addMethodBody(MethodSpec.Builder methodSpec, private void setField(MethodSpec.Builder methodSpec, VariableElement field, ScaleAnnotationParser scaleAnnotationParser, - TypeWriteGenerator generator) throws ProcessingException { + WriterCompositor compositor) throws ProcessingException { try { val fieldType = field.asType(); val typeOverride = scaleAnnotationParser.parse(field); val writerCode = typeOverride != null ? - generator.traverse(fieldType, typeOverride) : - generator.traverse(fieldType); + compositor.traverse(fieldType, typeOverride) : + compositor.traverse(fieldType); methodSpec.addStatement( CodeBlock.builder() .add("(($T)", ScaleWriter.class) .add(writerCode) - .add(").write(value.$L, stream)", field) + .add(").write(value.$L(), stream)", getGetterName(field)) .build()); } catch (Exception e) { throw new ProcessingException(e, field, e.getMessage()); diff --git a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/ScaleWriterProcessor.java b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/ScaleWriterProcessor.java index 07b93c61..31f0f851 100644 --- a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/ScaleWriterProcessor.java +++ b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/ScaleWriterProcessor.java @@ -1,36 +1,33 @@ package com.strategyobject.substrateclient.scale.codegen.writer; import com.google.auto.service.AutoService; +import com.strategyobject.substrateclient.common.codegen.ProcessingException; +import com.strategyobject.substrateclient.common.codegen.ProcessorContext; import com.strategyobject.substrateclient.scale.annotations.ScaleWriter; -import com.strategyobject.substrateclient.scale.codegen.ProcessingException; -import com.strategyobject.substrateclient.scale.codegen.ProcessorContext; import lombok.val; import javax.annotation.processing.*; import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; -import javax.tools.Diagnostic; import java.io.IOException; import java.util.Set; +import static com.strategyobject.substrateclient.scale.codegen.ScaleProcessorHelper.SCALE_SELF_WRITABLE; + @SupportedAnnotationTypes("com.strategyobject.substrateclient.scale.annotations.ScaleWriter") @SupportedSourceVersion(SourceVersion.RELEASE_8) @AutoService(Processor.class) public class ScaleWriterProcessor extends AbstractProcessor { private ProcessorContext context; - private Filer filer; - private Messager messager; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); - filer = processingEnv.getFiler(); - messager = processingEnv.getMessager(); - context = new ProcessorContext( - processingEnv.getTypeUtils(), - processingEnv.getElementUtils()); + context = new ProcessorContext(processingEnv.getTypeUtils(), + processingEnv.getElementUtils(), + processingEnv.getFiler(), + processingEnv.getMessager()); } @Override @@ -41,7 +38,7 @@ public boolean process(Set annotations, RoundEnvironment for (val annotatedElement : roundEnv.getElementsAnnotatedWith(ScaleWriter.class)) { if (annotatedElement.getKind() != ElementKind.CLASS) { - error( + context.error( annotatedElement, "Only classes can be annotated with `@%s`.", ScaleWriter.class.getSimpleName()); @@ -51,20 +48,20 @@ public boolean process(Set annotations, RoundEnvironment val typeElement = (TypeElement) annotatedElement; if (!validateScaleSelfWritable(typeElement)) { - error( + context.error( typeElement, - "Classes implementing `%1$s` and annotated with `@%2$s` have to be either nongeneric or all its parameters have to implement `%1$s`", - ProcessorContext.SCALE_SELF_WRITABLE, + "Classes implementing `%1$s` and annotated with `@%2$s` have to be either non generic or all its parameters have to implement `%1$s`", + SCALE_SELF_WRITABLE.getCanonicalName(), ScaleWriter.class.getSimpleName()); } try { - new ScaleWriterAnnotatedClass(typeElement).generateWriter(context, filer); + new ScaleWriterAnnotatedClass(typeElement).generateWriter(context); } catch (ProcessingException e) { - error(typeElement, e.getMessage()); + context.error(typeElement, e); return true; } catch (IOException e) { - error(null, e.getMessage()); + context.error(e); return true; } } @@ -73,20 +70,13 @@ public boolean process(Set annotations, RoundEnvironment } private boolean validateScaleSelfWritable(TypeElement typeElement) { - if (!context.isSubtypeOfScaleSelfWritable(typeElement.asType())) { + val selfWritable = context.erasure(context.getType(SCALE_SELF_WRITABLE)); + if (!context.isSubtypeOf(typeElement.asType(), selfWritable)) { return true; } val typeParameters = typeElement.getTypeParameters(); return typeParameters.size() == 0 || - typeParameters.stream().allMatch(x -> context.isSubtypeOfScaleSelfWritable(x.asType())); - } - - private void error(Element e, String message, Object... args) { - messager.printMessage( - Diagnostic.Kind.ERROR, - String.format(message, args), - e - ); + typeParameters.stream().allMatch(x -> context.isSubtypeOf(x.asType(), selfWritable)); } } diff --git a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/TypeWriteGenerator.java b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/TypeWriteGenerator.java deleted file mode 100644 index 051a5a71..00000000 --- a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/TypeWriteGenerator.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.strategyobject.substrateclient.scale.codegen.writer; - -import com.squareup.javapoet.CodeBlock; -import com.strategyobject.substrateclient.common.codegen.TypeTraverser; -import com.strategyobject.substrateclient.scale.codegen.ProcessorContext; -import lombok.NonNull; -import lombok.var; - -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; -import java.util.Map; - -public class TypeWriteGenerator extends TypeTraverser { - private final ProcessorContext context; - private final Map typeVarMap; - - public TypeWriteGenerator(Class clazz, - ProcessorContext context, - Map typeVarMap) { - super(clazz); - this.context = context; - this.typeVarMap = typeVarMap; - } - - @Override - protected CodeBlock whenTypeVar(@NonNull TypeVariable type, TypeMirror _override) { - return context.isSubtypeOfScaleSelfWritable(type) ? - CodeBlock.builder() - .add("registry.resolve($T.class)", context.getScaleSelfWritableRawType()) - .build() : - CodeBlock.builder() - .add("writers[$L]", typeVarMap.get(type.toString())) - .build(); - } - - @Override - protected CodeBlock whenNonGenericType(@NonNull DeclaredType type, TypeMirror override) { - return CodeBlock.builder() - .add("registry.resolve($T.class)", override != null ? override : type) - .build(); - } - - @Override - protected CodeBlock whenGenericType(@NonNull DeclaredType type, TypeMirror override, @NonNull CodeBlock[] subtypes) { - TypeMirror resolveType; - if (override != null) { - if (!context.isGeneric(override)) { - return CodeBlock.builder() - .add("registry.resolve($T.class)", override) - .build(); - } - - resolveType = override; - } else { - resolveType = context.getTypeUtils().erasure(type); - } - - var builder = CodeBlock.builder().add("registry.resolve($T.class).inject(", resolveType); - for (var i = 0; i < subtypes.length; i++) { - if (i > 0) builder.add(", "); - builder.add(subtypes[i]); - } - - return builder.add(")").build(); - } -} 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 new file mode 100644 index 00000000..de56c0b2 --- /dev/null +++ b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/WriterCompositor.java @@ -0,0 +1,101 @@ +package com.strategyobject.substrateclient.scale.codegen.writer; + +import com.squareup.javapoet.CodeBlock; +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 java.util.Map; + +import static com.strategyobject.substrateclient.scale.codegen.ScaleProcessorHelper.SCALE_SELF_WRITABLE; + +public class WriterCompositor extends TypeTraverser { + private final ProcessorContext context; + private final Map typeVarMap; + private final TypeMirror selfWritable; + private final String writerAccessor; + private final String registryVarName; + + public WriterCompositor(ProcessorContext context, + Map typeVarMap, + String writerAccessor, + String registryVarName) { + super(CodeBlock.class); + this.context = context; + this.typeVarMap = typeVarMap; + this.selfWritable = context.erasure(context.getType(SCALE_SELF_WRITABLE)); + this.writerAccessor = writerAccessor; + this.registryVarName = registryVarName; + } + + @Override + protected CodeBlock whenTypeVar(@NonNull TypeVariable type, TypeMirror _override) { + return context.isSubtypeOf(type, selfWritable) ? + CodeBlock.builder() + .add("$L.resolve($T.class)", registryVarName, selfWritable) + .build() : + CodeBlock.builder() + .add(writerAccessor, typeVarMap.get(type.toString())) + .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 : + context.isSubtypeOf(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)) { + return CodeBlock.builder() + .add("$L.resolve($T.class)", registryVarName, override) + .build(); + } + + resolveType = override; + } else { + resolveType = context.erasure(type); + + if (context.isSubtypeOf(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 boolean doTraverseArguments(@NonNull DeclaredType type, TypeMirror override) { + return override != null || !context.isSubtypeOf(context.erasure(type), selfWritable); + } +} diff --git a/scale/scale-codegen/src/test/resources/Annotated.java b/scale/scale-codegen/src/test/resources/Annotated.java index 4c966730..05064b53 100644 --- a/scale/scale-codegen/src/test/resources/Annotated.java +++ b/scale/scale-codegen/src/test/resources/Annotated.java @@ -15,31 +15,31 @@ @ScaleWriter public class Annotated { @Scale(Bool.class) - public Boolean testBool; + private Boolean testBool; @Scale(CompactBigInteger.class) - public BigInteger testCompactBigInteger; + private BigInteger testCompactBigInteger; @Scale(CompactInteger.class) - public Integer testCompactInteger; + private Integer testCompactInteger; @Scale(I8.class) - public Byte testI8; + private Byte testI8; @Scale(I16.class) - public Short testI16; + private Short testI16; @Scale(I32.class) - public Integer testI32; + private Integer testI32; @Scale(I64.class) - public Long testI64; + private Long testI64; @Scale(I128.class) - public BigInteger testI128; + private BigInteger testI128; @Scale(OptionBool.class) - public Optional testOptionBool; + private Optional testOptionBool; @ScaleGeneric( @@ -48,7 +48,7 @@ public class Annotated { @Scale(Option.class), @Scale(I32.class) }) - public Optional testOption; + private Optional testOption; @ScaleGeneric( template = "Result", @@ -58,25 +58,25 @@ public class Annotated { @Scale(ScaleType.String.class) } ) - public Result testResult; + private Result testResult; @Scale(ScaleType.String.class) - public String testString; + private String testString; @Scale(U8.class) - public Integer testU8; + private Integer testU8; @Scale(U16.class) - public Integer testU16; + private Integer testU16; @Scale(U32.class) - public Long testU32; + private Long testU32; @Scale(U64.class) - public BigInteger testU64; + private BigInteger testU64; @Scale(U128.class) - public BigInteger testU128; + private BigInteger testU128; @ScaleGeneric( template = "Vec", @@ -85,19 +85,195 @@ public class Annotated { @Scale(Vec.class) } ) - public List testVec; + private List testVec; @Ignore - public Object testIgnore; + private Object testIgnore; @Scale - public T2 testGeneric1; + private T2 testGeneric1; @Scale - public T3 testGeneric2; + private T3 testGeneric2; @Scale - public NestedClass testNestedField; + private NestedClass testNestedField; + + public Boolean getTestBool() { + return testBool; + } + + public void setTestBool(Boolean testBool) { + this.testBool = testBool; + } + + public BigInteger getTestCompactBigInteger() { + return testCompactBigInteger; + } + + public void setTestCompactBigInteger(BigInteger testCompactBigInteger) { + this.testCompactBigInteger = testCompactBigInteger; + } + + public Integer getTestCompactInteger() { + return testCompactInteger; + } + + public void setTestCompactInteger(Integer testCompactInteger) { + this.testCompactInteger = testCompactInteger; + } + + public Byte getTestI8() { + return testI8; + } + + public void setTestI8(Byte testI8) { + this.testI8 = testI8; + } + + public Short getTestI16() { + return testI16; + } + + public void setTestI16(Short testI16) { + this.testI16 = testI16; + } + + public Integer getTestI32() { + return testI32; + } + + public void setTestI32(Integer testI32) { + this.testI32 = testI32; + } + + public Long getTestI64() { + return testI64; + } + + public void setTestI64(Long testI64) { + this.testI64 = testI64; + } + + public BigInteger getTestI128() { + return testI128; + } + + public void setTestI128(BigInteger testI128) { + this.testI128 = testI128; + } + + public Optional getTestOptionBool() { + return testOptionBool; + } + + public void setTestOptionBool(Optional testOptionBool) { + this.testOptionBool = testOptionBool; + } + + public Optional getTestOption() { + return testOption; + } + + public void setTestOption(Optional testOption) { + this.testOption = testOption; + } + + public Result getTestResult() { + return testResult; + } + + public void setTestResult(Result testResult) { + this.testResult = testResult; + } + + public String getTestString() { + return testString; + } + + public void setTestString(String testString) { + this.testString = testString; + } + + public Integer getTestU8() { + return testU8; + } + + public void setTestU8(Integer testU8) { + this.testU8 = testU8; + } + + public Integer getTestU16() { + return testU16; + } + + public void setTestU16(Integer testU16) { + this.testU16 = testU16; + } + + public Long getTestU32() { + return testU32; + } + + public void setTestU32(Long testU32) { + this.testU32 = testU32; + } + + public BigInteger getTestU64() { + return testU64; + } + + public void setTestU64(BigInteger testU64) { + this.testU64 = testU64; + } + + public BigInteger getTestU128() { + return testU128; + } + + public void setTestU128(BigInteger testU128) { + this.testU128 = testU128; + } + + public List getTestVec() { + return testVec; + } + + public void setTestVec(List testVec) { + this.testVec = testVec; + } + + public Object getTestIgnore() { + return testIgnore; + } + + public void setTestIgnore(Object testIgnore) { + this.testIgnore = testIgnore; + } + + public T2 getTestGeneric1() { + return testGeneric1; + } + + public void setTestGeneric1(T2 testGeneric1) { + this.testGeneric1 = testGeneric1; + } + + public T3 getTestGeneric2() { + return testGeneric2; + } + + public void setTestGeneric2(T3 testGeneric2) { + this.testGeneric2 = testGeneric2; + } + + public NestedClass getTestNestedField() { + return testNestedField; + } + + public void setTestNestedField(NestedClass testNestedField) { + this.testNestedField = testNestedField; + } public static class NestedClass { public T nestedField; diff --git a/scale/scale-codegen/src/test/resources/ComplexGeneric.java b/scale/scale-codegen/src/test/resources/ComplexGeneric.java index 2c0921b9..76553fe9 100644 --- a/scale/scale-codegen/src/test/resources/ComplexGeneric.java +++ b/scale/scale-codegen/src/test/resources/ComplexGeneric.java @@ -23,7 +23,7 @@ public class ComplexGeneric { @Scale(String.class) } ) - public Map, Result, String>> testGeneric; + private Map, Result, String>> testGeneric; @ScaleGeneric( template = "Map", @@ -33,7 +33,7 @@ public class ComplexGeneric { @Scale(name = "Result"), } ) - public Map> testGenericDefaultImplicit; + private Map> testGenericDefaultImplicit; @ScaleGeneric( template = "Map", @@ -43,5 +43,29 @@ public class ComplexGeneric { @Scale(value = Default.class, name = "Result"), } ) - public Map> testGenericDefaultExplicit; + private Map> testGenericDefaultExplicit; + + public Map, Result, String>> getTestGeneric() { + return testGeneric; + } + + public void setTestGeneric(Map, Result, String>> testGeneric) { + this.testGeneric = testGeneric; + } + + public Map> getTestGenericDefaultImplicit() { + return testGenericDefaultImplicit; + } + + public void setTestGenericDefaultImplicit(Map> testGenericDefaultImplicit) { + this.testGenericDefaultImplicit = testGenericDefaultImplicit; + } + + public Map> getTestGenericDefaultExplicit() { + return testGenericDefaultExplicit; + } + + public void setTestGenericDefaultExplicit(Map> testGenericDefaultExplicit) { + this.testGenericDefaultExplicit = testGenericDefaultExplicit; + } } diff --git a/scale/scale-codegen/src/test/resources/GenericScaleSelfWritable.java b/scale/scale-codegen/src/test/resources/GenericScaleSelfWritable.java index 3c567caa..75e08a21 100644 --- a/scale/scale-codegen/src/test/resources/GenericScaleSelfWritable.java +++ b/scale/scale-codegen/src/test/resources/GenericScaleSelfWritable.java @@ -10,11 +10,35 @@ public class GenericScaleSelfWritable< T3 extends ScaleSelfWritable> implements ScaleSelfWritable> { @Scale - public T1 testGeneric1; + private T1 testGeneric1; @Scale - public T2 testGeneric2; + private T2 testGeneric2; @Scale - public T3 testGeneric3; + private T3 testGeneric3; + + public T1 getTestGeneric1() { + return testGeneric1; + } + + public void setTestGeneric1(T1 testGeneric1) { + this.testGeneric1 = testGeneric1; + } + + public T2 getTestGeneric2() { + return testGeneric2; + } + + public void setTestGeneric2(T2 testGeneric2) { + this.testGeneric2 = testGeneric2; + } + + public T3 getTestGeneric3() { + return testGeneric3; + } + + public void setTestGeneric3(T3 testGeneric3) { + this.testGeneric3 = testGeneric3; + } } diff --git a/scale/scale-codegen/src/test/resources/MissesScaleSelfWritable.java b/scale/scale-codegen/src/test/resources/MissesScaleSelfWritable.java index 6b3f42cb..83718773 100644 --- a/scale/scale-codegen/src/test/resources/MissesScaleSelfWritable.java +++ b/scale/scale-codegen/src/test/resources/MissesScaleSelfWritable.java @@ -4,5 +4,13 @@ @ScaleWriter public class MissesScaleSelfWritable implements ScaleSelfWritable { - public String testString; + private String testString; + + public String getTestString() { + return testString; + } + + public void setTestString(String testString) { + this.testString = testString; + } } diff --git a/scale/scale-codegen/src/test/resources/NonAnnotated.java b/scale/scale-codegen/src/test/resources/NonAnnotated.java index a7eb1b0f..99b75521 100644 --- a/scale/scale-codegen/src/test/resources/NonAnnotated.java +++ b/scale/scale-codegen/src/test/resources/NonAnnotated.java @@ -11,29 +11,125 @@ @ScaleReader @ScaleWriter public class NonAnnotated { - public Boolean testBool; + private Boolean testBool; - public Byte testI8; + private Byte testI8; - public Short testI16; + private Short testI16; - public Integer testI32; + private Integer testI32; - public Long testI64; + private Long testI64; - public BigInteger testI128; + private BigInteger testI128; - public Optional testOption; + private Optional testOption; - public Result testResult; + private Result testResult; - public String testString; + private String testString; - public List testVec; + private List testVec; - public T1 testGeneric1; + private T1 testGeneric1; - public NestedClass testNestedField; + private NestedClass testNestedField; + + public Boolean getTestBool() { + return testBool; + } + + public void setTestBool(Boolean testBool) { + this.testBool = testBool; + } + + public Byte getTestI8() { + return testI8; + } + + public void setTestI8(Byte testI8) { + this.testI8 = testI8; + } + + public Short getTestI16() { + return testI16; + } + + public void setTestI16(Short testI16) { + this.testI16 = testI16; + } + + public Integer getTestI32() { + return testI32; + } + + public void setTestI32(Integer testI32) { + this.testI32 = testI32; + } + + public Long getTestI64() { + return testI64; + } + + public void setTestI64(Long testI64) { + this.testI64 = testI64; + } + + public BigInteger getTestI128() { + return testI128; + } + + public void setTestI128(BigInteger testI128) { + this.testI128 = testI128; + } + + public Optional getTestOption() { + return testOption; + } + + public void setTestOption(Optional testOption) { + this.testOption = testOption; + } + + public Result getTestResult() { + return testResult; + } + + public void setTestResult(Result testResult) { + this.testResult = testResult; + } + + public String getTestString() { + return testString; + } + + public void setTestString(String testString) { + this.testString = testString; + } + + public List getTestVec() { + return testVec; + } + + public void setTestVec(List testVec) { + this.testVec = testVec; + } + + public T1 getTestGeneric1() { + return testGeneric1; + } + + public void setTestGeneric1(T1 testGeneric1) { + this.testGeneric1 = testGeneric1; + } + + public NestedClass getTestNestedField() { + return testNestedField; + } + + public void setTestNestedField(NestedClass testNestedField) { + this.testNestedField = testNestedField; + } public static class NestedClass { public T nestedField; diff --git a/scale/scale-codegen/src/test/resources/WrongTemplate.java b/scale/scale-codegen/src/test/resources/WrongTemplate.java index aef8e115..1d33d0e3 100644 --- a/scale/scale-codegen/src/test/resources/WrongTemplate.java +++ b/scale/scale-codegen/src/test/resources/WrongTemplate.java @@ -24,5 +24,13 @@ public class WrongTemplate { @Scale(String.class) } ) - public Map, Result, String>> testGeneric; + private Map, Result, String>> testGeneric; + + public Map, Result, String>> getTestGeneric() { + return testGeneric; + } + + public void setTestGeneric(Map, Result, String>> testGeneric) { + this.testGeneric = testGeneric; + } } diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/ScaleSelfWritable.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/ScaleSelfWritable.java index b9595471..13ae13c3 100644 --- a/scale/src/main/java/com/strategyobject/substrateclient/scale/ScaleSelfWritable.java +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/ScaleSelfWritable.java @@ -1,7 +1,6 @@ package com.strategyobject.substrateclient.scale; -import com.strategyobject.substrateclient.scale.registry.ScaleWriterNotFoundException; -import com.strategyobject.substrateclient.scale.registry.ScaleWriterRegistry; +import com.strategyobject.substrateclient.scale.registries.ScaleWriterRegistry; import lombok.NonNull; import lombok.val; @@ -10,7 +9,7 @@ public interface ScaleSelfWritable> { @SuppressWarnings("unchecked") - default void write(@NonNull OutputStream stream) throws ScaleWriterNotFoundException, IOException { + default void write(@NonNull OutputStream stream) throws IOException { val writer = (ScaleWriter) ScaleWriterRegistry .getInstance() .resolve(this.getClass()); diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/ScaleUtils.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/ScaleUtils.java new file mode 100644 index 00000000..552859a9 --- /dev/null +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/ScaleUtils.java @@ -0,0 +1,36 @@ +package com.strategyobject.substrateclient.scale; + +import com.strategyobject.substrateclient.common.utils.HexConverter; +import lombok.NonNull; +import lombok.val; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public final class ScaleUtils { + public static String toHexString(@NonNull T value, @NonNull ScaleWriter writer) { + val stream = new ByteArrayOutputStream(); + + try { + writer.write(value, stream); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return HexConverter.toHex(stream.toByteArray()); + } + + public static T fromHexString(@NonNull String hex, @NonNull ScaleReader reader) { + val stream = new ByteArrayInputStream(HexConverter.toBytes(hex)); + + try { + return reader.read(stream); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private ScaleUtils() { + } +} diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/annotations/Scale.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/annotations/Scale.java index 833c0eab..3a9a5d4f 100644 --- a/scale/src/main/java/com/strategyobject/substrateclient/scale/annotations/Scale.java +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/annotations/Scale.java @@ -5,7 +5,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Target({ElementType.FIELD}) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) @Retention(RetentionPolicy.SOURCE) public @interface Scale { Class value() default Default.class; diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/annotations/ScaleGeneric.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/annotations/ScaleGeneric.java index 6fd18c3c..112e0a94 100644 --- a/scale/src/main/java/com/strategyobject/substrateclient/scale/annotations/ScaleGeneric.java +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/annotations/ScaleGeneric.java @@ -5,7 +5,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Target({ElementType.FIELD}) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) @Retention(RetentionPolicy.SOURCE) public @interface ScaleGeneric { String template(); diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/registry/ScaleReaderRegistry.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/registries/ScaleReaderRegistry.java similarity index 71% rename from scale/src/main/java/com/strategyobject/substrateclient/scale/registry/ScaleReaderRegistry.java rename to scale/src/main/java/com/strategyobject/substrateclient/scale/registries/ScaleReaderRegistry.java index 0bafa912..aa36c7e5 100644 --- a/scale/src/main/java/com/strategyobject/substrateclient/scale/registry/ScaleReaderRegistry.java +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/registries/ScaleReaderRegistry.java @@ -1,4 +1,4 @@ -package com.strategyobject.substrateclient.scale.registry; +package com.strategyobject.substrateclient.scale.registries; import com.strategyobject.substrateclient.common.reflection.Scanner; import com.strategyobject.substrateclient.scale.ScaleReader; @@ -19,19 +19,8 @@ public final class ScaleReaderRegistry { private static final Logger logger = LoggerFactory.getLogger(ScaleReaderRegistry.class); + private static final String ROOT_PREFIX = "com.strategyobject.substrateclient"; private static volatile ScaleReaderRegistry instance; - - public static ScaleReaderRegistry getInstance() { - if (instance == null) { - synchronized (ScaleReaderRegistry.class) { - if (instance == null) { - instance = new ScaleReaderRegistry(); - } - } - } - return instance; - } - private final Map, ScaleReader> readers; private ScaleReaderRegistry() { @@ -57,26 +46,38 @@ private ScaleReaderRegistry() { register(new VecReader(), ScaleType.Vec.class, List.class); register(new VoidReader(), Void.class); - autoRegistration(); + registerAnnotatedFrom(ROOT_PREFIX); } - private void autoRegistration() { - Scanner.getSubTypesOf(ScaleReader.class).forEach(reader -> { - val autoRegister = reader.getAnnotation(AutoRegister.class); - if (autoRegister == null) { - return; + public static ScaleReaderRegistry getInstance() { + if (instance == null) { + synchronized (ScaleReaderRegistry.class) { + if (instance == null) { + instance = new ScaleReaderRegistry(); + } } + } + return instance; + } - try { - val types = autoRegister.types(); - logger.info("Auto register reader {} for types: {}", reader, types); + public void registerAnnotatedFrom(String... prefixes) { + Scanner.forPrefixes(prefixes) + .getSubTypesOf(ScaleReader.class).forEach(reader -> { + val autoRegister = reader.getAnnotation(AutoRegister.class); + if (autoRegister == null) { + return; + } - final ScaleReader readerInstance = reader.newInstance(); - register(readerInstance, types); - } catch (InstantiationException | IllegalAccessException e) { - logger.error("Auto registration error", e); - } - }); + try { + val types = autoRegister.types(); + logger.info("Auto register reader {} for types: {}", reader, types); + + final ScaleReader readerInstance = reader.newInstance(); + register(readerInstance, types); + } catch (InstantiationException | IllegalAccessException e) { + logger.error("Auto registration error", e); + } + }); } public void register(@NonNull ScaleReader scaleReader, @NonNull Class... clazz) { @@ -85,12 +86,7 @@ public void register(@NonNull ScaleReader scaleReader, @NonNull Class. } } - public ScaleReader resolve(@NonNull Class clazz) throws ScaleReaderNotFoundException { - val result = readers.get(clazz); - if (result == null) { - throw new ScaleReaderNotFoundException(clazz); - } - - return result; + public ScaleReader resolve(@NonNull Class clazz) { + return readers.get(clazz); } } diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/registry/ScaleWriterRegistry.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/registries/ScaleWriterRegistry.java similarity index 68% rename from scale/src/main/java/com/strategyobject/substrateclient/scale/registry/ScaleWriterRegistry.java rename to scale/src/main/java/com/strategyobject/substrateclient/scale/registries/ScaleWriterRegistry.java index 1bfde7f4..f3ee57e7 100644 --- a/scale/src/main/java/com/strategyobject/substrateclient/scale/registry/ScaleWriterRegistry.java +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/registries/ScaleWriterRegistry.java @@ -1,4 +1,4 @@ -package com.strategyobject.substrateclient.scale.registry; +package com.strategyobject.substrateclient.scale.registries; import com.strategyobject.substrateclient.common.reflection.Scanner; import com.strategyobject.substrateclient.scale.ScaleSelfWritable; @@ -6,7 +6,9 @@ import com.strategyobject.substrateclient.scale.ScaleWriter; import com.strategyobject.substrateclient.scale.annotations.AutoRegister; import com.strategyobject.substrateclient.scale.writers.*; +import com.strategyobject.substrateclient.types.PublicKey; import com.strategyobject.substrateclient.types.Result; +import com.strategyobject.substrateclient.types.SignatureData; import lombok.NonNull; import lombok.val; import org.slf4j.Logger; @@ -20,19 +22,8 @@ public final class ScaleWriterRegistry { private static final Logger logger = LoggerFactory.getLogger(ScaleWriterRegistry.class); + private static final String ROOT_PREFIX = "com.strategyobject.substrateclient"; private static volatile ScaleWriterRegistry instance; - - public static ScaleWriterRegistry getInstance() { - if (instance == null) { - synchronized (ScaleWriterRegistry.class) { - if (instance == null) { - instance = new ScaleWriterRegistry(); - } - } - } - return instance; - } - private final Map, ScaleWriter> writers; private ScaleWriterRegistry() { @@ -58,27 +49,41 @@ private ScaleWriterRegistry() { register(new VecWriter(), ScaleType.Vec.class, List.class); register(new VoidWriter(), Void.class); register(new SelfWriter(), ScaleSelfWritable.class); + register(new PublicKeyWriter(), PublicKey.class); + register(new SignatureDataWriter(), SignatureData.class); - autoRegistration(); + registerAnnotatedFrom(ROOT_PREFIX); } - private void autoRegistration() { - Scanner.getSubTypesOf(ScaleWriter.class).forEach(writer -> { - val autoRegister = writer.getAnnotation(AutoRegister.class); - if (autoRegister == null) { - return; + public static ScaleWriterRegistry getInstance() { + if (instance == null) { + synchronized (ScaleWriterRegistry.class) { + if (instance == null) { + instance = new ScaleWriterRegistry(); + } } + } + return instance; + } - try { - val types = autoRegister.types(); - logger.info("Auto register writer {} for types: {}", writer, types); + public void registerAnnotatedFrom(String... prefixes) { + Scanner.forPrefixes(prefixes) + .getSubTypesOf(ScaleWriter.class).forEach(writer -> { + val autoRegister = writer.getAnnotation(AutoRegister.class); + if (autoRegister == null) { + return; + } - final ScaleWriter writerInstance = writer.newInstance(); - register(writerInstance, types); - } catch (InstantiationException | IllegalAccessException e) { - logger.error("Auto registration error", e); - } - }); + try { + val types = autoRegister.types(); + logger.info("Auto register writer {} for types: {}", writer, types); + + final ScaleWriter writerInstance = writer.newInstance(); + register(writerInstance, types); + } catch (InstantiationException | IllegalAccessException e) { + logger.error("Auto registration error", e); + } + }); } public void register(@NonNull ScaleWriter scaleWriter, @NonNull Class... clazz) { @@ -87,12 +92,7 @@ public void register(@NonNull ScaleWriter scaleWriter, @NonNull Class. } } - public ScaleWriter resolve(@NonNull Class clazz) throws ScaleWriterNotFoundException { - val result = writers.get(clazz); - if (result == null) { - throw new ScaleWriterNotFoundException(clazz); - } - - return result; + public ScaleWriter resolve(@NonNull Class clazz) { + return writers.get(clazz); } } diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/registry/ScaleReaderNotFoundException.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/registry/ScaleReaderNotFoundException.java deleted file mode 100644 index 3ab11929..00000000 --- a/scale/src/main/java/com/strategyobject/substrateclient/scale/registry/ScaleReaderNotFoundException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.strategyobject.substrateclient.scale.registry; - -public class ScaleReaderNotFoundException extends Exception { - public ScaleReaderNotFoundException(Class clazz) { - super(String.format("ScaleReader for %s wasn't found in the registry.", clazz)); - } -} diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/registry/ScaleWriterNotFoundException.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/registry/ScaleWriterNotFoundException.java deleted file mode 100644 index a6c2a5a4..00000000 --- a/scale/src/main/java/com/strategyobject/substrateclient/scale/registry/ScaleWriterNotFoundException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.strategyobject.substrateclient.scale.registry; - -public class ScaleWriterNotFoundException extends Exception { - public ScaleWriterNotFoundException(Class clazz) { - super(String.format("ScaleWriter for %s wasn't found in the registry.", clazz)); - } -} diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/PublicKeyWriter.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/PublicKeyWriter.java new file mode 100644 index 00000000..e05a0f9e --- /dev/null +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/PublicKeyWriter.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 com.strategyobject.substrateclient.types.PublicKey; +import lombok.NonNull; + +import java.io.IOException; +import java.io.OutputStream; + +public class PublicKeyWriter implements ScaleWriter { + @Override + public void write(@NonNull PublicKey value, @NonNull OutputStream stream, ScaleWriter... writers) throws IOException { + Preconditions.checkArgument(writers == null || writers.length == 0); + + Streamer.writeBytes(value.getData(), stream); + } +} \ No newline at end of file diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/SelfWriter.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/SelfWriter.java index 3dfcd4c3..d0fa25ef 100644 --- a/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/SelfWriter.java +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/SelfWriter.java @@ -3,7 +3,6 @@ import com.google.common.base.Preconditions; import com.strategyobject.substrateclient.scale.ScaleSelfWritable; import com.strategyobject.substrateclient.scale.ScaleWriter; -import com.strategyobject.substrateclient.scale.registry.ScaleWriterNotFoundException; import lombok.NonNull; import java.io.IOException; @@ -14,10 +13,6 @@ public class SelfWriter implements ScaleWriter> { public void write(@NonNull ScaleSelfWritable value, @NonNull OutputStream stream, ScaleWriter... writers) throws IOException { Preconditions.checkArgument(writers == null || writers.length == 0); - try { - value.write(stream); - } catch (ScaleWriterNotFoundException e) { - throw new RuntimeException(e); - } + value.write(stream); } } diff --git a/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/SignatureDataWriter.java b/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/SignatureDataWriter.java new file mode 100644 index 00000000..6d9ddfb9 --- /dev/null +++ b/scale/src/main/java/com/strategyobject/substrateclient/scale/writers/SignatureDataWriter.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 com.strategyobject.substrateclient.types.SignatureData; +import lombok.NonNull; + +import java.io.IOException; +import java.io.OutputStream; + +public class SignatureDataWriter implements ScaleWriter { + @Override + public void write(@NonNull SignatureData value, @NonNull OutputStream stream, ScaleWriter... writers) throws IOException { + Preconditions.checkArgument(writers == null || writers.length == 0); + + Streamer.writeBytes(value.getData(), stream); + } +} \ No newline at end of file diff --git a/transport/src/main/java/com/strategyobject/substrateclient/transport/ProviderInterface.java b/transport/src/main/java/com/strategyobject/substrateclient/transport/ProviderInterface.java index eb1b0863..921eee4b 100644 --- a/transport/src/main/java/com/strategyobject/substrateclient/transport/ProviderInterface.java +++ b/transport/src/main/java/com/strategyobject/substrateclient/transport/ProviderInterface.java @@ -48,7 +48,7 @@ public interface ProviderInterface { * @param params Encoded parameters as applicable for the method * @return future containing result */ - CompletableFuture send(String method, + CompletableFuture send(String method, // TODO replace `Object` to something like `JObject` to have more strict contract List params); /**