Skip to content

Commit d337038

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

File tree

18 files changed

+308
-119
lines changed

18 files changed

+308
-119
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.strategyobject.substrateclient.common.types;
2+
3+
import com.google.common.base.Preconditions;
4+
import lombok.NonNull;
5+
6+
public class Enums {
7+
public static <E extends Enum<E>> E lookup(E @NonNull [] enumValues, int index) {
8+
Preconditions.checkArgument(enumValues.length > 0);
9+
Preconditions.checkArgument(index >= 0);
10+
Preconditions.checkArgument(index < enumValues.length,
11+
enumValues[0].getClass().getSimpleName() + " has no value associated with index " + index);
12+
13+
return enumValues[index];
14+
}
15+
16+
private Enums() {
17+
}
18+
}

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

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.strategyobject.substrateclient.common.types;
2+
3+
import lombok.val;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static org.junit.jupiter.api.Assertions.*;
7+
8+
class EnumsTest {
9+
10+
enum TestEnum {
11+
YES, NO, MAYBE
12+
}
13+
14+
@Test
15+
void lookup() {
16+
val actual = Enums.lookup(TestEnum.values(), 0);
17+
18+
assertEquals(TestEnum.YES, actual);
19+
}
20+
21+
@Test
22+
void lookupOutOfBounds() {
23+
val values = TestEnum.values();
24+
val thrown = assertThrows(RuntimeException.class, () -> Enums.lookup(values, 10));
25+
26+
assertTrue(thrown.getMessage().contains("TestEnum"));
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
11
package com.strategyobject.substrateclient.rpc.api;
22

3-
import lombok.Getter;
3+
import com.strategyobject.substrateclient.scale.annotation.ScaleWriter;
44

5+
@ScaleWriter
56
public enum AddressKind {
6-
ID((byte) 0);
7-
8-
@Getter
9-
private final byte value;
10-
11-
AddressKind(byte value) {
12-
this.value = value;
13-
}
7+
ID
148
}

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,77 @@
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.common.types.Enums;
8+
import com.strategyobject.substrateclient.scale.ScaleReader;
9+
import com.strategyobject.substrateclient.scale.annotation.AutoRegister;
10+
import com.strategyobject.substrateclient.scale.codegen.ScaleProcessorHelper;
11+
import lombok.NonNull;
12+
import lombok.RequiredArgsConstructor;
13+
import lombok.val;
14+
15+
import javax.lang.model.element.Modifier;
16+
import javax.lang.model.element.TypeElement;
17+
import java.io.IOException;
18+
import java.io.InputStream;
19+
20+
@RequiredArgsConstructor
21+
public class ScaleReaderAnnotatedEnum {
22+
private static final String READERS_ARG = "readers";
23+
private static final String ENUM_VALUES = "values";
24+
25+
private final @NonNull TypeElement enumElement;
26+
27+
public void generateReader(@NonNull ProcessorContext context) throws IOException, ProcessingException {
28+
val readerName = ScaleProcessorHelper.getReaderName(enumElement.getSimpleName().toString());
29+
val enumType = TypeName.get(enumElement.asType());
30+
31+
val typeSpecBuilder = TypeSpec.classBuilder(readerName)
32+
.addAnnotation(AnnotationSpec.builder(AutoRegister.class)
33+
.addMember("types", "{$L.class}", enumElement.getQualifiedName().toString())
34+
.build())
35+
.addModifiers(Modifier.PUBLIC)
36+
.addSuperinterface(ParameterizedTypeName.get(ClassName.get(ScaleReader.class), enumType))
37+
.addField(
38+
FieldSpec.builder(ArrayTypeName.of(enumType), ENUM_VALUES, Modifier.PRIVATE, Modifier.FINAL)
39+
.initializer("$T.values()", enumType)
40+
.build())
41+
.addMethod(generateReadMethod(enumType));
42+
43+
JavaFile.builder(
44+
context.getPackageName(enumElement),
45+
typeSpecBuilder.build()
46+
).build().writeTo(context.getFiler());
47+
}
48+
49+
private MethodSpec generateReadMethod(TypeName enumType) {
50+
val methodSpec = MethodSpec.methodBuilder("read")
51+
.addAnnotation(Override.class)
52+
.addModifiers(Modifier.PUBLIC)
53+
.returns(enumType)
54+
.addParameter(InputStream.class, "stream")
55+
.addParameter(ArrayTypeName.of(
56+
ParameterizedTypeName.get(
57+
ClassName.get(ScaleReader.class),
58+
WildcardTypeName.subtypeOf(Object.class))),
59+
READERS_ARG)
60+
.varargs(true)
61+
.addException(IOException.class);
62+
63+
addValidationRules(methodSpec);
64+
addMethodBody(methodSpec);
65+
return methodSpec.build();
66+
}
67+
68+
private void addValidationRules(MethodSpec.Builder methodSpec) {
69+
methodSpec
70+
.addStatement("if (stream == null) throw new IllegalArgumentException(\"stream is null\")")
71+
.addStatement("if (readers != null && readers.length > 0) throw new IllegalArgumentException()");
72+
}
73+
74+
private void addMethodBody(MethodSpec.Builder methodSpec) {
75+
methodSpec.addStatement("return $T.lookup($L, $T.readByte(stream))", Enums.class, ENUM_VALUES, Streamer.class);
76+
}
77+
}

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;

0 commit comments

Comments
 (0)