Skip to content

Commit

Permalink
[WIP] Annotations for pallets and their storages
Browse files Browse the repository at this point in the history
  • Loading branch information
vnabiev committed Apr 15, 2022
1 parent 511a756 commit eb7bb80
Show file tree
Hide file tree
Showing 41 changed files with 1,108 additions and 54 deletions.
15 changes: 15 additions & 0 deletions api/build.gradle
Original file line number Diff line number Diff line change
@@ -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')
}
12 changes: 12 additions & 0 deletions api/src/main/java/com/strategyobject/substrateclient/api/Api.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
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;

public interface Api {
static DefaultApi with(ProviderInterface provider) {
val rpc = new RpcImpl(provider);

return DefaultApi.with(rpc, GeneratedPalletResolver.with(rpc));
}

Rpc getRpc();

<T> T pallet(Class<T> clazz);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.strategyobject.substrateclient.api;

import com.strategyobject.substrateclient.pallet.PalletResolver;
import com.strategyobject.substrateclient.rpc.Rpc;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor(staticName = "with")
public class DefaultApi implements Api, AutoCloseable {
private final @NonNull Rpc rpc;
private final @NonNull PalletResolver palletResolver;

@Override
public Rpc getRpc() {
return rpc;
}

@Override
public <T> T pallet(@NonNull Class<T> clazz) {
return palletResolver.resolve(clazz);
}

@Override
public void close() throws Exception {
if (rpc instanceof AutoCloseable) {
((AutoCloseable) rpc).close();
}
}
}
Original file line number Diff line number Diff line change
@@ -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()));
}
}
}
Original file line number Diff line number Diff line change
@@ -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(name = "System")
public interface SystemPallet {
@Storage(
name = "BlockHash",
keys = {
@StorageKey(
type = @Scale(Integer.class),
hasher = StorageHasher.TwoX64Concat
)
})
StorageNMap<BlockHash> blockHash();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<? extends TypeMirror> getTypeArgumentsOrDefault(DeclaredType declaredType, TypeMirror override) {
return (doTraverseArguments(declaredType, override) ?
declaredType.getTypeArguments() :
Expand All @@ -125,6 +148,7 @@ private boolean typeIsOverriddenByNonGeneric(int typeOverrideSize) {


public static class TypeTreeNode {
@Getter
private final TypeMirror type;
private final List<TypeTreeNode> children;

Expand Down
4 changes: 4 additions & 0 deletions pallet/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dependencies {
implementation project(':scale')
implementation project(':rpc')
}
16 changes: 16 additions & 0 deletions pallet/pallet-codegen/build.gradle
Original file line number Diff line number Diff line change
@@ -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'
}
Original file line number Diff line number Diff line change
@@ -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<PalletMethodProcessor> processors;

public CompoundMethodProcessor(TypeElement typeElement, List<PalletMethodProcessor> 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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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_FACTORY_METHOD = "sizeOf";
}
Original file line number Diff line number Diff line change
@@ -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.name())) {
throw new ProcessingException(
interfaceElement,
"`@%s` of `%s` contains null or empty `name`.",
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);
}
}
Loading

0 comments on commit eb7bb80

Please sign in to comment.