Skip to content

Commit ba0e699

Browse files
committed
add java enums support
1 parent 01002ce commit ba0e699

File tree

16 files changed

+255
-110
lines changed

16 files changed

+255
-110
lines changed

common/src/test/java/com/strategyobject/substrateclient/common/convert/HexConverterTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ Stream<DynamicTest> toBytesThrows() {
5757
}
5858

5959
@AllArgsConstructor(access = AccessLevel.PRIVATE)
60-
static class Test extends TestSuite.TestCase {
60+
static class Test implements TestSuite.TestCase {
6161
private final String displayName;
6262
private final Executable executable;
6363

rpc/rpc-api/src/main/java/com/strategyobject/substrateclient/rpc/api/AddressKind.java

+2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.strategyobject.substrateclient.rpc.api;
22

3+
import com.strategyobject.substrateclient.scale.annotation.ScaleWriter;
34
import lombok.Getter;
45

6+
@ScaleWriter
57
public enum AddressKind {
68
ID((byte) 0);
79

rpc/rpc-api/src/main/java/com/strategyobject/substrateclient/rpc/api/AddressKindWriter.java

-25
This file was deleted.

rpc/src/test/java/com/strategyobject/substrateclient/rpc/decoders/KnownDecoderTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ Stream<DynamicTest> decodeRpcNull() {
7676
}
7777

7878
@AllArgsConstructor(access = AccessLevel.PRIVATE)
79-
static class Test<T> extends TestSuite.TestCase {
79+
static class Test<T> implements TestSuite.TestCase {
8080
private final RpcDecoder<T> decoder;
8181
private final RpcObject given;
8282
private final T expected;

rpc/src/test/java/com/strategyobject/substrateclient/rpc/decoders/KnownEncoderTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ Stream<DynamicTest> encode() {
6868
}
6969

7070
@AllArgsConstructor(access = AccessLevel.PRIVATE)
71-
static class Test<T> extends TestSuite.TestCase {
71+
static class Test<T> implements TestSuite.TestCase {
7272
private static final Gson GSON = new Gson();
7373

7474
private final RpcEncoder<T> encoder;

scale/scale-codegen/build.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@ dependencies {
66
annotationProcessor 'com.google.auto.service:auto-service:1.0.1'
77

88
implementation 'com.squareup:javapoet:1.13.0'
9+
10+
testImplementation project(':tests')
911
testImplementation 'com.google.testing.compile:compile-testing:0.19'
1012
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.strategyobject.substrateclient.scale.codegen.reader;
2+
3+
import com.squareup.javapoet.*;
4+
import com.strategyobject.substrateclient.common.codegen.ProcessingException;
5+
import com.strategyobject.substrateclient.common.codegen.ProcessorContext;
6+
import com.strategyobject.substrateclient.common.io.Streamer;
7+
import com.strategyobject.substrateclient.scale.ScaleReader;
8+
import com.strategyobject.substrateclient.scale.annotation.AutoRegister;
9+
import com.strategyobject.substrateclient.scale.codegen.ScaleProcessorHelper;
10+
import lombok.NonNull;
11+
import lombok.RequiredArgsConstructor;
12+
import lombok.val;
13+
14+
import javax.lang.model.element.Modifier;
15+
import javax.lang.model.element.TypeElement;
16+
import java.io.IOException;
17+
import java.io.InputStream;
18+
19+
@RequiredArgsConstructor
20+
public class ScaleReaderAnnotatedEnum {
21+
private static final String READERS_ARG = "readers";
22+
23+
private final @NonNull TypeElement enumElement;
24+
25+
public void generateReader(@NonNull ProcessorContext context) throws IOException, ProcessingException {
26+
val readerName = ScaleProcessorHelper.getReaderName(enumElement.getSimpleName().toString());
27+
val enumType = TypeName.get(enumElement.asType());
28+
29+
val typeSpecBuilder = TypeSpec.classBuilder(readerName)
30+
.addAnnotation(AnnotationSpec.builder(AutoRegister.class)
31+
.addMember("types", "{$L.class}", enumElement.getQualifiedName().toString())
32+
.build())
33+
.addModifiers(Modifier.PUBLIC)
34+
.addSuperinterface(ParameterizedTypeName.get(ClassName.get(ScaleReader.class), enumType))
35+
.addMethod(generateReadMethod(enumType));
36+
37+
JavaFile.builder(
38+
context.getPackageName(enumElement),
39+
typeSpecBuilder.build()
40+
).build().writeTo(context.getFiler());
41+
}
42+
43+
private MethodSpec generateReadMethod(TypeName enumType) {
44+
val methodSpec = MethodSpec.methodBuilder("read")
45+
.addAnnotation(Override.class)
46+
.addModifiers(Modifier.PUBLIC)
47+
.returns(enumType)
48+
.addParameter(InputStream.class, "stream")
49+
.addParameter(ArrayTypeName.of(
50+
ParameterizedTypeName.get(
51+
ClassName.get(ScaleReader.class),
52+
WildcardTypeName.subtypeOf(Object.class))),
53+
READERS_ARG)
54+
.varargs(true)
55+
.addException(IOException.class);
56+
57+
addValidationRules(methodSpec);
58+
addMethodBody(methodSpec);
59+
return methodSpec.build();
60+
}
61+
62+
private void addValidationRules(MethodSpec.Builder methodSpec) {
63+
methodSpec
64+
.addStatement("if (stream == null) throw new IllegalArgumentException(\"stream is null\")")
65+
.addStatement("if (readers != null && readers.length > 0) throw new IllegalArgumentException()");
66+
}
67+
68+
private void addMethodBody(MethodSpec.Builder methodSpec) {
69+
methodSpec.addStatement("return $T.values()[$T.readByte(stream)]", enumElement, Streamer.class);
70+
}
71+
}

scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/reader/ScaleReaderProcessor.java

+8-3
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,23 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
3636
}
3737

3838
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(ScaleReader.class)) {
39-
if (annotatedElement.getKind() != ElementKind.CLASS) {
39+
val elementKind = annotatedElement.getKind();
40+
if (!elementKind.isClass()) {
4041
context.error(
4142
annotatedElement,
42-
"Only classes can be annotated with `@%s`.",
43+
"Only classes and enums can be annotated with `@%s`.",
4344
ScaleReader.class.getSimpleName());
4445

4546
return true;
4647
}
4748

4849
val typeElement = (TypeElement) annotatedElement;
4950
try {
50-
new ScaleReaderAnnotatedClass(typeElement).generateReader(context);
51+
if (elementKind == ElementKind.CLASS) {
52+
new ScaleReaderAnnotatedClass(typeElement).generateReader(context);
53+
} else {
54+
new ScaleReaderAnnotatedEnum(typeElement).generateReader(context);
55+
}
5156
} catch (ProcessingException e) {
5257
context.error(typeElement, e);
5358
return true;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.strategyobject.substrateclient.scale.codegen.writer;
2+
3+
import com.squareup.javapoet.*;
4+
import com.strategyobject.substrateclient.common.codegen.ProcessingException;
5+
import com.strategyobject.substrateclient.common.codegen.ProcessorContext;
6+
import com.strategyobject.substrateclient.scale.ScaleWriter;
7+
import com.strategyobject.substrateclient.scale.annotation.AutoRegister;
8+
import com.strategyobject.substrateclient.scale.codegen.ScaleProcessorHelper;
9+
import lombok.NonNull;
10+
import lombok.RequiredArgsConstructor;
11+
import lombok.val;
12+
13+
import javax.lang.model.element.Modifier;
14+
import javax.lang.model.element.TypeElement;
15+
import java.io.IOException;
16+
import java.io.OutputStream;
17+
18+
@RequiredArgsConstructor
19+
public class ScaleWriterAnnotatedEnum {
20+
private static final String WRITERS_ARG = "writers";
21+
22+
private final @NonNull TypeElement enumElement;
23+
24+
public void generateWriter(@NonNull ProcessorContext context) throws IOException, ProcessingException {
25+
val writerName = ScaleProcessorHelper.getWriterName(enumElement.getSimpleName().toString());
26+
val enumType = TypeName.get(enumElement.asType());
27+
28+
val typeSpecBuilder = TypeSpec.classBuilder(writerName)
29+
.addAnnotation(AnnotationSpec.builder(AutoRegister.class)
30+
.addMember("types", "{$L.class}", enumElement.getQualifiedName().toString())
31+
.build())
32+
.addModifiers(Modifier.PUBLIC)
33+
.addSuperinterface(ParameterizedTypeName.get(ClassName.get(ScaleWriter.class), enumType))
34+
.addMethod(generateWriteMethod(enumType));
35+
36+
JavaFile.builder(
37+
context.getPackageName(enumElement),
38+
typeSpecBuilder.build()
39+
).build().writeTo(context.getFiler());
40+
}
41+
42+
private MethodSpec generateWriteMethod(TypeName classWildcardTyped) {
43+
val methodSpec = MethodSpec.methodBuilder("write")
44+
.addAnnotation(Override.class)
45+
.addModifiers(Modifier.PUBLIC)
46+
.returns(TypeName.VOID)
47+
.addParameter(classWildcardTyped, "value")
48+
.addParameter(OutputStream.class, "stream")
49+
.addParameter(ArrayTypeName.of(
50+
ParameterizedTypeName.get(
51+
ClassName.get(ScaleWriter.class),
52+
WildcardTypeName.subtypeOf(Object.class))),
53+
WRITERS_ARG)
54+
.varargs(true)
55+
.addException(IOException.class);
56+
57+
addValidationRules(methodSpec);
58+
addMethodBody(methodSpec);
59+
return methodSpec.build();
60+
}
61+
62+
private void addValidationRules(MethodSpec.Builder methodSpec) {
63+
methodSpec.addStatement("if (stream == null) throw new IllegalArgumentException(\"stream is null\")");
64+
methodSpec.addStatement("if (value == null) throw new IllegalArgumentException(\"value is null\")");
65+
methodSpec.addStatement("if (writers != null && writers.length > 0) throw new IllegalArgumentException()");
66+
}
67+
68+
private void addMethodBody(MethodSpec.Builder methodSpec) {
69+
methodSpec.addStatement("stream.write(value.ordinal())");
70+
}
71+
}

scale/scale-codegen/src/main/java/com/strategyobject/substrateclient/scale/codegen/writer/ScaleWriterProcessor.java

+8-3
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,23 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
3636
}
3737

3838
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(ScaleWriter.class)) {
39-
if (annotatedElement.getKind() != ElementKind.CLASS) {
39+
val elementKind = annotatedElement.getKind();
40+
if (!elementKind.isClass()) {
4041
context.error(
4142
annotatedElement,
42-
"Only classes can be annotated with `@%s`.",
43+
"Only classes and enums can be annotated with `@%s`.",
4344
ScaleWriter.class.getSimpleName());
4445

4546
return true;
4647
}
4748

4849
val typeElement = (TypeElement) annotatedElement;
4950
try {
50-
new ScaleWriterAnnotatedClass(typeElement).generateWriter(context);
51+
if (elementKind == ElementKind.CLASS) {
52+
new ScaleWriterAnnotatedClass(typeElement).generateWriter(context);
53+
} else {
54+
new ScaleWriterAnnotatedEnum(typeElement).generateWriter(context);
55+
}
5156
} catch (ProcessingException e) {
5257
context.error(typeElement, e);
5358
return true;
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
package com.strategyobject.substrateclient.scale.codegen.reader;
22

33
import com.google.testing.compile.JavaFileObjects;
4+
import com.strategyobject.substrateclient.tests.TestSuite;
5+
import lombok.AccessLevel;
6+
import lombok.AllArgsConstructor;
47
import lombok.val;
8+
import org.junit.jupiter.api.DynamicTest;
59
import org.junit.jupiter.api.Test;
10+
import org.junit.jupiter.api.TestFactory;
11+
12+
import java.util.stream.Stream;
613

714
import static com.google.testing.compile.CompilationSubject.assertThat;
815
import static com.google.testing.compile.Compiler.javac;
@@ -21,47 +28,39 @@ void failsWhenWrongTemplate() {
2128
.hadErrorContaining("brackets");
2229
}
2330

24-
@Test
25-
void compilesAnnotated() {
26-
val clazz = JavaFileObjects.forResource("Annotated.java");
27-
28-
val compilation = javac()
29-
.withProcessors(new ScaleReaderProcessor())
30-
.compile(clazz);
31-
32-
assertThat(compilation).succeeded();
33-
}
34-
35-
@Test
36-
void compilesNonAnnotated() {
37-
val clazz = JavaFileObjects.forResource("NonAnnotated.java");
38-
39-
val compilation = javac()
40-
.withProcessors(new ScaleReaderProcessor())
41-
.compile(clazz);
42-
43-
assertThat(compilation).succeeded();
31+
@TestFactory
32+
Stream<DynamicTest> compiles() {
33+
return TestSuite.of(
34+
TestCase.compile("Annotated.java"),
35+
TestCase.compile("NonAnnotated.java"),
36+
TestCase.compile("ComplexGeneric.java"),
37+
TestCase.compile("Arrays.java"),
38+
TestCase.compile("Enum.java")
39+
);
4440
}
4541

46-
@Test
47-
void compilesComplexGeneric() {
48-
val clazz = JavaFileObjects.forResource("ComplexGeneric.java");
42+
@AllArgsConstructor(access = AccessLevel.PRIVATE)
43+
static class TestCase implements TestSuite.TestCase {
44+
private final String filename;
4945

50-
val compilation = javac()
51-
.withProcessors(new ScaleReaderProcessor())
52-
.compile(clazz);
46+
@Override
47+
public String getDisplayName() {
48+
return "compiles " + filename;
49+
}
5350

54-
assertThat(compilation).succeeded();
55-
}
51+
@Override
52+
public void execute() {
53+
val clazz = JavaFileObjects.forResource(filename);
5654

57-
@Test
58-
void compilesArrays() {
59-
val clazz = JavaFileObjects.forResource("Arrays.java");
55+
val compilation = javac()
56+
.withProcessors(new ScaleReaderProcessor())
57+
.compile(clazz);
6058

61-
val compilation = javac()
62-
.withProcessors(new ScaleReaderProcessor())
63-
.compile(clazz);
59+
assertThat(compilation).succeeded();
60+
}
6461

65-
assertThat(compilation).succeeded();
62+
public static TestCase compile(String filename) {
63+
return new TestCase(filename);
64+
}
6665
}
6766
}

0 commit comments

Comments
 (0)