From 8b5fe92e06b2b4140c5f5f7c4aceb3e2e9095cfc Mon Sep 17 00:00:00 2001 From: Vadim Nabiev Date: Thu, 14 Apr 2022 16:23:36 +0300 Subject: [PATCH] Annotations for pallets and their storages Added annotations `Pallet` and `Storage` and related entities: `StorageKey`, `StorageHasher`. Added `PalletInterfaceProccessor` which generates proxies for pallets to the blockchain by their annotations. Added corresponding and auxiliary classes. --- api/build.gradle | 15 + .../substrateclient/api/Api.java | 26 ++ .../substrateclient/api/DefaultApi.java | 34 ++ .../substrateclient/api/ApiTests.java | 42 ++ .../substrateclient/api/SystemPallet.java | 22 + build.gradle | 2 +- .../common/codegen/ProcessorContext.java | 6 +- .../common/codegen/TypeTraverser.java | 24 + pallet/build.gradle | 4 + pallet/pallet-codegen/build.gradle | 16 + .../CompoundMethodProcessor.java | 32 ++ .../Constants.java | 15 + .../PalletAnnotatedInterface.java | 83 ++++ .../PalletInterfaceProcessor.java | 69 +++ .../PalletMethodProcessor.java | 23 + .../StorageProcessor.java | 441 ++++++++++++++++++ .../pallet/PalletInterfaceProcessorTests.java | 119 +++++ .../src/test/resources/AmbiguousScale.java | 26 ++ .../src/test/resources/ClassPallet.java | 9 + .../src/test/resources/NotAStorage.java | 17 + .../src/test/resources/TestPallet.java | 55 +++ .../src/test/resources/UnnamedPallet.java | 9 + .../src/test/resources/UnnamedStorage.java | 9 + .../src/test/resources/WithoutScale.java | 13 + .../pallet/GeneratedPalletResolver.java | 33 ++ .../pallet/PalletResolver.java | 5 + .../pallet/annotations/Pallet.java | 22 + .../pallet/annotations/Storage.java | 24 + .../pallet/annotations/StorageHasher.java | 19 + .../pallet/annotations/StorageKey.java | 28 ++ .../pallet/GeneratedPalletResolverTests.java | 41 ++ .../substrateclient/pallet/TestPallet.java | 7 + .../pallet/TestPalletImpl.java | 9 + .../pallet/TestPalletNotAnnotated.java | 6 + .../pallet/TestPalletNotAnnotatedImpl.java | 9 + .../pallet/TestPalletWithoutConstructor.java | 7 + .../TestPalletWithoutConstructorImpl.java | 4 + .../decoder/RpcDecoderAnnotatedClass.java | 2 +- .../codegen/encoder/EncoderCompositor.java | 8 +- .../encoder/RpcEncoderAnnotatedClass.java | 4 +- .../sections/RpcAnnotatedInterface.java | 4 +- .../codegen/sections/RpcCallProcessor.java | 6 +- .../codegen/sections/RpcMethodProcessor.java | 8 +- .../sections/RpcInterfaceProcessorTest.java | 2 +- .../rpc/codegen/substitutes/TestSection.java | 4 +- .../src/test/resources/ClassSection.java | 4 +- .../src/test/resources/NamelessSection.java | 4 +- .../SectionWithAmbiguousAnnotatedMethod.java | 4 +- .../SectionWithIncorrectReturnOfMethod.java | 4 +- ...tionWithIncorrectReturnOfSubscription.java | 2 +- .../resources/SectionWithManyCallbacks.java | 2 +- .../SectionWithoutAnnotatedMethod.java | 2 +- .../resources/SectionWithoutCallback.java | 2 +- .../src/test/resources/TestSection.java | 4 +- .../rpc/core/annotations/RpcCall.java | 2 +- .../rpc/core/annotations/RpcInterface.java | 2 +- .../substrateclient/rpc/sections/Author.java | 8 +- .../substrateclient/rpc/sections/Chain.java | 8 +- .../substrateclient/rpc/sections/State.java | 36 +- .../substrateclient/rpc/sections/System.java | 4 +- .../substrateclient/rpc/RpcImpl.java | 47 +- .../scale/codegen/ScaleAnnotationParser.java | 19 +- .../codegen/reader/ReaderCompositor.java | 26 +- .../reader/ScaleReaderAnnotatedClass.java | 7 +- .../writer/ScaleWriterAnnotatedClass.java | 7 +- .../codegen/writer/ScaleWriterProcessor.java | 4 +- .../codegen/writer/WriterCompositor.java | 29 +- settings.gradle | 2 + .../storage/StorageDoubleMapImplTests.java | 2 +- .../storage/StorageMapImplTests.java | 2 +- .../storage/StorageNMapImplTests.java | 14 +- .../storage/StorageValueImplTests.java | 4 +- .../substrateclient/types/Size.java | 9 +- 73 files changed, 1505 insertions(+), 118 deletions(-) create mode 100644 api/src/main/java/com/strategyobject/substrateclient/api/DefaultApi.java create mode 100644 api/src/test/java/com/strategyobject/substrateclient/api/ApiTests.java create mode 100644 api/src/test/java/com/strategyobject/substrateclient/api/SystemPallet.java create mode 100644 pallet/build.gradle create mode 100644 pallet/pallet-codegen/build.gradle create mode 100644 pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/CompoundMethodProcessor.java create mode 100644 pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/Constants.java create mode 100644 pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/PalletAnnotatedInterface.java create mode 100644 pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/PalletInterfaceProcessor.java create mode 100644 pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/PalletMethodProcessor.java create mode 100644 pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/StorageProcessor.java create mode 100644 pallet/pallet-codegen/src/test/java/com/strategyobject/substrateclient/pallet/PalletInterfaceProcessorTests.java create mode 100644 pallet/pallet-codegen/src/test/resources/AmbiguousScale.java create mode 100644 pallet/pallet-codegen/src/test/resources/ClassPallet.java create mode 100644 pallet/pallet-codegen/src/test/resources/NotAStorage.java create mode 100644 pallet/pallet-codegen/src/test/resources/TestPallet.java create mode 100644 pallet/pallet-codegen/src/test/resources/UnnamedPallet.java create mode 100644 pallet/pallet-codegen/src/test/resources/UnnamedStorage.java create mode 100644 pallet/pallet-codegen/src/test/resources/WithoutScale.java create mode 100644 pallet/src/main/java/com/strategyobject/substrateclient/pallet/GeneratedPalletResolver.java create mode 100644 pallet/src/main/java/com/strategyobject/substrateclient/pallet/PalletResolver.java create mode 100644 pallet/src/main/java/com/strategyobject/substrateclient/pallet/annotations/Pallet.java create mode 100644 pallet/src/main/java/com/strategyobject/substrateclient/pallet/annotations/Storage.java create mode 100644 pallet/src/main/java/com/strategyobject/substrateclient/pallet/annotations/StorageHasher.java create mode 100644 pallet/src/main/java/com/strategyobject/substrateclient/pallet/annotations/StorageKey.java create mode 100644 pallet/src/test/java/com/strategyobject/substrateclient/pallet/GeneratedPalletResolverTests.java create mode 100644 pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPallet.java create mode 100644 pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPalletImpl.java create mode 100644 pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPalletNotAnnotated.java create mode 100644 pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPalletNotAnnotatedImpl.java create mode 100644 pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPalletWithoutConstructor.java create mode 100644 pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPalletWithoutConstructorImpl.java diff --git a/api/build.gradle b/api/build.gradle index 82ceb33d..5724e2c2 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -1,3 +1,18 @@ dependencies { implementation project(':rpc') + implementation project(':rpc:rpc-core') + implementation project(':rpc:rpc-sections') + implementation project(':transport') + implementation project(':pallet') + implementation project(':scale') + implementation project(':types') + implementation project(':rpc:rpc-types') + implementation project(':storage') + + testImplementation project(':tests') + + testImplementation 'org.testcontainers:testcontainers:1.16.3' + testImplementation 'org.testcontainers:junit-jupiter:1.16.3' + + testAnnotationProcessor project(':pallet:pallet-codegen') } \ No newline at end of file diff --git a/api/src/main/java/com/strategyobject/substrateclient/api/Api.java b/api/src/main/java/com/strategyobject/substrateclient/api/Api.java index e2c5fd29..fbb8575f 100644 --- a/api/src/main/java/com/strategyobject/substrateclient/api/Api.java +++ b/api/src/main/java/com/strategyobject/substrateclient/api/Api.java @@ -1,7 +1,33 @@ package com.strategyobject.substrateclient.api; +import com.strategyobject.substrateclient.pallet.GeneratedPalletResolver; import com.strategyobject.substrateclient.rpc.Rpc; +import com.strategyobject.substrateclient.rpc.RpcImpl; +import com.strategyobject.substrateclient.transport.ProviderInterface; +import lombok.val; +/** + * Provides the ability to query a node and interact with the Polkadot or Substrate chains. + * It allows interacting with blockchain in various ways: using RPC's queries directly or + * accessing Pallets and its APIs, such as storages, transactions, etc. + */ public interface Api { + static DefaultApi with(ProviderInterface provider) { + val rpc = RpcImpl.with(provider); + + return DefaultApi.with(rpc, GeneratedPalletResolver.with(rpc)); + } + + /** + * @return the instance that provides a proper API for querying the RPC's methods. + */ Rpc rpc(); + + /** + * Resolves the instance of a pallet by its definition. + * @param clazz the class of the pallet + * @param the type of the pallet + * @return appropriate instance of the pallet + */ + T pallet(Class clazz); } diff --git a/api/src/main/java/com/strategyobject/substrateclient/api/DefaultApi.java b/api/src/main/java/com/strategyobject/substrateclient/api/DefaultApi.java new file mode 100644 index 00000000..2ca53390 --- /dev/null +++ b/api/src/main/java/com/strategyobject/substrateclient/api/DefaultApi.java @@ -0,0 +1,34 @@ +package com.strategyobject.substrateclient.api; + +import com.strategyobject.substrateclient.pallet.PalletResolver; +import com.strategyobject.substrateclient.rpc.Rpc; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@RequiredArgsConstructor(staticName = "with") +public class DefaultApi implements Api, AutoCloseable { + private final @NonNull Rpc rpc; + private final @NonNull PalletResolver palletResolver; + private final Map, Object> palletCache = new ConcurrentHashMap<>(); + + @Override + public Rpc rpc() { + return rpc; + } + + @Override + public T pallet(@NonNull Class clazz) { + return clazz.cast(palletCache + .computeIfAbsent(clazz, palletResolver::resolve)); + } + + @Override + public void close() throws Exception { + if (rpc instanceof AutoCloseable) { + ((AutoCloseable) rpc).close(); + } + } +} \ No newline at end of file diff --git a/api/src/test/java/com/strategyobject/substrateclient/api/ApiTests.java b/api/src/test/java/com/strategyobject/substrateclient/api/ApiTests.java new file mode 100644 index 00000000..dab720dc --- /dev/null +++ b/api/src/test/java/com/strategyobject/substrateclient/api/ApiTests.java @@ -0,0 +1,42 @@ +package com.strategyobject.substrateclient.api; + +import com.strategyobject.substrateclient.tests.containers.SubstrateVersion; +import com.strategyobject.substrateclient.tests.containers.TestSubstrateContainer; +import com.strategyobject.substrateclient.transport.ws.WsProvider; +import lombok.val; +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.math.BigInteger; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@Testcontainers +public class ApiTests { + private static final int WAIT_TIMEOUT = 1000; + + @Container + private final TestSubstrateContainer substrate = new TestSubstrateContainer(SubstrateVersion.V3_0_0); + + @Test + public void getSystemPalletAndCall() throws Exception { // TODO move the test out of the project + val wsProvider = WsProvider.builder() + .setEndpoint(substrate.getWsAddress()) + .build(); + wsProvider.connect().get(WAIT_TIMEOUT, TimeUnit.SECONDS); + + try (val api = Api.with(wsProvider)) { + val systemPallet = api.pallet(SystemPallet.class); + val blockHash = systemPallet + .blockHash() + .get(0) + .get(WAIT_TIMEOUT, TimeUnit.SECONDS); + + assertNotNull(blockHash); + assertNotEquals(BigInteger.ZERO, new BigInteger(blockHash.getData())); + } + } +} diff --git a/api/src/test/java/com/strategyobject/substrateclient/api/SystemPallet.java b/api/src/test/java/com/strategyobject/substrateclient/api/SystemPallet.java new file mode 100644 index 00000000..fd5a9250 --- /dev/null +++ b/api/src/test/java/com/strategyobject/substrateclient/api/SystemPallet.java @@ -0,0 +1,22 @@ +package com.strategyobject.substrateclient.api; + +import com.strategyobject.substrateclient.pallet.annotations.Pallet; +import com.strategyobject.substrateclient.pallet.annotations.Storage; +import com.strategyobject.substrateclient.pallet.annotations.StorageHasher; +import com.strategyobject.substrateclient.pallet.annotations.StorageKey; +import com.strategyobject.substrateclient.rpc.types.BlockHash; +import com.strategyobject.substrateclient.scale.annotations.Scale; +import com.strategyobject.substrateclient.storage.StorageNMap; + +@Pallet("System") +public interface SystemPallet { + @Storage( + value = "BlockHash", + keys = { + @StorageKey( + type = @Scale(Integer.class), + hasher = StorageHasher.TwoX64Concat + ) + }) + StorageNMap blockHash(); +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 9248963f..f7b84986 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { allprojects { group = 'com.strategyobject.substrateclient' - version = '0.0.4-SNAPSHOT' + version = '0.1.0-SNAPSHOT' repositories { mavenLocal() diff --git a/common/src/main/java/com/strategyobject/substrateclient/common/codegen/ProcessorContext.java b/common/src/main/java/com/strategyobject/substrateclient/common/codegen/ProcessorContext.java index e3c35e5d..64710fb1 100644 --- a/common/src/main/java/com/strategyobject/substrateclient/common/codegen/ProcessorContext.java +++ b/common/src/main/java/com/strategyobject/substrateclient/common/codegen/ProcessorContext.java @@ -26,9 +26,13 @@ public String getPackageName(@NonNull TypeElement classElement) { return elementUtils.getPackageOf(classElement).getQualifiedName().toString(); } - public boolean isSubtypeOf(@NonNull TypeMirror candidate, @NonNull TypeMirror supertype) { + public boolean isAssignable(@NonNull TypeMirror candidate, @NonNull TypeMirror supertype) { return typeUtils.isAssignable(candidate, supertype); } + + public boolean isSubtype(@NonNull TypeMirror candidate, @NonNull TypeMirror supertype) { + return typeUtils.isSubtype(candidate, supertype); + } public boolean isGeneric(@NonNull TypeMirror type) { return ((TypeElement) typeUtils.asElement(type)) 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 7a1b2c51..3893622e 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 @@ -101,6 +101,29 @@ public T traverse(@NonNull TypeMirror type, @NonNull TypeTraverser.TypeTreeNode .toArray(x -> (T[]) Array.newInstance(clazz, typeArguments.size()))); } + @SuppressWarnings({"unchecked"}) + public T traverse(@NonNull TypeTraverser.TypeTreeNode typeOverride) { + if (typeOverride.type.getKind().isPrimitive()) { + return whenPrimitiveType((PrimitiveType) typeOverride.type, typeOverride.type); + } + + if (!(typeOverride.type instanceof DeclaredType)) { + throw new IllegalArgumentException("Type is not supported: " + typeOverride.type); + } + + val declaredType = (DeclaredType) typeOverride.type; + if (typeOverride.children.size() == 0) { + return whenNonGenericType(declaredType, typeOverride.type); + } + + return whenGenericType( + declaredType, + typeOverride.type, + typeOverride.children.stream() + .map(this::traverse) + .toArray(x -> (T[]) Array.newInstance(clazz, typeOverride.children.size()))); + } + private List getTypeArgumentsOrDefault(DeclaredType declaredType, TypeMirror override) { return (doTraverseArguments(declaredType, override) ? declaredType.getTypeArguments() : @@ -125,6 +148,7 @@ private boolean typeIsOverriddenByNonGeneric(int typeOverrideSize) { public static class TypeTreeNode { + @Getter private final TypeMirror type; private final List children; diff --git a/pallet/build.gradle b/pallet/build.gradle new file mode 100644 index 00000000..e5b3aca3 --- /dev/null +++ b/pallet/build.gradle @@ -0,0 +1,4 @@ +dependencies { + implementation project(':scale') + implementation project(':rpc') +} \ No newline at end of file diff --git a/pallet/pallet-codegen/build.gradle b/pallet/pallet-codegen/build.gradle new file mode 100644 index 00000000..fc9b225b --- /dev/null +++ b/pallet/pallet-codegen/build.gradle @@ -0,0 +1,16 @@ +dependencies { + implementation project(':common') + implementation project(':rpc') + implementation project(':types') + implementation project(':scale') + implementation project(':scale:scale-codegen') + implementation project(':storage') + implementation project(':pallet') + + implementation 'com.squareup:javapoet:1.13.0' + + compileOnly 'com.google.auto.service:auto-service-annotations:1.0.1' + annotationProcessor 'com.google.auto.service:auto-service:1.0.1' + + testImplementation 'com.google.testing.compile:compile-testing:0.19' +} \ No newline at end of file diff --git a/pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/CompoundMethodProcessor.java b/pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/CompoundMethodProcessor.java new file mode 100644 index 00000000..9e48cb9a --- /dev/null +++ b/pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/CompoundMethodProcessor.java @@ -0,0 +1,32 @@ +package com.strategyobject.substrateclient.pallet; + +import com.squareup.javapoet.MethodSpec; +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 java.util.List; + +class CompoundMethodProcessor extends PalletMethodProcessor { + private final List processors; + + public CompoundMethodProcessor(TypeElement typeElement, List processors) { + super(typeElement); + this.processors = processors; + } + + @Override + void process(@NonNull String palletName, @NonNull ExecutableElement method, TypeSpec.@NonNull Builder typeSpecBuilder, MethodSpec.Builder constructorBuilder, @NonNull ProcessorContext context) throws ProcessingException { + for (var processor : processors) { + processor.process(palletName, + method, + typeSpecBuilder, + constructorBuilder, + context); + } + } +} diff --git a/pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/Constants.java b/pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/Constants.java new file mode 100644 index 00000000..0fe76acc --- /dev/null +++ b/pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/Constants.java @@ -0,0 +1,15 @@ +package com.strategyobject.substrateclient.pallet; + +class Constants { + static final String RPC = "rpc"; + static final String CLASS_NAME_TEMPLATE = "%sImpl"; + static final String SCALE_READER_REGISTRY = "scaleReaderRegistry"; + static final String SCALE_WRITER_REGISTRY = "scaleWriterRegistry"; + static final String STORAGE_FACTORY_METHOD = "with"; + static final String STORAGE_KEY_PROVIDER_FACTORY_METHOD = "of"; + static final String STORAGE_KEY_PROVIDER_ADD_HASHERS = "use"; + static final String KEY_HASHER_FACTORY_METHOD = "with"; + static final String BLAKE_2_128_CONCAT_INSTANCE = "getInstance"; + static final String TWO_X64_CONCAT_INSTANCE = "getInstance"; + static final String IDENTITY_INSTANCE = "getInstance"; +} diff --git a/pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/PalletAnnotatedInterface.java b/pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/PalletAnnotatedInterface.java new file mode 100644 index 00000000..22373635 --- /dev/null +++ b/pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/PalletAnnotatedInterface.java @@ -0,0 +1,83 @@ +package com.strategyobject.substrateclient.pallet; + +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.pallet.annotations.Pallet; +import com.strategyobject.substrateclient.rpc.Rpc; +import lombok.val; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import java.io.IOException; + +import static com.strategyobject.substrateclient.pallet.Constants.CLASS_NAME_TEMPLATE; +import static com.strategyobject.substrateclient.pallet.Constants.RPC; + +public class PalletAnnotatedInterface { + private final TypeElement interfaceElement; + private final String name; + private final PalletMethodProcessor methodProcessor; + + public PalletAnnotatedInterface(TypeElement interfaceElement, PalletMethodProcessor methodProcessor) throws ProcessingException { + this.interfaceElement = interfaceElement; + val annotation = interfaceElement.getAnnotation(Pallet.class); + + if (!interfaceElement.getModifiers().contains(Modifier.PUBLIC)) { + throw new ProcessingException( + interfaceElement, + "`%s` is not public. That is not allowed.", + interfaceElement.getQualifiedName().toString()); + } + + if (Strings.isNullOrEmpty(name = annotation.value())) { + throw new ProcessingException( + interfaceElement, + "`@%s` of `%s` contains null or empty `value`.", + annotation.getClass().getSimpleName(), + interfaceElement.getQualifiedName().toString()); + } + + this.methodProcessor = methodProcessor; + } + + public void generateClass(ProcessorContext context) throws ProcessingException, IOException { + val interfaceName = interfaceElement.getSimpleName().toString(); + val className = String.format(CLASS_NAME_TEMPLATE, interfaceName); + val packageName = context.getPackageName(interfaceElement); + + val typeSpecBuilder = TypeSpec.classBuilder(className) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addSuperinterface(TypeName.get(interfaceElement.asType())) + .addField(Rpc.class, RPC, Modifier.FINAL, Modifier.PRIVATE); + + val constructorBuilder = createConstructorBuilder(); + + for (val method : interfaceElement.getEnclosedElements()) { + this.methodProcessor.process(name, + (ExecutableElement) method, + typeSpecBuilder, + constructorBuilder, + context); + } + + typeSpecBuilder.addMethod(constructorBuilder.build()); + + JavaFile.builder(packageName, typeSpecBuilder.build()).build().writeTo(context.getFiler()); + } + + private MethodSpec.Builder createConstructorBuilder() { + return MethodSpec.constructorBuilder() + .addModifiers(Modifier.PUBLIC) + .addParameter(Rpc.class, RPC) + .beginControlFlow("if ($L == null)", RPC) + .addStatement("throw new $T(\"$L can't be null.\")", IllegalArgumentException.class, RPC) + .endControlFlow() + .addStatement("this.$1L = $1L", RPC); + } +} diff --git a/pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/PalletInterfaceProcessor.java b/pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/PalletInterfaceProcessor.java new file mode 100644 index 00000000..020cbc7c --- /dev/null +++ b/pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/PalletInterfaceProcessor.java @@ -0,0 +1,69 @@ +package com.strategyobject.substrateclient.pallet; + +import com.google.auto.service.AutoService; +import com.strategyobject.substrateclient.common.codegen.ProcessingException; +import com.strategyobject.substrateclient.common.codegen.ProcessorContext; +import com.strategyobject.substrateclient.pallet.annotations.Pallet; +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.Collections; +import java.util.Set; + +@SupportedAnnotationTypes("com.strategyobject.substrateclient.pallet.annotations.Pallet") +@SupportedSourceVersion(SourceVersion.RELEASE_8) +@AutoService(Processor.class) +public class PalletInterfaceProcessor 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(Pallet.class)) { + if (annotatedElement.getKind() != ElementKind.INTERFACE) { + context.error( + annotatedElement, + "Only interfaces can be annotated with `@%s`.", + Pallet.class.getSimpleName()); + + return true; + } + + val typeElement = (TypeElement) annotatedElement; + try { + val annotatedInterface = new PalletAnnotatedInterface( + typeElement, + new CompoundMethodProcessor(typeElement, + Collections.singletonList( + new StorageProcessor(typeElement) + ))); + + annotatedInterface.generateClass(context); + } catch (ProcessingException e) { + context.error(typeElement, e); + return true; + } catch (IOException e) { + context.error(e); + return true; + } + } + + return true; + } +} diff --git a/pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/PalletMethodProcessor.java b/pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/PalletMethodProcessor.java new file mode 100644 index 00000000..139cb6d3 --- /dev/null +++ b/pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/PalletMethodProcessor.java @@ -0,0 +1,23 @@ +package com.strategyobject.substrateclient.pallet; + +import com.squareup.javapoet.MethodSpec; +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; + +@RequiredArgsConstructor +abstract class PalletMethodProcessor { + @NonNull + protected final TypeElement palletElement; + + abstract void process(@NonNull String palletName, + @NonNull ExecutableElement method, + @NonNull TypeSpec.Builder typeSpecBuilder, + MethodSpec.Builder constructorBuilder, + @NonNull ProcessorContext context) throws ProcessingException; +} diff --git a/pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/StorageProcessor.java b/pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/StorageProcessor.java new file mode 100644 index 00000000..cba323cc --- /dev/null +++ b/pallet/pallet-codegen/src/main/java/com.strategyobject.substrateclient.pallet/StorageProcessor.java @@ -0,0 +1,441 @@ +package com.strategyobject.substrateclient.pallet; + +import com.google.common.base.Strings; +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.AnnotationUtils; +import com.strategyobject.substrateclient.common.codegen.ProcessingException; +import com.strategyobject.substrateclient.common.codegen.ProcessorContext; +import com.strategyobject.substrateclient.common.codegen.TypeTraverser; +import com.strategyobject.substrateclient.pallet.annotations.Storage; +import com.strategyobject.substrateclient.pallet.annotations.StorageHasher; +import com.strategyobject.substrateclient.scale.ScaleReader; +import com.strategyobject.substrateclient.scale.ScaleWriter; +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 com.strategyobject.substrateclient.storage.*; +import com.strategyobject.substrateclient.types.FixedBytes; +import com.strategyobject.substrateclient.types.Size; +import lombok.NonNull; +import lombok.val; +import lombok.var; + +import javax.lang.model.element.*; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; +import java.util.List; +import java.util.Objects; + +import static com.strategyobject.substrateclient.common.codegen.AnnotationUtils.suppressWarnings; +import static com.strategyobject.substrateclient.pallet.Constants.*; + +class StorageProcessor extends PalletMethodProcessor { + private static final String VALUE_READER = "valueReader"; + private static final String KEY_PROVIDER = "keyProvider"; + private static final String STORAGE_MAP = "storage"; + private static final String HASHERS = "hashers"; + private static final String INITIALIZER_SUFFIX = "Initializer"; + + public StorageProcessor(@NonNull TypeElement palletElement) { + super(palletElement); + } + + @Override + void process(@NonNull String palletName, + @NonNull ExecutableElement method, + TypeSpec.@NonNull Builder typeSpecBuilder, + MethodSpec.Builder constructorBuilder, + @NonNull ProcessorContext context) throws ProcessingException { + val annotation = AnnotationUtils.getAnnotationMirror(method, Storage.class); + if (annotation == null) { + return; + } + + validate(method, annotation, context); + + createBackField(typeSpecBuilder, method); + typeSpecBuilder.addMethod(backFieldInitializer(palletName, method, annotation, context)); + assignBackFieldInConstructor(constructorBuilder, method); + typeSpecBuilder.addMethod(publicMethod(method)); + } + + private void createBackField(TypeSpec.Builder typeSpecBuilder, ExecutableElement method) { + typeSpecBuilder.addField( + TypeName.get(method.getReturnType()), + method.getSimpleName().toString(), + Modifier.FINAL, Modifier.PRIVATE + ); + } + + private void assignBackFieldInConstructor(MethodSpec.Builder constructorBuilder, ExecutableElement method) { + constructorBuilder.addStatement("this.$1L = $1L$2L()", + method.getSimpleName().toString(), + INITIALIZER_SUFFIX); + } + + private MethodSpec publicMethod(ExecutableElement method) { + val returnType = method.getReturnType(); + + return MethodSpec.methodBuilder(method.getSimpleName().toString()) + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(TypeName.get(returnType)) + .addStatement("return this.$L", method.getSimpleName().toString()) + .build(); + } + + private MethodSpec backFieldInitializer(String palletName, + ExecutableElement method, + AnnotationMirror storageAnnotation, + ProcessorContext context) throws ProcessingException { + val returnType = method.getReturnType(); + validateMethodSignature(method, returnType, context); + + val valueType = ((DeclaredType) returnType).getTypeArguments().get(0); + + val methodSpecBuilder = + MethodSpec.methodBuilder(method.getSimpleName().toString() + INITIALIZER_SUFFIX) + .addModifiers(Modifier.PRIVATE) + .addAnnotation(suppressWarnings("unchecked", "rawtypes")) + .returns(TypeName.get(returnType)); + + declareReaderAndWriterRegistries(methodSpecBuilder); + + val scaleAnnotationParser = new ScaleAnnotationParser(context); + val readerCompositor = ReaderCompositor.disallowOpenGeneric( + context, + SCALE_READER_REGISTRY); + val writerCompositor = WriterCompositor.disallowOpenGeneric( + context, + SCALE_WRITER_REGISTRY); + + assignStorageMapImpl(methodSpecBuilder, + palletName, + method, + storageAnnotation, + valueType, + scaleAnnotationParser, + readerCompositor, + writerCompositor, + context); + + methodSpecBuilder + .addStatement("return $L", STORAGE_MAP); + + + return methodSpecBuilder.build(); + } + + private void assignStorageMapImpl(MethodSpec.Builder methodSpecBuilder, + String palletName, + ExecutableElement method, + AnnotationMirror storageAnnotation, + TypeMirror valueType, + ScaleAnnotationParser scaleAnnotationParser, + ReaderCompositor readerCompositor, + WriterCompositor writerCompositor, + ProcessorContext context) throws ProcessingException { + assignValueReader(methodSpecBuilder, + method, + valueType, + scaleAnnotationParser, + readerCompositor); + + assignKeyProvider(methodSpecBuilder, + method, + palletName, + storageAnnotation, + scaleAnnotationParser, + readerCompositor, + writerCompositor, + context); + + methodSpecBuilder + .addStatement("$1T $2L = $1T.$3L($4L, $5L, $6L)", + StorageNMapImpl.class, + STORAGE_MAP, + STORAGE_FACTORY_METHOD, + RPC, + VALUE_READER, + KEY_PROVIDER); + } + + private void assignKeyProvider(MethodSpec.Builder methodSpecBuilder, + ExecutableElement method, + String palletName, + AnnotationMirror storageAnnotation, + ScaleAnnotationParser scaleAnnotationParser, + ReaderCompositor readerCompositor, + WriterCompositor writerCompositor, + ProcessorContext context) throws ProcessingException { + val storageName = AnnotationUtils.getValueFromAnnotation(storageAnnotation, + "value"); + + methodSpecBuilder.addStatement("$1T $2L = $1T.$3L($4S, $5S)", + StorageKeyProvider.class, + KEY_PROVIDER, + STORAGE_KEY_PROVIDER_FACTORY_METHOD, + palletName, + storageName); + + val keys = AnnotationUtils + .>getValueFromAnnotation(storageAnnotation, + "keys"); + + if (keys != null && keys.size() > 0) { + methodSpecBuilder.addStatement("$1T[] $2L = new $1T[$3L]", + KeyHasher.class, + HASHERS, + keys.size()); + + for (var i = 0; i < keys.size(); i++) { + putHasher(methodSpecBuilder, + method, + storageName, + context, + keys.get(i), + scaleAnnotationParser, + readerCompositor, + writerCompositor, + CodeBlock + .builder() + .add("$L[$L]", HASHERS, i) + .build()); + } + + methodSpecBuilder.addStatement("$L.$L($L)", + KEY_PROVIDER, + STORAGE_KEY_PROVIDER_ADD_HASHERS, + HASHERS); + } + } + + private void putHasher(MethodSpec.Builder methodSpecBuilder, + ExecutableElement method, + String storageName, + ProcessorContext context, + AnnotationMirror keyAnnotation, + ScaleAnnotationParser scaleAnnotationParser, + ReaderCompositor readerCompositor, + WriterCompositor writerCompositor, + CodeBlock hasher) throws ProcessingException { + val type = AnnotationUtils.getValueFromAnnotation(keyAnnotation, "type"); + val typeOverride = type != null + ? scaleAnnotationParser.parse(type) + : scaleAnnotationParser.parse( + Objects.requireNonNull(AnnotationUtils.getValueFromAnnotation(keyAnnotation, "generic"))); + val keySize = determineKeySize(context, typeOverride); + val readerCode = readerCompositor.traverse(typeOverride); + val writerCode = writerCompositor.traverse(typeOverride); + + methodSpecBuilder + .addStatement( + CodeBlock.builder() + .add(hasher) + .add(" = $T.$L(($T)", + KeyHasher.class, + KEY_HASHER_FACTORY_METHOD, + ScaleWriter.class) + .add(writerCode) + .add(", ($T)", ScaleReader.class) + .add(readerCode) + .add(", ") + .add(resolveHashingAlgorithm(keyAnnotation, + keySize, + storageName, + method)) + .add(")") + .build()); + } + + private int determineKeySize(ProcessorContext context, + TypeTraverser.TypeTreeNode typeOverride) { + if (context.isSubtype(typeOverride.getType(), context.erasure(context.getType(FixedBytes.class)))) { + val fixedBytes = ((TypeElement) ((DeclaredType) typeOverride.getType()) + .asElement()) + .getSuperclass(); + val size = ((DeclaredType) fixedBytes).getTypeArguments().get(0); + + val zero = context.getType(Size.Zero.class); + if (context.isAssignable(size, zero)) { + return Size.zero.getValue(); + } + + val of32 = context.getType(Size.Of32.class); + if (context.isAssignable(size, of32)) { + return Size.of32.getValue(); + } + + val of64 = context.getType(Size.Of64.class); + if (context.isAssignable(size, of64)) { + return Size.of64.getValue(); + } + + val of96 = context.getType(Size.Of96.class); + if (context.isAssignable(size, of96)) { + return Size.of96.getValue(); + } + + val of128 = context.getType(Size.Of128.class); + if (context.isAssignable(size, of128)) { + return Size.of128.getValue(); + } + } + + return -1; + } + + private CodeBlock resolveHashingAlgorithm(AnnotationMirror keyAnnotation, + int keySize, + String storageAnnotation, + ExecutableElement method) throws ProcessingException { + val builder = CodeBlock.builder(); + val hasher = AnnotationUtils.getValueFromAnnotation(keyAnnotation, "hasher"); + val hasherName = Objects.requireNonNull(hasher).getSimpleName().toString(); + + if (hasherName.equals(StorageHasher.Blake2B128Concat.toString())) { + builder.add("$T.$L()", + Blake2B128Concat.class, + BLAKE_2_128_CONCAT_INSTANCE); + } + + if (hasherName.equals(StorageHasher.TwoX64Concat.toString())) { + builder.add("$T.$L()", + TwoX64Concat.class, + TWO_X64_CONCAT_INSTANCE); + } + + if (hasherName.equals(StorageHasher.Identity.toString())) { + if (keySize < 0) { + throw new ProcessingException( + palletElement, + "`@%s` of `%s.%s` contains an incorrect type of key or a hashing algorithm. " + + "`%s` can only be applied to a key with fixed size.", + storageAnnotation.getClass().getSimpleName(), + palletElement.getQualifiedName().toString(), + StorageHasher.Identity.toString(), + method.getSimpleName()); + } + + builder.add("$T.$L()", + Identity.class, + IDENTITY_INSTANCE); + } + + return builder.build(); + } + + private void assignValueReader(MethodSpec.Builder methodSpecBuilder, + ExecutableElement method, + TypeMirror valueType, + ScaleAnnotationParser scaleAnnotationParser, + ReaderCompositor readerCompositor) { + val typeOverride = scaleAnnotationParser.parse(method); + val readerCode = typeOverride != null ? + readerCompositor.traverse(valueType, typeOverride) : + readerCompositor.traverse(valueType); + + methodSpecBuilder + .addStatement(CodeBlock.builder() + .add("$T $L = ", ScaleReader.class, VALUE_READER) + .add(readerCode) + .build()); + } + + private void validateMethodSignature(ExecutableElement method, + TypeMirror returnType, + ProcessorContext context) throws ProcessingException { + val expectedReturnType = context.erasure( + context.getType(StorageNMap.class)); + if (!context.isSameType(expectedReturnType, context.erasure(returnType))) { + throw new ProcessingException( + palletElement, + "Method `%s.%s` has unexpected return type. Must be `%s`.", + palletElement.getQualifiedName().toString(), + method.getSimpleName(), + StorageNMap.class.getSimpleName()); + } + + if (method.getParameters().size() > 0) { + throw new ProcessingException( + palletElement, + "Method `%s.%s` mustn't have parameters.", + palletElement.getQualifiedName().toString(), + method.getSimpleName()); + } + } + + protected void validate(ExecutableElement method, AnnotationMirror storageAnnotation, ProcessorContext context) + throws ProcessingException { + ensureNameIsSet(method, storageAnnotation); + validateKeys(method, storageAnnotation); + + val returnType = method.getReturnType(); + if (!context.isSameType(context.erasure(returnType), context.erasure(context.getType(StorageNMap.class)))) { + throw new ProcessingException( + palletElement, + "`Method `%s.%s` has incorrect return type." + + "Must be `%s`.", + palletElement.getQualifiedName().toString(), + method.getSimpleName(), + StorageNMap.class.getSimpleName()); + } + } + + private void validateKeys(ExecutableElement method, AnnotationMirror storageAnnotation) throws ProcessingException { + val keys = AnnotationUtils.>getValueFromAnnotation(storageAnnotation, + "keys"); + if (keys == null) { + return; + } + + for (val key : keys) { + val type = AnnotationUtils.getValueFromAnnotation(key, "type"); + val generic = AnnotationUtils.getValueFromAnnotation(key, "generic"); + if (type == null && generic == null) { + throw new ProcessingException( + palletElement, + "`@%s` of `%s.%s` isn't adjusted correctly. " + + "Please set either `type` or `generic` parameter of @StorageKey.", + storageAnnotation.getClass().getSimpleName(), + palletElement.getQualifiedName().toString(), + method.getSimpleName()); + } + + if (type != null && generic != null) { + throw new ProcessingException( + palletElement, + "`@%s` of `%s.%s` isn't adjusted correctly. " + + "Ambiguous scale type of key. " + + "Please set only one parameter of @StorageKey: `type` or `generic`.", + storageAnnotation.getClass().getSimpleName(), + palletElement.getQualifiedName().toString(), + method.getSimpleName()); + } + } + } + + private void ensureNameIsSet(ExecutableElement method, AnnotationMirror storageAnnotation) throws ProcessingException { + val name = AnnotationUtils.getValueFromAnnotation(storageAnnotation, + "value"); + if (Strings.isNullOrEmpty(name)) { + throw new ProcessingException( + palletElement, + "`@%s` of `%s.%s` doesn't have `value`.", + Storage.class.getSimpleName(), + palletElement.getQualifiedName().toString(), + method.getSimpleName()); + } + } + + private void declareReaderAndWriterRegistries(MethodSpec.Builder methodSpecBuilder) { + methodSpecBuilder + .addStatement("$1T $2L = $1T.getInstance()", ScaleReaderRegistry.class, SCALE_READER_REGISTRY) + .addStatement("$1T $2L = $1T.getInstance()", ScaleWriterRegistry.class, SCALE_WRITER_REGISTRY); + } +} diff --git a/pallet/pallet-codegen/src/test/java/com/strategyobject/substrateclient/pallet/PalletInterfaceProcessorTests.java b/pallet/pallet-codegen/src/test/java/com/strategyobject/substrateclient/pallet/PalletInterfaceProcessorTests.java new file mode 100644 index 00000000..d7a84389 --- /dev/null +++ b/pallet/pallet-codegen/src/test/java/com/strategyobject/substrateclient/pallet/PalletInterfaceProcessorTests.java @@ -0,0 +1,119 @@ +package com.strategyobject.substrateclient.pallet; + +import com.google.testing.compile.Compilation; +import com.google.testing.compile.JavaFileObjects; +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; + +public class PalletInterfaceProcessorTests { + @Test + void failsWhenAnnotationIsAppliedToClass() { + val clazz = JavaFileObjects.forResource("ClassPallet.java"); + + val compilation = javac() + .withProcessors(new PalletInterfaceProcessor()) + .compile(clazz); + + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining("Only interfaces"); + } + + @Test + void failsWhenPalletHasEmptyName() { + val clazz = JavaFileObjects.forResource("UnnamedPallet.java"); + + val compilation = javac() + .withProcessors(new PalletInterfaceProcessor()) + .compile(clazz); + + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining("contains null or empty `value`"); + } + + @Test + void failsWhenStorageHasEmptyName() { + val clazz = JavaFileObjects.forResource("UnnamedStorage.java"); + + val compilation = javac() + .withProcessors(new PalletInterfaceProcessor()) + .compile(clazz); + + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining("doesn't have `value`"); + } + + @Test + void failsWhenScaleIsNotSet() { + val clazz = JavaFileObjects.forResource("WithoutScale.java"); + + val compilation = javac() + .withProcessors(new PalletInterfaceProcessor()) + .compile(clazz); + + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining("Please set either `type` or `generic` parameter of @StorageKey"); + } + + @Test + void failsWhenScaleTypeOfKeyIsAmbiguous() { + val clazz = JavaFileObjects.forResource("AmbiguousScale.java"); + + val compilation = javac() + .withProcessors(new PalletInterfaceProcessor()) + .compile(clazz); + + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining("Please set only one parameter of @StorageKey: `type` or `generic`."); + } + + @Test + void failsWhenStorageMethodReturnsIncorrectType() { + val clazz = JavaFileObjects.forResource("NotAStorage.java"); + + val compilation = javac() + .withProcessors(new PalletInterfaceProcessor()) + .compile(clazz); + + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining("has incorrect return type"); + } + + @Test + public void compiles() { + val generatedName = String.format("TestPalletImpl"); + + val clazz = JavaFileObjects.forResource("TestPallet.java"); + val compilation = javac() + .withProcessors(new PalletInterfaceProcessor()) + .compile(clazz); + + assertThat(compilation).succeeded(); + + assertContains(generatedName, compilation, "private final Rpc rpc;"); + assertContains(generatedName, compilation, "private final StorageNMap value;"); + assertContains(generatedName, compilation, "private final StorageNMap map;"); + assertContains(generatedName, compilation, "private final StorageNMap doubleMap;"); + assertContains(generatedName, compilation, "private final StorageNMap tripleMap;"); + assertContains(generatedName, compilation, "public TestPalletImpl(Rpc rpc)"); + assertContains(generatedName, compilation, "public StorageNMap value()"); + assertContains(generatedName, compilation, "public StorageNMap map()"); + assertContains(generatedName, compilation, "public StorageNMap doubleMap()"); + assertContains(generatedName, compilation, "public StorageNMap tripleMap()"); + } + + private void assertContains(String className, Compilation compilation, String target) { + assertThat(compilation) + .generatedSourceFile(className) + .contentsAsUtf8String() + .contains(target); + } +} diff --git a/pallet/pallet-codegen/src/test/resources/AmbiguousScale.java b/pallet/pallet-codegen/src/test/resources/AmbiguousScale.java new file mode 100644 index 00000000..abef9033 --- /dev/null +++ b/pallet/pallet-codegen/src/test/resources/AmbiguousScale.java @@ -0,0 +1,26 @@ +import com.strategyobject.substrateclient.pallet.annotations.Pallet; +import com.strategyobject.substrateclient.pallet.annotations.Storage; +import com.strategyobject.substrateclient.pallet.annotations.StorageHasher; +import com.strategyobject.substrateclient.pallet.annotations.StorageKey; +import com.strategyobject.substrateclient.scale.ScaleType; +import com.strategyobject.substrateclient.scale.annotations.Scale; +import com.strategyobject.substrateclient.scale.annotations.ScaleGeneric; +import com.strategyobject.substrateclient.storage.StorageNMap; + +@Pallet("Test") +public interface AmbiguousScale { + @Storage(value = "Test", keys = { + @StorageKey( + hasher = StorageHasher.Blake2B128Concat, + type = @Scale(ScaleType.I32.class), + generic = @ScaleGeneric( + template = "Option", + types = { + @Scale(ScaleType.Option.class), + @Scale(ScaleType.I32.class) + } + ) + ) + }) + StorageNMap test(); +} diff --git a/pallet/pallet-codegen/src/test/resources/ClassPallet.java b/pallet/pallet-codegen/src/test/resources/ClassPallet.java new file mode 100644 index 00000000..98223c74 --- /dev/null +++ b/pallet/pallet-codegen/src/test/resources/ClassPallet.java @@ -0,0 +1,9 @@ +import com.strategyobject.substrateclient.pallet.annotations.Pallet; +import com.strategyobject.substrateclient.pallet.annotations.Storage; +import com.strategyobject.substrateclient.storage.StorageNMap; + +@Pallet("Class") +public abstract class ClassPallet { + @Storage("Test") + public abstract StorageNMap test(); +} diff --git a/pallet/pallet-codegen/src/test/resources/NotAStorage.java b/pallet/pallet-codegen/src/test/resources/NotAStorage.java new file mode 100644 index 00000000..dc7afc92 --- /dev/null +++ b/pallet/pallet-codegen/src/test/resources/NotAStorage.java @@ -0,0 +1,17 @@ +import com.strategyobject.substrateclient.pallet.annotations.Pallet; +import com.strategyobject.substrateclient.pallet.annotations.Storage; +import com.strategyobject.substrateclient.pallet.annotations.StorageHasher; +import com.strategyobject.substrateclient.pallet.annotations.StorageKey; +import com.strategyobject.substrateclient.scale.ScaleType; +import com.strategyobject.substrateclient.scale.annotations.Scale; + +@Pallet("Test") +public interface NotAStorage { + @Storage(value = "Test", keys = { + @StorageKey( + hasher = StorageHasher.Blake2B128Concat, + type = @Scale(ScaleType.I32.class) + ) + }) + String test(); +} diff --git a/pallet/pallet-codegen/src/test/resources/TestPallet.java b/pallet/pallet-codegen/src/test/resources/TestPallet.java new file mode 100644 index 00000000..e39037fd --- /dev/null +++ b/pallet/pallet-codegen/src/test/resources/TestPallet.java @@ -0,0 +1,55 @@ +import com.strategyobject.substrateclient.pallet.annotations.Pallet; +import com.strategyobject.substrateclient.pallet.annotations.Storage; +import com.strategyobject.substrateclient.pallet.annotations.StorageHasher; +import com.strategyobject.substrateclient.pallet.annotations.StorageKey; +import com.strategyobject.substrateclient.rpc.types.AccountId; +import com.strategyobject.substrateclient.scale.ScaleType; +import com.strategyobject.substrateclient.scale.annotations.Scale; +import com.strategyobject.substrateclient.scale.annotations.ScaleGeneric; +import com.strategyobject.substrateclient.storage.StorageNMap; +import com.strategyobject.substrateclient.types.Result; + +@Pallet("Test") +public interface TestPallet { + @Storage("Value") + StorageNMap value(); + + @Storage( + value = "Map", + keys = { + @StorageKey(type = @Scale(ScaleType.I32.class), + hasher = StorageHasher.TwoX64Concat) + }) + StorageNMap map(); + + @Storage( + value = "DoubleMap", + keys = { + @StorageKey(type = @Scale(ScaleType.I32.class), + hasher = StorageHasher.TwoX64Concat), + @StorageKey( + generic = @ScaleGeneric( + template = "Result", + types = { + @Scale(Result.class), + @Scale(ScaleType.Bool.class), + @Scale(ScaleType.String.class) + } + ), + hasher = StorageHasher.Blake2B128Concat + ) + }) + StorageNMap doubleMap(); + + @Storage( + value = "TripleMap", + keys = { + @StorageKey(type = @Scale(String.class), + hasher = StorageHasher.Blake2B128Concat), + @StorageKey(type = @Scale(ScaleType.I32.class), + hasher = StorageHasher.TwoX64Concat), + @StorageKey(type = @Scale(AccountId.class), + hasher = StorageHasher.Identity) + }) + StorageNMap tripleMap(); +} diff --git a/pallet/pallet-codegen/src/test/resources/UnnamedPallet.java b/pallet/pallet-codegen/src/test/resources/UnnamedPallet.java new file mode 100644 index 00000000..0785901a --- /dev/null +++ b/pallet/pallet-codegen/src/test/resources/UnnamedPallet.java @@ -0,0 +1,9 @@ +import com.strategyobject.substrateclient.pallet.annotations.Pallet; +import com.strategyobject.substrateclient.pallet.annotations.Storage; +import com.strategyobject.substrateclient.storage.StorageNMap; + +@Pallet("") +public interface UnnamedPallet { + @Storage("Test") + StorageNMap test(); +} diff --git a/pallet/pallet-codegen/src/test/resources/UnnamedStorage.java b/pallet/pallet-codegen/src/test/resources/UnnamedStorage.java new file mode 100644 index 00000000..b9b5c1f9 --- /dev/null +++ b/pallet/pallet-codegen/src/test/resources/UnnamedStorage.java @@ -0,0 +1,9 @@ +import com.strategyobject.substrateclient.pallet.annotations.Pallet; +import com.strategyobject.substrateclient.pallet.annotations.Storage; +import com.strategyobject.substrateclient.storage.StorageNMap; + +@Pallet("Test") +public interface UnnamedStorage { + @Storage("") + StorageNMap unnamed(); +} diff --git a/pallet/pallet-codegen/src/test/resources/WithoutScale.java b/pallet/pallet-codegen/src/test/resources/WithoutScale.java new file mode 100644 index 00000000..21bfc264 --- /dev/null +++ b/pallet/pallet-codegen/src/test/resources/WithoutScale.java @@ -0,0 +1,13 @@ +import com.strategyobject.substrateclient.pallet.annotations.Pallet; +import com.strategyobject.substrateclient.pallet.annotations.Storage; +import com.strategyobject.substrateclient.pallet.annotations.StorageHasher; +import com.strategyobject.substrateclient.pallet.annotations.StorageKey; +import com.strategyobject.substrateclient.storage.StorageNMap; + +@Pallet("Test") +public interface WithoutScale { + @Storage(value = "Test", keys = { + @StorageKey(hasher = StorageHasher.Blake2B128Concat) + }) + StorageNMap test(); +} diff --git a/pallet/src/main/java/com/strategyobject/substrateclient/pallet/GeneratedPalletResolver.java b/pallet/src/main/java/com/strategyobject/substrateclient/pallet/GeneratedPalletResolver.java new file mode 100644 index 00000000..13085dd0 --- /dev/null +++ b/pallet/src/main/java/com/strategyobject/substrateclient/pallet/GeneratedPalletResolver.java @@ -0,0 +1,33 @@ +package com.strategyobject.substrateclient.pallet; + +import com.strategyobject.substrateclient.pallet.annotations.Pallet; +import com.strategyobject.substrateclient.rpc.Rpc; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.val; + +@RequiredArgsConstructor(staticName = "with") +public class GeneratedPalletResolver implements PalletResolver { + private static final String CLASS_NAME_TEMPLATE = "%sImpl"; + private final @NonNull Rpc rpc; + + @Override + public T resolve(Class interfaceClass) { + if (interfaceClass.getDeclaredAnnotationsByType(Pallet.class).length == 0) { + throw new IllegalArgumentException( + String.format("%s can't be constructed because it is not annotated with @%s.", + interfaceClass.getSimpleName(), + Pallet.class.getSimpleName())); + } + + Class implClazz; + try { + implClazz = Class.forName(String.format(CLASS_NAME_TEMPLATE, interfaceClass.getCanonicalName())); + val ctor = implClazz.getConstructor(Rpc.class); + + return interfaceClass.cast(ctor.newInstance(rpc)); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } +} diff --git a/pallet/src/main/java/com/strategyobject/substrateclient/pallet/PalletResolver.java b/pallet/src/main/java/com/strategyobject/substrateclient/pallet/PalletResolver.java new file mode 100644 index 00000000..22bfeec7 --- /dev/null +++ b/pallet/src/main/java/com/strategyobject/substrateclient/pallet/PalletResolver.java @@ -0,0 +1,5 @@ +package com.strategyobject.substrateclient.pallet; + +public interface PalletResolver { + T resolve(Class clazz); +} diff --git a/pallet/src/main/java/com/strategyobject/substrateclient/pallet/annotations/Pallet.java b/pallet/src/main/java/com/strategyobject/substrateclient/pallet/annotations/Pallet.java new file mode 100644 index 00000000..0d91ce76 --- /dev/null +++ b/pallet/src/main/java/com/strategyobject/substrateclient/pallet/annotations/Pallet.java @@ -0,0 +1,22 @@ +package com.strategyobject.substrateclient.pallet.annotations; + +import lombok.NonNull; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates the pallet which represents a proxy to the blockchain's pallet. + * The processor will generate proper implementations for the interfaces defined with this annotation. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface Pallet { + /** + * @return the name of the pallet + */ + @NonNull + String value(); +} diff --git a/pallet/src/main/java/com/strategyobject/substrateclient/pallet/annotations/Storage.java b/pallet/src/main/java/com/strategyobject/substrateclient/pallet/annotations/Storage.java new file mode 100644 index 00000000..651e078d --- /dev/null +++ b/pallet/src/main/java/com/strategyobject/substrateclient/pallet/annotations/Storage.java @@ -0,0 +1,24 @@ +package com.strategyobject.substrateclient.pallet.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates the storage of the pallet. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.SOURCE) +public @interface Storage { + /** + * @return the name of the storage + */ + String value(); + + /** + * @return the array of items which describe SCALE-codecs + * and hashing algorithms of storage's keys. + */ + StorageKey[] keys() default {}; +} diff --git a/pallet/src/main/java/com/strategyobject/substrateclient/pallet/annotations/StorageHasher.java b/pallet/src/main/java/com/strategyobject/substrateclient/pallet/annotations/StorageHasher.java new file mode 100644 index 00000000..69b6d4aa --- /dev/null +++ b/pallet/src/main/java/com/strategyobject/substrateclient/pallet/annotations/StorageHasher.java @@ -0,0 +1,19 @@ +package com.strategyobject.substrateclient.pallet.annotations; + +/** + * Represents a kind of key's hash algorithm + */ +public enum StorageHasher { + /** + * Blake2 128 Concat hash algorithm. + */ + Blake2B128Concat, + /** + * TwoX 64 Concat hash algorithm. + */ + TwoX64Concat, + /** + * Identity hash algorithm. + */ + Identity +} diff --git a/pallet/src/main/java/com/strategyobject/substrateclient/pallet/annotations/StorageKey.java b/pallet/src/main/java/com/strategyobject/substrateclient/pallet/annotations/StorageKey.java new file mode 100644 index 00000000..99866013 --- /dev/null +++ b/pallet/src/main/java/com/strategyobject/substrateclient/pallet/annotations/StorageKey.java @@ -0,0 +1,28 @@ +package com.strategyobject.substrateclient.pallet.annotations; + +import com.strategyobject.substrateclient.scale.annotations.Scale; +import com.strategyobject.substrateclient.scale.annotations.ScaleGeneric; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Describes the key's SCALE-codec and hash algorithm. + */ +@Retention(RetentionPolicy.SOURCE) +public @interface StorageKey { + /** + * @return the SCALE representation of the key in case it's non-generic. + */ + Scale type() default @Scale(); + + /** + * @return the SCALE representation of the key in case it's generic. + */ + ScaleGeneric generic() default @ScaleGeneric(template = "", types = {}); + + /** + * @return the hash algorithm of the key that's used to generate the map's key. + */ + StorageHasher hasher(); +} diff --git a/pallet/src/test/java/com/strategyobject/substrateclient/pallet/GeneratedPalletResolverTests.java b/pallet/src/test/java/com/strategyobject/substrateclient/pallet/GeneratedPalletResolverTests.java new file mode 100644 index 00000000..1cf0d39e --- /dev/null +++ b/pallet/src/test/java/com/strategyobject/substrateclient/pallet/GeneratedPalletResolverTests.java @@ -0,0 +1,41 @@ +package com.strategyobject.substrateclient.pallet; + +import com.strategyobject.substrateclient.rpc.Rpc; +import lombok.val; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +public class GeneratedPalletResolverTests { + @Test + public void throwsWhenPalletIsNotAnnotated() { + val rpc = mock(Rpc.class); + + val resolver = GeneratedPalletResolver.with(rpc); + + assertThrows(IllegalArgumentException.class, + () -> resolver.resolve(TestPalletNotAnnotated.class)); + } + + @Test + public void throwsWhenPalletImplementationDoesNotHaveAppropriateConstructor() { + val rpc = mock(Rpc.class); + + val resolver = GeneratedPalletResolver.with(rpc); + + assertThrows(RuntimeException.class, + () -> resolver.resolve(TestPalletWithoutConstructor.class)); + } + + @Test + public void resolve() { + val rpc = mock(Rpc.class); + + val resolver = GeneratedPalletResolver.with(rpc); + val pallet = resolver.resolve(TestPallet.class); + + assertNotNull(pallet); + assertEquals(TestPalletImpl.class, pallet.getClass()); + } +} diff --git a/pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPallet.java b/pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPallet.java new file mode 100644 index 00000000..79fb2171 --- /dev/null +++ b/pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPallet.java @@ -0,0 +1,7 @@ +package com.strategyobject.substrateclient.pallet; + +import com.strategyobject.substrateclient.pallet.annotations.Pallet; + +@Pallet("Test") +public interface TestPallet { +} diff --git a/pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPalletImpl.java b/pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPalletImpl.java new file mode 100644 index 00000000..3eb675a9 --- /dev/null +++ b/pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPalletImpl.java @@ -0,0 +1,9 @@ +package com.strategyobject.substrateclient.pallet; + +import com.strategyobject.substrateclient.rpc.Rpc; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class TestPalletImpl implements TestPallet { + private final Rpc rpc; +} diff --git a/pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPalletNotAnnotated.java b/pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPalletNotAnnotated.java new file mode 100644 index 00000000..79e99755 --- /dev/null +++ b/pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPalletNotAnnotated.java @@ -0,0 +1,6 @@ +package com.strategyobject.substrateclient.pallet; + +import com.strategyobject.substrateclient.pallet.annotations.Pallet; + +public interface TestPalletNotAnnotated { +} diff --git a/pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPalletNotAnnotatedImpl.java b/pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPalletNotAnnotatedImpl.java new file mode 100644 index 00000000..5d5ca68e --- /dev/null +++ b/pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPalletNotAnnotatedImpl.java @@ -0,0 +1,9 @@ +package com.strategyobject.substrateclient.pallet; + +import com.strategyobject.substrateclient.rpc.Rpc; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class TestPalletNotAnnotatedImpl implements TestPalletNotAnnotated { + private final Rpc rpc; +} diff --git a/pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPalletWithoutConstructor.java b/pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPalletWithoutConstructor.java new file mode 100644 index 00000000..89f48dd9 --- /dev/null +++ b/pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPalletWithoutConstructor.java @@ -0,0 +1,7 @@ +package com.strategyobject.substrateclient.pallet; + +import com.strategyobject.substrateclient.pallet.annotations.Pallet; + +@Pallet("Test") +public interface TestPalletWithoutConstructor { +} diff --git a/pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPalletWithoutConstructorImpl.java b/pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPalletWithoutConstructorImpl.java new file mode 100644 index 00000000..9d754f3e --- /dev/null +++ b/pallet/src/test/java/com/strategyobject/substrateclient/pallet/TestPalletWithoutConstructorImpl.java @@ -0,0 +1,4 @@ +package com.strategyobject.substrateclient.pallet; + +public class TestPalletWithoutConstructorImpl implements TestPalletWithoutConstructor { +} diff --git a/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/RpcDecoderAnnotatedClass.java b/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/RpcDecoderAnnotatedClass.java index 3602af64..4ddf40fe 100644 --- a/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/RpcDecoderAnnotatedClass.java +++ b/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/decoder/RpcDecoderAnnotatedClass.java @@ -126,7 +126,7 @@ private void setFields(MethodSpec.Builder methodSpec, ProcessorContext context) DECODER_REGISTRY, SCALE_READER_REGISTRY); val scaleAnnotationParser = new ScaleAnnotationParser(context); - val scaleReaderCompositor = new ReaderCompositor(context, + val scaleReaderCompositor = ReaderCompositor.forAnyType(context, typeVarMap, String.format("%s[$L].%s", DECODERS_ARG, READER_ACCESSOR), SCALE_READER_REGISTRY); diff --git a/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/EncoderCompositor.java b/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/EncoderCompositor.java index 735715d0..16fae5be 100644 --- a/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/EncoderCompositor.java +++ b/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/EncoderCompositor.java @@ -53,7 +53,7 @@ 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)) { + if (context.isAssignable(type, selfEncodable)) { builder.add("$L.resolve($T.class)", encoderRegistryVarName, selfEncodable); } else { builder.add(encoderAccessor, typeVarMap.get(type.toString())); @@ -80,7 +80,7 @@ private CodeBlock getNonGenericCodeBlock(TypeMirror type) { .add("$T.$L(($T) ", EncoderPair.class, PAIR_FACTORY_METHOD, RpcEncoder.class) .add("$L.resolve($T.class)", encoderRegistryVarName, - context.isSubtypeOf(type, selfEncodable) ? + context.isAssignable(type, selfEncodable) ? selfEncodable : type) .add(", ($T) ", ScaleWriter.class) @@ -95,7 +95,7 @@ protected CodeBlock whenGenericType(@NonNull DeclaredType type, TypeMirror _over val builder = CodeBlock.builder() .add("$T.$L(", EncoderPair.class, PAIR_FACTORY_METHOD); - if (context.isSubtypeOf(resolveType, selfEncodable)) { + if (context.isAssignable(resolveType, selfEncodable)) { builder.add("($T) registry.resolve($T.class)", selfEncodable, RpcEncoder.class); } else { builder.add("$T.$L($T.class, ", RpcRegistryHelper.class, RESOLVE_AND_INJECT_METHOD, resolveType); @@ -118,6 +118,6 @@ protected CodeBlock whenGenericType(@NonNull DeclaredType type, TypeMirror _over @Override protected boolean doTraverseArguments(@NonNull DeclaredType type, TypeMirror override) { - return override != null || !context.isSubtypeOf(context.erasure(type), selfEncodable); + return override != null || !context.isAssignable(context.erasure(type), selfEncodable); } } diff --git a/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/RpcEncoderAnnotatedClass.java b/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/RpcEncoderAnnotatedClass.java index 5b1c9276..5cc2c689 100644 --- a/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/RpcEncoderAnnotatedClass.java +++ b/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/encoder/RpcEncoderAnnotatedClass.java @@ -124,7 +124,7 @@ private void setFields(MethodSpec.Builder methodSpec, ProcessorContext context) ENCODER_REGISTRY, SCALE_WRITER_REGISTRY); val scaleAnnotationParser = new ScaleAnnotationParser(context); - val scaleWriterCompositor = new WriterCompositor(context, + val scaleWriterCompositor = WriterCompositor.forAnyType(context, typeVarMap, String.format("%s[$L].%s", ENCODERS_ARG, WRITER_ACCESSOR), SCALE_WRITER_REGISTRY); @@ -212,7 +212,7 @@ private void setScaleField(MethodSpec.Builder methodSpec, 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)))) { + if (classTypeParametersSize == 0 || context.isAssignable(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 diff --git a/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcAnnotatedInterface.java b/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcAnnotatedInterface.java index 20bd7ad5..2c1a19ee 100644 --- a/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcAnnotatedInterface.java +++ b/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcAnnotatedInterface.java @@ -37,10 +37,10 @@ public RpcAnnotatedInterface(@NonNull TypeElement interfaceElement, interfaceElement.getQualifiedName().toString()); } - if (Strings.isNullOrEmpty(section = annotation.section())) { + if (Strings.isNullOrEmpty(section = annotation.value())) { throw new ProcessingException( interfaceElement, - "`@%s` of `%s` contains null or empty `section`.", + "`@%s` of `%s` contains null or empty `value`.", annotation.getClass().getSimpleName(), interfaceElement.getQualifiedName().toString()); } diff --git a/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcCallProcessor.java b/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcCallProcessor.java index 2c156c17..621ec438 100644 --- a/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcCallProcessor.java +++ b/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcCallProcessor.java @@ -29,10 +29,10 @@ public RpcCallProcessor(@NonNull TypeElement interfaceElement) { @Override protected void ensureAnnotationIsFilled(ExecutableElement method, RpcCall methodAnnotation) throws ProcessingException { - if (Strings.isNullOrEmpty(methodAnnotation.method())) { + if (Strings.isNullOrEmpty(methodAnnotation.value())) { throw new ProcessingException( interfaceElement, - "`@%s` of `%s.%s` contains null or empty `method`.", + "`@%s` of `%s.%s` contains null or empty `value`.", methodAnnotation.getClass().getSimpleName(), interfaceElement.getQualifiedName().toString(), method.getSimpleName()); @@ -51,7 +51,7 @@ protected void callProviderInterface(MethodSpec.Builder methodSpecBuilder, RpcCall annotation, ProcessorContext context, BiFunction decoder) { - val rpcMethodName = String.format(RPC_METHOD_NAME_TEMPLATE, section, annotation.method()); + val rpcMethodName = String.format(RPC_METHOD_NAME_TEMPLATE, section, annotation.value()); val paramsArgument = method.getParameters().size() == 0 ? "" : String.format(", %s", PARAMS_VAR); val isReturnVoid = isReturnVoid(method, context); diff --git a/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcMethodProcessor.java b/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcMethodProcessor.java index 75c2ab1d..1d8b5faa 100644 --- a/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcMethodProcessor.java +++ b/rpc/rpc-codegen/src/main/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcMethodProcessor.java @@ -146,9 +146,7 @@ private CodeBlock getScaleReadCodeBlock(AnnotatedConstruct annotated, String arg, ScaleAnnotationParser scaleAnnotationParser, ProcessorContext context) { - val readerCompositor = new ReaderCompositor(context, - EMPTY_TYPE_VAR_MAP, - String.format("%s[$L].%s", DECODERS_ARG, READER_ACCESSOR), + val readerCompositor = ReaderCompositor.disallowOpenGeneric(context, SCALE_READER_REGISTRY); val typeOverride = scaleAnnotationParser.parse(annotated); val readerCode = typeOverride != null ? @@ -176,9 +174,7 @@ private void processParameters(MethodSpec.Builder methodSpecBuilder, return; } - val writerCompositor = new WriterCompositor(context, - EMPTY_TYPE_VAR_MAP, - String.format("%s[$L].%s", ENCODERS_ARG, WRITER_ACCESSOR), + val writerCompositor = WriterCompositor.disallowOpenGeneric(context, SCALE_WRITER_REGISTRY); val encoderCompositor = new EncoderCompositor( context, diff --git a/rpc/rpc-codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcInterfaceProcessorTest.java b/rpc/rpc-codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcInterfaceProcessorTest.java index 52464ebe..f5436c74 100644 --- a/rpc/rpc-codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcInterfaceProcessorTest.java +++ b/rpc/rpc-codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/sections/RpcInterfaceProcessorTest.java @@ -20,7 +20,7 @@ void failsWhenAnnotationHasEmptySection() { assertThat(compilation).failed(); assertThat(compilation) - .hadErrorContaining("null or empty `section`"); + .hadErrorContaining("null or empty `value`"); } @Test diff --git a/rpc/rpc-codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/substitutes/TestSection.java b/rpc/rpc-codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/substitutes/TestSection.java index 98b759a0..e6d60939 100644 --- a/rpc/rpc-codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/substitutes/TestSection.java +++ b/rpc/rpc-codegen/src/test/java/com/strategyobject/substrateclient/rpc/codegen/substitutes/TestSection.java @@ -5,8 +5,8 @@ import java.util.concurrent.CompletableFuture; -@RpcInterface(section = "empty") +@RpcInterface("empty") public interface TestSection { - @RpcCall(method = "doNothing") + @RpcCall("doNothing") CompletableFuture doNothing(String a); } diff --git a/rpc/rpc-codegen/src/test/resources/ClassSection.java b/rpc/rpc-codegen/src/test/resources/ClassSection.java index 08c9cf27..0ea3d33b 100644 --- a/rpc/rpc-codegen/src/test/resources/ClassSection.java +++ b/rpc/rpc-codegen/src/test/resources/ClassSection.java @@ -5,8 +5,8 @@ import java.util.concurrent.CompletableFuture; -@RpcInterface(section = "empty") +@RpcInterface("empty") public class ClassSection { - @RpcCall(method = "doNothing") + @RpcCall("doNothing") CompletableFuture doNothing(); } diff --git a/rpc/rpc-codegen/src/test/resources/NamelessSection.java b/rpc/rpc-codegen/src/test/resources/NamelessSection.java index 2518e764..c0811f03 100644 --- a/rpc/rpc-codegen/src/test/resources/NamelessSection.java +++ b/rpc/rpc-codegen/src/test/resources/NamelessSection.java @@ -5,8 +5,8 @@ import java.util.concurrent.CompletableFuture; -@RpcInterface(section = "") +@RpcInterface("") public interface NamelessSection { - @RpcCall(method = "doNothing") + @RpcCall("doNothing") CompletableFuture doNothing(); } diff --git a/rpc/rpc-codegen/src/test/resources/SectionWithAmbiguousAnnotatedMethod.java b/rpc/rpc-codegen/src/test/resources/SectionWithAmbiguousAnnotatedMethod.java index 915fd8cc..aa212251 100644 --- a/rpc/rpc-codegen/src/test/resources/SectionWithAmbiguousAnnotatedMethod.java +++ b/rpc/rpc-codegen/src/test/resources/SectionWithAmbiguousAnnotatedMethod.java @@ -6,9 +6,9 @@ import java.util.concurrent.CompletableFuture; -@RpcInterface(section = "empty") +@RpcInterface("empty") public interface SectionWithAmbiguousAnnotatedMethod { - @RpcCall(method = "a") + @RpcCall("a") @RpcSubscription(type = "b", subscribeMethod = "subscribeB", unsubscribeMethod = "unsubscribeB") CompletableFuture doNothing(); } diff --git a/rpc/rpc-codegen/src/test/resources/SectionWithIncorrectReturnOfMethod.java b/rpc/rpc-codegen/src/test/resources/SectionWithIncorrectReturnOfMethod.java index 56680837..d9bba444 100644 --- a/rpc/rpc-codegen/src/test/resources/SectionWithIncorrectReturnOfMethod.java +++ b/rpc/rpc-codegen/src/test/resources/SectionWithIncorrectReturnOfMethod.java @@ -3,8 +3,8 @@ import com.strategyobject.substrateclient.rpc.core.annotations.RpcCall; import com.strategyobject.substrateclient.rpc.core.annotations.RpcInterface; -@RpcInterface(section = "empty") +@RpcInterface("empty") public interface SectionWithIncorrectReturnOfMethod { - @RpcCall(method = "doNothing") + @RpcCall("doNothing") boolean doNothing(boolean a); } diff --git a/rpc/rpc-codegen/src/test/resources/SectionWithIncorrectReturnOfSubscription.java b/rpc/rpc-codegen/src/test/resources/SectionWithIncorrectReturnOfSubscription.java index 5a9ed374..4ce64c41 100644 --- a/rpc/rpc-codegen/src/test/resources/SectionWithIncorrectReturnOfSubscription.java +++ b/rpc/rpc-codegen/src/test/resources/SectionWithIncorrectReturnOfSubscription.java @@ -7,7 +7,7 @@ import java.util.function.BiConsumer; import java.util.function.Supplier; -@RpcInterface(section = "empty") +@RpcInterface("empty") public interface SectionWithIncorrectReturnOfSubscription { @RpcSubscription(type = "nothing", subscribeMethod = "doNothing", unsubscribeMethod = "undoNothing") CompletableFuture> doNothing(boolean a, BiConsumer callback); diff --git a/rpc/rpc-codegen/src/test/resources/SectionWithManyCallbacks.java b/rpc/rpc-codegen/src/test/resources/SectionWithManyCallbacks.java index 70c4d290..e211b10c 100644 --- a/rpc/rpc-codegen/src/test/resources/SectionWithManyCallbacks.java +++ b/rpc/rpc-codegen/src/test/resources/SectionWithManyCallbacks.java @@ -7,7 +7,7 @@ import java.util.function.BiConsumer; import java.util.function.Supplier; -@RpcInterface(section = "empty") +@RpcInterface("empty") public interface SectionWithManyCallbacks { @RpcSubscription(type = "nothing", subscribeMethod = "doNothing", unsubscribeMethod = "undoNothing") CompletableFuture>> doNothing( diff --git a/rpc/rpc-codegen/src/test/resources/SectionWithoutAnnotatedMethod.java b/rpc/rpc-codegen/src/test/resources/SectionWithoutAnnotatedMethod.java index 58e4d926..ded782d2 100644 --- a/rpc/rpc-codegen/src/test/resources/SectionWithoutAnnotatedMethod.java +++ b/rpc/rpc-codegen/src/test/resources/SectionWithoutAnnotatedMethod.java @@ -4,7 +4,7 @@ import java.util.concurrent.CompletableFuture; -@RpcInterface(section = "empty") +@RpcInterface("empty") public interface SectionWithoutAnnotatedMethod { CompletableFuture doNothing(); } diff --git a/rpc/rpc-codegen/src/test/resources/SectionWithoutCallback.java b/rpc/rpc-codegen/src/test/resources/SectionWithoutCallback.java index 1f58d1d9..4023df24 100644 --- a/rpc/rpc-codegen/src/test/resources/SectionWithoutCallback.java +++ b/rpc/rpc-codegen/src/test/resources/SectionWithoutCallback.java @@ -6,7 +6,7 @@ import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; -@RpcInterface(section = "empty") +@RpcInterface("empty") public interface SectionWithoutCallback { @RpcSubscription(type = "nothing", subscribeMethod = "doNothing", unsubscribeMethod = "undoNothing") CompletableFuture>> doNothing(boolean a); diff --git a/rpc/rpc-codegen/src/test/resources/TestSection.java b/rpc/rpc-codegen/src/test/resources/TestSection.java index e0a10e2c..b4619cbf 100644 --- a/rpc/rpc-codegen/src/test/resources/TestSection.java +++ b/rpc/rpc-codegen/src/test/resources/TestSection.java @@ -8,9 +8,9 @@ import java.util.function.BiConsumer; import java.util.function.Supplier; -@RpcInterface(section = "empty") +@RpcInterface("empty") public interface TestSection { - @RpcCall(method = "doNothing") + @RpcCall("doNothing") CompletableFuture doNothing(boolean a); @RpcSubscription(type = "nothing", subscribeMethod = "subscribeNothing", unsubscribeMethod = "unsubscribeNothing") diff --git a/rpc/rpc-core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/RpcCall.java b/rpc/rpc-core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/RpcCall.java index 70344cdf..9c0cd426 100644 --- a/rpc/rpc-core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/RpcCall.java +++ b/rpc/rpc-core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/RpcCall.java @@ -8,5 +8,5 @@ @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface RpcCall { - String method(); + String value(); } diff --git a/rpc/rpc-core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/RpcInterface.java b/rpc/rpc-core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/RpcInterface.java index 76399cca..4797eeb9 100644 --- a/rpc/rpc-core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/RpcInterface.java +++ b/rpc/rpc-core/src/main/java/com/strategyobject/substrateclient/rpc/core/annotations/RpcInterface.java @@ -11,5 +11,5 @@ @Retention(RetentionPolicy.RUNTIME) public @interface RpcInterface { @NonNull - String section(); + String value(); } diff --git a/rpc/rpc-sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/Author.java b/rpc/rpc-sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/Author.java index 017203fe..ce4bb979 100644 --- a/rpc/rpc-sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/Author.java +++ b/rpc/rpc-sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/Author.java @@ -13,15 +13,15 @@ import java.util.function.BiConsumer; import java.util.function.Supplier; -@RpcInterface(section = "author") +@RpcInterface("author") public interface Author { - @RpcCall(method = "hasKey") + @RpcCall("hasKey") CompletableFuture hasKey(@Scale PublicKey publicKey, String keyType); - @RpcCall(method = "insertKey") + @RpcCall("insertKey") CompletableFuture insertKey(String keyType, String secretUri, @Scale PublicKey publicKey); - @RpcCall(method = "submitExtrinsic") + @RpcCall("submitExtrinsic") @Scale CompletableFuture submitExtrinsic(@Scale Extrinsic extrinsic); diff --git a/rpc/rpc-sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/Chain.java b/rpc/rpc-sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/Chain.java index e4056317..550351a0 100644 --- a/rpc/rpc-sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/Chain.java +++ b/rpc/rpc-sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/Chain.java @@ -12,19 +12,19 @@ import java.util.function.BiConsumer; import java.util.function.Supplier; -@RpcInterface(section = "chain") +@RpcInterface("chain") public interface Chain { - @RpcCall(method = "getFinalizedHead") + @RpcCall("getFinalizedHead") @Scale CompletableFuture getFinalizedHead(); @RpcSubscription(type = "newHead", subscribeMethod = "subscribeNewHead", unsubscribeMethod = "unsubscribeNewHead") CompletableFuture>> subscribeNewHeads(BiConsumer callback); - @RpcCall(method = "getBlockHash") + @RpcCall("getBlockHash") @Scale CompletableFuture getBlockHash(long number); - @RpcCall(method = "getBlock") + @RpcCall("getBlock") CompletableFuture getBlock(@Scale BlockHash hash); } diff --git a/rpc/rpc-sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/State.java b/rpc/rpc-sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/State.java index bccddaa6..7f54a408 100644 --- a/rpc/rpc-sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/State.java +++ b/rpc/rpc-sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/State.java @@ -11,65 +11,65 @@ import java.util.function.BiConsumer; import java.util.function.Supplier; -@RpcInterface(section = "state") +@RpcInterface("state") public interface State { - @RpcCall(method = "getRuntimeVersion") + @RpcCall("getRuntimeVersion") CompletableFuture getRuntimeVersion(); - @RpcCall(method = "getMetadata") + @RpcCall("getMetadata") @Scale CompletableFuture getMetadata(); - @RpcCall(method = "getKeys") + @RpcCall("getKeys") CompletableFuture> getKeys(StorageKey key); - @RpcCall(method = "getKeys") + @RpcCall("getKeys") CompletableFuture> getKeys(StorageKey key, @Scale BlockHash at); - @RpcCall(method = "getKeysPaged") + @RpcCall("getKeysPaged") CompletableFuture> getKeysPaged(StorageKey key, int count); - @RpcCall(method = "getKeysPaged") + @RpcCall("getKeysPaged") CompletableFuture> getKeysPaged(StorageKey key, int count, StorageKey startKey); - @RpcCall(method = "getKeysPaged") + @RpcCall("getKeysPaged") CompletableFuture> getKeysPaged(StorageKey key, int count, StorageKey startKey, @Scale BlockHash at); - @RpcCall(method = "getStorage") + @RpcCall("getStorage") CompletableFuture getStorage(StorageKey key); - @RpcCall(method = "getStorage") + @RpcCall("getStorage") CompletableFuture getStorage(StorageKey key, @Scale BlockHash at); - @RpcCall(method = "getStorageHash") + @RpcCall("getStorageHash") @Scale CompletableFuture getStorageHash(StorageKey key); - @RpcCall(method = "getStorageHash") + @RpcCall("getStorageHash") @Scale CompletableFuture getStorageHash(StorageKey key, @Scale BlockHash at); - @RpcCall(method = "getStorageSize") + @RpcCall("getStorageSize") CompletableFuture getStorageSize(StorageKey key); - @RpcCall(method = "getStorageSize") + @RpcCall("getStorageSize") CompletableFuture getStorageSize(StorageKey key, @Scale BlockHash at); - @RpcCall(method = "queryStorage") + @RpcCall("queryStorage") CompletableFuture> queryStorage(List keys, @Scale BlockHash fromBlock); - @RpcCall(method = "queryStorage") + @RpcCall("queryStorage") CompletableFuture> queryStorage(List keys, @Scale BlockHash fromBlock, @Scale BlockHash toBlock); - @RpcCall(method = "queryStorageAt") + @RpcCall("queryStorageAt") CompletableFuture> queryStorageAt(List keys); - @RpcCall(method = "queryStorageAt") + @RpcCall("queryStorageAt") CompletableFuture> queryStorageAt(List keys, @Scale BlockHash at); @RpcSubscription(type = "storage", subscribeMethod = "subscribeStorage", unsubscribeMethod = "unsubscribeStorage") diff --git a/rpc/rpc-sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/System.java b/rpc/rpc-sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/System.java index f39c7866..e5103ea6 100644 --- a/rpc/rpc-sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/System.java +++ b/rpc/rpc-sections/src/main/java/com/strategyobject/substrateclient/rpc/sections/System.java @@ -6,8 +6,8 @@ import java.util.concurrent.CompletableFuture; -@RpcInterface(section = "system") +@RpcInterface("system") public interface System { - @RpcCall(method = "accountNextIndex") + @RpcCall("accountNextIndex") CompletableFuture accountNextIndex(AccountId accountId); } diff --git a/rpc/src/main/java/com/strategyobject/substrateclient/rpc/RpcImpl.java b/rpc/src/main/java/com/strategyobject/substrateclient/rpc/RpcImpl.java index 3ce902d9..6adb2d0f 100644 --- a/rpc/src/main/java/com/strategyobject/substrateclient/rpc/RpcImpl.java +++ b/rpc/src/main/java/com/strategyobject/substrateclient/rpc/RpcImpl.java @@ -7,46 +7,53 @@ import com.strategyobject.substrateclient.rpc.sections.State; import com.strategyobject.substrateclient.rpc.sections.System; import com.strategyobject.substrateclient.transport.ProviderInterface; -import lombok.RequiredArgsConstructor; +import lombok.NonNull; -@RequiredArgsConstructor public class RpcImpl implements Rpc, AutoCloseable { private final ProviderInterface providerInterface; + private final Author author; + private final Chain chain; + private final State state; + private final System system; - @Override - public Author author() { + private RpcImpl(ProviderInterface providerInterface) { + this.providerInterface = providerInterface; + author = resolveSection(Author.class); + chain = resolveSection(Chain.class); + state = resolveSection(State.class); + system = resolveSection(System.class); + } + + private T resolveSection(Class clazz) { try { - return RpcGeneratedSectionFactory.create(Author.class, providerInterface); + return RpcGeneratedSectionFactory.create(clazz, providerInterface); } catch (RpcInterfaceInitializationException e) { throw new RuntimeException(e); } } + public static RpcImpl with(@NonNull ProviderInterface providerInterface) { + return new RpcImpl(providerInterface); + } + + @Override + public Author author() { + return author; + } + @Override public Chain chain() { - try { - return RpcGeneratedSectionFactory.create(Chain.class, providerInterface); - } catch (RpcInterfaceInitializationException e) { - throw new RuntimeException(e); - } + return chain; } @Override public State state() { - try { - return RpcGeneratedSectionFactory.create(State.class, providerInterface); - } catch (RpcInterfaceInitializationException e) { - throw new RuntimeException(e); - } + return state; } @Override public System system() { - try { - return RpcGeneratedSectionFactory.create(System.class, providerInterface); - } catch (RpcInterfaceInitializationException e) { - throw new RuntimeException(e); - } + return system; } @Override 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 6ee8fe64..2dfd0dee 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 @@ -45,6 +45,23 @@ public TypeTraverser.TypeTreeNode parse(@NonNull AnnotatedConstruct annotated) { return null; } + public TypeTraverser.TypeTreeNode parse(AnnotationMirror annotation) { + if (context.isSameType(annotation.getAnnotationType(), context.getType(Scale.class))) { + val scaleType = AnnotationUtils.getValueFromAnnotation(annotation, "value"); + + return new TypeTraverser.TypeTreeNode(scaleType); + } + + if (context.isSameType(annotation.getAnnotationType(), context.getType(ScaleGeneric.class))) { + val template = AnnotationUtils.getValueFromAnnotation(annotation, "template"); + val typesMap = getTypesMap(annotation); + + return parseTemplate(template, typesMap); + } + + return null; + } + private TypeTraverser.TypeTreeNode parseTemplate(String template, Map typesMap) { val indexes = StringUtils.allIndexesOfAny(template, "<,>"); if (indexes.size() == 0 || indexes.get(0) == 0) { @@ -92,7 +109,7 @@ private TypeTraverser.TypeTreeNode parseTemplate(String template, Map typesMap, String name) { val type = typesMap.get(name); - return type == null || context.isSubtypeOf(type, context.getType(SCALE_ANNOTATIONS_DEFAULT)) ? null : type; + return type == null || context.isAssignable(type, context.getType(SCALE_ANNOTATIONS_DEFAULT)) ? null : type; } private Map getTypesMap(AnnotationMirror scaleGeneric) { diff --git a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/ReaderCompositor.java b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/ReaderCompositor.java index 8a795d5a..ef55bb37 100644 --- a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/ReaderCompositor.java +++ b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/ReaderCompositor.java @@ -1,5 +1,6 @@ package com.strategyobject.substrateclient.scale.codegen.reader; +import com.google.common.base.Strings; import com.squareup.javapoet.CodeBlock; import com.strategyobject.substrateclient.common.codegen.ProcessorContext; import com.strategyobject.substrateclient.common.codegen.TypeTraverser; @@ -18,19 +19,36 @@ public class ReaderCompositor extends TypeTraverser { private final String readerAccessor; private final String registryVarName; - public ReaderCompositor(@NonNull ProcessorContext context, - @NonNull Map typeVarMap, - @NonNull String readerAccessor, - @NonNull String registryVarName) { + private ReaderCompositor(ProcessorContext context, + Map typeVarMap, + String readerAccessor, + String registryVarName) { super(CodeBlock.class); + this.context = context; this.typeVarMap = typeVarMap; this.readerAccessor = readerAccessor; this.registryVarName = registryVarName; } + public static ReaderCompositor forAnyType(@NonNull ProcessorContext context, + @NonNull Map typeVarMap, + @NonNull String readerAccessor, + @NonNull String registryVarName) { + return new ReaderCompositor(context, typeVarMap, readerAccessor, registryVarName); + } + + public static ReaderCompositor disallowOpenGeneric(@NonNull ProcessorContext context, + @NonNull String registryVarName) { + return new ReaderCompositor(context, null, null, registryVarName); + } + @Override protected CodeBlock whenTypeVar(@NonNull TypeVariable type, TypeMirror _override) { + if (Strings.isNullOrEmpty(readerAccessor)) { + throw new IllegalStateException("The compositor doesn't support open generics."); + } + return CodeBlock.builder() .add(readerAccessor, typeVarMap.get(type.toString())) .build(); 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 de1d5664..40ff4b4a 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 @@ -7,8 +7,8 @@ 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.ScaleProcessorHelper; import com.strategyobject.substrateclient.scale.codegen.ScaleAnnotationParser; +import com.strategyobject.substrateclient.scale.codegen.ScaleProcessorHelper; import com.strategyobject.substrateclient.scale.registries.ScaleReaderRegistry; import lombok.NonNull; import lombok.val; @@ -109,7 +109,10 @@ private void addMethodBody(MethodSpec.Builder methodSpec, .beginControlFlow("try"); val scaleAnnotationParser = new ScaleAnnotationParser(context); - val compositor = new ReaderCompositor(context, typeVarMap, String.format("%s[$L]", READERS_ARG), REGISTRY); + val compositor = ReaderCompositor.forAnyType(context, + typeVarMap, + String.format("%s[$L]", READERS_ARG), + REGISTRY); for (Element element : classElement.getEnclosedElements()) { if (element instanceof VariableElement) { val field = (VariableElement) element; 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 57d251a0..bf27c1c5 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 @@ -90,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.isSubtypeOf(classElement.asType(), context.erasure(context.getType(SCALE_SELF_WRITABLE)))) { + if (classTypeParametersSize == 0 || context.isAssignable(classElement.asType(), context.erasure(context.getType(SCALE_SELF_WRITABLE)))) { methodSpec.addStatement("if (writers != null && writers.length > 0) throw new IllegalArgumentException()"); } else { methodSpec @@ -109,7 +109,10 @@ private void addMethodBody(MethodSpec.Builder methodSpec, .beginControlFlow("try"); val scaleAnnotationParser = new ScaleAnnotationParser(context); - val compositor = new WriterCompositor(context, typeVarMap, String.format("%s[$L]", WRITERS_ARG), REGISTRY); + val compositor = WriterCompositor.forAnyType(context, + typeVarMap, + String.format("%s[$L]", WRITERS_ARG), + REGISTRY); for (Element element : classElement.getEnclosedElements()) { if (element instanceof VariableElement) { val field = (VariableElement) element; 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 31f0f851..cdd2e061 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 @@ -71,12 +71,12 @@ public boolean process(Set annotations, RoundEnvironment private boolean validateScaleSelfWritable(TypeElement typeElement) { val selfWritable = context.erasure(context.getType(SCALE_SELF_WRITABLE)); - if (!context.isSubtypeOf(typeElement.asType(), selfWritable)) { + if (!context.isAssignable(typeElement.asType(), selfWritable)) { return true; } val typeParameters = typeElement.getTypeParameters(); return typeParameters.size() == 0 || - typeParameters.stream().allMatch(x -> context.isSubtypeOf(x.asType(), selfWritable)); + typeParameters.stream().allMatch(x -> context.isAssignable(x.asType(), selfWritable)); } } diff --git a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/WriterCompositor.java b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/WriterCompositor.java index de56c0b2..316486ce 100644 --- a/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/WriterCompositor.java +++ b/scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/WriterCompositor.java @@ -21,11 +21,12 @@ public class WriterCompositor extends TypeTraverser { private final String writerAccessor; private final String registryVarName; - public WriterCompositor(ProcessorContext context, - Map typeVarMap, - String writerAccessor, - String registryVarName) { + private 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)); @@ -33,9 +34,21 @@ public WriterCompositor(ProcessorContext context, this.registryVarName = registryVarName; } + public static WriterCompositor forAnyType(@NonNull ProcessorContext context, + @NonNull Map typeVarMap, + @NonNull String writerAccessor, + @NonNull String registryVarName) { + return new WriterCompositor(context, typeVarMap, writerAccessor, registryVarName); + } + + public static WriterCompositor disallowOpenGeneric(@NonNull ProcessorContext context, + @NonNull String registryVarName) { + return new WriterCompositor(context, null, null, registryVarName); + } + @Override protected CodeBlock whenTypeVar(@NonNull TypeVariable type, TypeMirror _override) { - return context.isSubtypeOf(type, selfWritable) ? + return context.isAssignable(type, selfWritable) ? CodeBlock.builder() .add("$L.resolve($T.class)", registryVarName, selfWritable) .build() : @@ -60,7 +73,7 @@ private CodeBlock getNonGenericCodeBlock(TypeMirror type, TypeMirror override) { registryVarName, override != null ? override : - context.isSubtypeOf(type, selfWritable) ? + context.isAssignable(type, selfWritable) ? selfWritable : type) .build(); @@ -80,7 +93,7 @@ protected CodeBlock whenGenericType(@NonNull DeclaredType type, TypeMirror overr } else { resolveType = context.erasure(type); - if (context.isSubtypeOf(resolveType, selfWritable)) { + if (context.isAssignable(resolveType, selfWritable)) { return CodeBlock.builder().add("$L.resolve($T.class)", registryVarName, selfWritable).build(); } } @@ -96,6 +109,6 @@ protected CodeBlock whenGenericType(@NonNull DeclaredType type, TypeMirror overr @Override protected boolean doTraverseArguments(@NonNull DeclaredType type, TypeMirror override) { - return override != null || !context.isSubtypeOf(context.erasure(type), selfWritable); + return override != null || !context.isAssignable(context.erasure(type), selfWritable); } } diff --git a/settings.gradle b/settings.gradle index ee9ac553..64e2f05a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,4 +15,6 @@ include 'tests' include 'transport' include 'types' include 'storage' +include 'pallet' +include 'pallet:pallet-codegen' diff --git a/storage/src/test/java/com/strategyobject/substrateclient/storage/StorageDoubleMapImplTests.java b/storage/src/test/java/com/strategyobject/substrateclient/storage/StorageDoubleMapImplTests.java index 8508ea2d..405d5279 100644 --- a/storage/src/test/java/com/strategyobject/substrateclient/storage/StorageDoubleMapImplTests.java +++ b/storage/src/test/java/com/strategyobject/substrateclient/storage/StorageDoubleMapImplTests.java @@ -33,7 +33,7 @@ public void societyVotes() throws Exception { .disableAutoConnect() .build(); wsProvider.connect().get(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS); - try (val rpc = new RpcImpl(wsProvider)) { + try (val rpc = RpcImpl.with(wsProvider)) { val storage = StorageDoubleMapImpl.with( rpc, (ScaleReader) ScaleReaderRegistry.getInstance().resolve(Void.class), diff --git a/storage/src/test/java/com/strategyobject/substrateclient/storage/StorageMapImplTests.java b/storage/src/test/java/com/strategyobject/substrateclient/storage/StorageMapImplTests.java index b099c2a1..acd6c364 100644 --- a/storage/src/test/java/com/strategyobject/substrateclient/storage/StorageMapImplTests.java +++ b/storage/src/test/java/com/strategyobject/substrateclient/storage/StorageMapImplTests.java @@ -33,7 +33,7 @@ public void systemBlockHash() throws Exception { .disableAutoConnect() .build(); wsProvider.connect().get(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS); - try (val rpc = new RpcImpl(wsProvider)) { + try (val rpc = RpcImpl.with(wsProvider)) { val storage = StorageMapImpl.with( rpc, (ScaleReader) ScaleReaderRegistry.getInstance().resolve(BlockHash.class), diff --git a/storage/src/test/java/com/strategyobject/substrateclient/storage/StorageNMapImplTests.java b/storage/src/test/java/com/strategyobject/substrateclient/storage/StorageNMapImplTests.java index 8666f4bf..0d65c519 100644 --- a/storage/src/test/java/com/strategyobject/substrateclient/storage/StorageNMapImplTests.java +++ b/storage/src/test/java/com/strategyobject/substrateclient/storage/StorageNMapImplTests.java @@ -63,7 +63,7 @@ private WsProvider getConnectedProvider() throws InterruptedException, Execution @Test public void keys() throws Exception { val wsProvider = getConnectedProvider(); - try (val rpc = new RpcImpl(wsProvider)) { + try (val rpc = RpcImpl.with(wsProvider)) { val storage = newSystemBlockHashStorage(rpc); val collection = storage.keys().get(); @@ -90,7 +90,7 @@ public void keys() throws Exception { @Test public void multiToDifferentStorages() throws Exception { val wsProvider = getConnectedProvider(); - try (val rpc = new RpcImpl(wsProvider)) { + try (val rpc = RpcImpl.with(wsProvider)) { val storageValue = StorageNMapImpl.with( rpc, ScaleReaderRegistry.getInstance().resolve(AccountId.class), @@ -120,7 +120,7 @@ public void multiToDifferentStorages() throws Exception { @Test public void entries() throws Exception { val wsProvider = getConnectedProvider(); - try (val rpc = new RpcImpl(wsProvider)) { + try (val rpc = RpcImpl.with(wsProvider)) { val storage = newSystemBlockHashStorage(rpc); val collection = storage.entries().get(); @@ -142,7 +142,7 @@ public void entries() throws Exception { @Test public void multi() throws Exception { val wsProvider = getConnectedProvider(); - try (val rpc = new RpcImpl(wsProvider)) { + try (val rpc = RpcImpl.with(wsProvider)) { val storage = newSystemBlockHashStorage(rpc); val collection = storage.multi(Arg.of(0), Arg.of(1)).get(); @@ -164,7 +164,7 @@ public void multi() throws Exception { @Test public void keysPaged() throws Exception { val wsProvider = getConnectedProvider(); - try (val rpc = new RpcImpl(wsProvider)) { + try (val rpc = RpcImpl.with(wsProvider)) { waitForNewBlocks(rpc); val storage = newSystemBlockHashStorage(rpc); @@ -190,7 +190,7 @@ public void keysPaged() throws Exception { @Test public void entriesPaged() throws Exception { val wsProvider = getConnectedProvider(); - try (val rpc = new RpcImpl(wsProvider)) { + try (val rpc = RpcImpl.with(wsProvider)) { waitForNewBlocks(rpc); val storage = newSystemBlockHashStorage(rpc); @@ -223,7 +223,7 @@ public void entriesPaged() throws Exception { @Test public void subscribe() throws Exception { val wsProvider = getConnectedProvider(); - try (val rpc = new RpcImpl(wsProvider)) { + try (val rpc = RpcImpl.with(wsProvider)) { val blockNumber = 2; val storage = newSystemBlockHashStorage(rpc); val blockHash = new AtomicReference(); diff --git a/storage/src/test/java/com/strategyobject/substrateclient/storage/StorageValueImplTests.java b/storage/src/test/java/com/strategyobject/substrateclient/storage/StorageValueImplTests.java index 9db86862..692f9669 100644 --- a/storage/src/test/java/com/strategyobject/substrateclient/storage/StorageValueImplTests.java +++ b/storage/src/test/java/com/strategyobject/substrateclient/storage/StorageValueImplTests.java @@ -34,7 +34,7 @@ public void sudoKey() throws Exception { .disableAutoConnect() .build(); wsProvider.connect().get(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS); - try (val rpc = new RpcImpl(wsProvider)) { + try (val rpc = RpcImpl.with(wsProvider)) { val storage = StorageValueImpl.with( rpc, ScaleReaderRegistry.getInstance().resolve(AccountId.class), @@ -58,7 +58,7 @@ public void sudoKeyAtGenesis() throws Exception { .disableAutoConnect() .build(); wsProvider.connect().get(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS); - try (val rpc = new RpcImpl(wsProvider)) { + try (val rpc = RpcImpl.with(wsProvider)) { val blockHash = rpc.chain().getBlockHash(0).get(); val storage = StorageValueImpl.with( rpc, diff --git a/types/src/main/java/com/strategyobject/substrateclient/types/Size.java b/types/src/main/java/com/strategyobject/substrateclient/types/Size.java index 0f64b3c7..11a1c49b 100644 --- a/types/src/main/java/com/strategyobject/substrateclient/types/Size.java +++ b/types/src/main/java/com/strategyobject/substrateclient/types/Size.java @@ -3,13 +3,20 @@ public interface Size { int getValue(); + Zero zero = new Zero(); Of32 of32 = new Of32(); Of64 of64 = new Of64(); Of96 of96 = new Of96(); Of128 of128 = new Of128(); - class Of32 implements Size { + class Zero implements Size { + @Override + public int getValue() { + return 0; + } + } + class Of32 implements Size { @Override public int getValue() { return 32;