Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add java enums support
Browse files Browse the repository at this point in the history
kalaninja committed Jul 19, 2022
1 parent 01002ce commit ba0e699
Showing 16 changed files with 255 additions and 110 deletions.
Original file line number Diff line number Diff line change
@@ -57,7 +57,7 @@ Stream<DynamicTest> toBytesThrows() {
}

@AllArgsConstructor(access = AccessLevel.PRIVATE)
static class Test extends TestSuite.TestCase {
static class Test implements TestSuite.TestCase {
private final String displayName;
private final Executable executable;

Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.strategyobject.substrateclient.rpc.api;

import com.strategyobject.substrateclient.scale.annotation.ScaleWriter;
import lombok.Getter;

@ScaleWriter
public enum AddressKind {
ID((byte) 0);

This file was deleted.

Original file line number Diff line number Diff line change
@@ -76,7 +76,7 @@ Stream<DynamicTest> decodeRpcNull() {
}

@AllArgsConstructor(access = AccessLevel.PRIVATE)
static class Test<T> extends TestSuite.TestCase {
static class Test<T> implements TestSuite.TestCase {
private final RpcDecoder<T> decoder;
private final RpcObject given;
private final T expected;
Original file line number Diff line number Diff line change
@@ -68,7 +68,7 @@ Stream<DynamicTest> encode() {
}

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

private final RpcEncoder<T> encoder;
2 changes: 2 additions & 0 deletions scale/scale-codegen/build.gradle
Original file line number Diff line number Diff line change
@@ -6,5 +6,7 @@ dependencies {
annotationProcessor 'com.google.auto.service:auto-service:1.0.1'

implementation 'com.squareup:javapoet:1.13.0'

testImplementation project(':tests')
testImplementation 'com.google.testing.compile:compile-testing:0.19'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.strategyobject.substrateclient.scale.codegen.reader;

import com.squareup.javapoet.*;
import com.strategyobject.substrateclient.common.codegen.ProcessingException;
import com.strategyobject.substrateclient.common.codegen.ProcessorContext;
import com.strategyobject.substrateclient.common.io.Streamer;
import com.strategyobject.substrateclient.scale.ScaleReader;
import com.strategyobject.substrateclient.scale.annotation.AutoRegister;
import com.strategyobject.substrateclient.scale.codegen.ScaleProcessorHelper;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.val;

import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import java.io.IOException;
import java.io.InputStream;

@RequiredArgsConstructor
public class ScaleReaderAnnotatedEnum {
private static final String READERS_ARG = "readers";

private final @NonNull TypeElement enumElement;

public void generateReader(@NonNull ProcessorContext context) throws IOException, ProcessingException {
val readerName = ScaleProcessorHelper.getReaderName(enumElement.getSimpleName().toString());
val enumType = TypeName.get(enumElement.asType());

val typeSpecBuilder = TypeSpec.classBuilder(readerName)
.addAnnotation(AnnotationSpec.builder(AutoRegister.class)
.addMember("types", "{$L.class}", enumElement.getQualifiedName().toString())
.build())
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(ParameterizedTypeName.get(ClassName.get(ScaleReader.class), enumType))
.addMethod(generateReadMethod(enumType));

JavaFile.builder(
context.getPackageName(enumElement),
typeSpecBuilder.build()
).build().writeTo(context.getFiler());
}

private MethodSpec generateReadMethod(TypeName enumType) {
val methodSpec = MethodSpec.methodBuilder("read")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(enumType)
.addParameter(InputStream.class, "stream")
.addParameter(ArrayTypeName.of(
ParameterizedTypeName.get(
ClassName.get(ScaleReader.class),
WildcardTypeName.subtypeOf(Object.class))),
READERS_ARG)
.varargs(true)
.addException(IOException.class);

addValidationRules(methodSpec);
addMethodBody(methodSpec);
return methodSpec.build();
}

private void addValidationRules(MethodSpec.Builder methodSpec) {
methodSpec
.addStatement("if (stream == null) throw new IllegalArgumentException(\"stream is null\")")
.addStatement("if (readers != null && readers.length > 0) throw new IllegalArgumentException()");
}

private void addMethodBody(MethodSpec.Builder methodSpec) {
methodSpec.addStatement("return $T.values()[$T.readByte(stream)]", enumElement, Streamer.class);
}
}
Original file line number Diff line number Diff line change
@@ -36,18 +36,23 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
}

for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(ScaleReader.class)) {
if (annotatedElement.getKind() != ElementKind.CLASS) {
val elementKind = annotatedElement.getKind();
if (!elementKind.isClass()) {
context.error(
annotatedElement,
"Only classes can be annotated with `@%s`.",
"Only classes and enums can be annotated with `@%s`.",
ScaleReader.class.getSimpleName());

return true;
}

val typeElement = (TypeElement) annotatedElement;
try {
new ScaleReaderAnnotatedClass(typeElement).generateReader(context);
if (elementKind == ElementKind.CLASS) {
new ScaleReaderAnnotatedClass(typeElement).generateReader(context);
} else {
new ScaleReaderAnnotatedEnum(typeElement).generateReader(context);
}
} catch (ProcessingException e) {
context.error(typeElement, e);
return true;
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.strategyobject.substrateclient.scale.codegen.writer;

import com.squareup.javapoet.*;
import com.strategyobject.substrateclient.common.codegen.ProcessingException;
import com.strategyobject.substrateclient.common.codegen.ProcessorContext;
import com.strategyobject.substrateclient.scale.ScaleWriter;
import com.strategyobject.substrateclient.scale.annotation.AutoRegister;
import com.strategyobject.substrateclient.scale.codegen.ScaleProcessorHelper;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.val;

import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import java.io.IOException;
import java.io.OutputStream;

@RequiredArgsConstructor
public class ScaleWriterAnnotatedEnum {
private static final String WRITERS_ARG = "writers";

private final @NonNull TypeElement enumElement;

public void generateWriter(@NonNull ProcessorContext context) throws IOException, ProcessingException {
val writerName = ScaleProcessorHelper.getWriterName(enumElement.getSimpleName().toString());
val enumType = TypeName.get(enumElement.asType());

val typeSpecBuilder = TypeSpec.classBuilder(writerName)
.addAnnotation(AnnotationSpec.builder(AutoRegister.class)
.addMember("types", "{$L.class}", enumElement.getQualifiedName().toString())
.build())
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(ParameterizedTypeName.get(ClassName.get(ScaleWriter.class), enumType))
.addMethod(generateWriteMethod(enumType));

JavaFile.builder(
context.getPackageName(enumElement),
typeSpecBuilder.build()
).build().writeTo(context.getFiler());
}

private MethodSpec generateWriteMethod(TypeName classWildcardTyped) {
val methodSpec = MethodSpec.methodBuilder("write")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(TypeName.VOID)
.addParameter(classWildcardTyped, "value")
.addParameter(OutputStream.class, "stream")
.addParameter(ArrayTypeName.of(
ParameterizedTypeName.get(
ClassName.get(ScaleWriter.class),
WildcardTypeName.subtypeOf(Object.class))),
WRITERS_ARG)
.varargs(true)
.addException(IOException.class);

addValidationRules(methodSpec);
addMethodBody(methodSpec);
return methodSpec.build();
}

private void addValidationRules(MethodSpec.Builder methodSpec) {
methodSpec.addStatement("if (stream == null) throw new IllegalArgumentException(\"stream is null\")");
methodSpec.addStatement("if (value == null) throw new IllegalArgumentException(\"value is null\")");
methodSpec.addStatement("if (writers != null && writers.length > 0) throw new IllegalArgumentException()");
}

private void addMethodBody(MethodSpec.Builder methodSpec) {
methodSpec.addStatement("stream.write(value.ordinal())");
}
}
Original file line number Diff line number Diff line change
@@ -36,18 +36,23 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
}

for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(ScaleWriter.class)) {
if (annotatedElement.getKind() != ElementKind.CLASS) {
val elementKind = annotatedElement.getKind();
if (!elementKind.isClass()) {
context.error(
annotatedElement,
"Only classes can be annotated with `@%s`.",
"Only classes and enums can be annotated with `@%s`.",
ScaleWriter.class.getSimpleName());

return true;
}

val typeElement = (TypeElement) annotatedElement;
try {
new ScaleWriterAnnotatedClass(typeElement).generateWriter(context);
if (elementKind == ElementKind.CLASS) {
new ScaleWriterAnnotatedClass(typeElement).generateWriter(context);
} else {
new ScaleWriterAnnotatedEnum(typeElement).generateWriter(context);
}
} catch (ProcessingException e) {
context.error(typeElement, e);
return true;
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
package com.strategyobject.substrateclient.scale.codegen.reader;

import com.google.testing.compile.JavaFileObjects;
import com.strategyobject.substrateclient.tests.TestSuite;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.val;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;

import java.util.stream.Stream;

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

@Test
void compilesAnnotated() {
val clazz = JavaFileObjects.forResource("Annotated.java");

val compilation = javac()
.withProcessors(new ScaleReaderProcessor())
.compile(clazz);

assertThat(compilation).succeeded();
}

@Test
void compilesNonAnnotated() {
val clazz = JavaFileObjects.forResource("NonAnnotated.java");

val compilation = javac()
.withProcessors(new ScaleReaderProcessor())
.compile(clazz);

assertThat(compilation).succeeded();
@TestFactory
Stream<DynamicTest> compiles() {
return TestSuite.of(
TestCase.compile("Annotated.java"),
TestCase.compile("NonAnnotated.java"),
TestCase.compile("ComplexGeneric.java"),
TestCase.compile("Arrays.java"),
TestCase.compile("Enum.java")
);
}

@Test
void compilesComplexGeneric() {
val clazz = JavaFileObjects.forResource("ComplexGeneric.java");
@AllArgsConstructor(access = AccessLevel.PRIVATE)
static class TestCase implements TestSuite.TestCase {
private final String filename;

val compilation = javac()
.withProcessors(new ScaleReaderProcessor())
.compile(clazz);
@Override
public String getDisplayName() {
return "compiles " + filename;
}

assertThat(compilation).succeeded();
}
@Override
public void execute() {
val clazz = JavaFileObjects.forResource(filename);

@Test
void compilesArrays() {
val clazz = JavaFileObjects.forResource("Arrays.java");
val compilation = javac()
.withProcessors(new ScaleReaderProcessor())
.compile(clazz);

val compilation = javac()
.withProcessors(new ScaleReaderProcessor())
.compile(clazz);
assertThat(compilation).succeeded();
}

assertThat(compilation).succeeded();
public static TestCase compile(String filename) {
return new TestCase(filename);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
package com.strategyobject.substrateclient.scale.codegen.writer;

import com.google.testing.compile.JavaFileObjects;
import com.strategyobject.substrateclient.tests.TestSuite;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.val;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;

import java.util.stream.Stream;

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

@Test
void compilesAnnotated() {
val clazz = JavaFileObjects.forResource("Annotated.java");

val compilation = javac()
.withProcessors(new ScaleWriterProcessor())
.compile(clazz);

assertThat(compilation).succeeded();
}

@Test
void compilesNonAnnotated() {
val clazz = JavaFileObjects.forResource("NonAnnotated.java");

val compilation = javac()
.withProcessors(new ScaleWriterProcessor())
.compile(clazz);

assertThat(compilation).succeeded();
@TestFactory
Stream<DynamicTest> compiles() {
return TestSuite.of(
TestCase.compile("Annotated.java"),
TestCase.compile("NonAnnotated.java"),
TestCase.compile("ComplexGeneric.java"),
TestCase.compile("Arrays.java"),
TestCase.compile("Enum.java")
);
}

@Test
void compilesComplexGeneric() {
val clazz = JavaFileObjects.forResource("ComplexGeneric.java");
@AllArgsConstructor(access = AccessLevel.PRIVATE)
static class TestCase implements TestSuite.TestCase {
private final String filename;

val compilation = javac()
.withProcessors(new ScaleWriterProcessor())
.compile(clazz);
@Override
public String getDisplayName() {
return "compiles " + filename;
}

assertThat(compilation).succeeded();
}
@Override
public void execute() {
val clazz = JavaFileObjects.forResource(filename);

@Test
void compilesArrays() {
val clazz = JavaFileObjects.forResource("Arrays.java");
val compilation = javac()
.withProcessors(new ScaleWriterProcessor())
.compile(clazz);

val compilation = javac()
.withProcessors(new ScaleWriterProcessor())
.compile(clazz);
assertThat(compilation).succeeded();
}

assertThat(compilation).succeeded();
public static TestCase compile(String filename) {
return new TestCase(filename);
}
}
}
16 changes: 16 additions & 0 deletions scale/scale-codegen/src/test/resources/Enum.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.strategyobject.substrateclient.scale;

import com.strategyobject.substrateclient.scale.annotation.ScaleReader;
import com.strategyobject.substrateclient.scale.annotation.ScaleWriter;

@ScaleReader
@ScaleWriter
public enum Enum {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
Original file line number Diff line number Diff line change
@@ -73,7 +73,7 @@ Stream<DynamicTest> readEmpty() {
}

@AllArgsConstructor(access = AccessLevel.PRIVATE)
static class Test<T> extends TestSuite.TestCase {
static class Test<T> implements TestSuite.TestCase {
private final ScaleReader<T> reader;
private final String given;
private final T expected;
Original file line number Diff line number Diff line change
@@ -62,7 +62,7 @@ Stream<DynamicTest> writeEmpty() {
}

@AllArgsConstructor(access = AccessLevel.PRIVATE)
static class Test<T> extends TestSuite.TestCase {
static class Test<T> implements TestSuite.TestCase {
private final ScaleWriter<T> writer;
private final T given;
private final String expected;
Original file line number Diff line number Diff line change
@@ -14,12 +14,12 @@ public static Stream<DynamicTest> of(TestCase... testCases) {
private TestSuite() {
}

public abstract static class TestCase {
public abstract String getDisplayName();
public interface TestCase {
String getDisplayName();

public abstract void execute() throws Throwable;
void execute() throws Throwable;

public DynamicTest generate() {
default DynamicTest generate() {
return DynamicTest.dynamicTest(getDisplayName(), this::execute);
}
}

0 comments on commit ba0e699

Please sign in to comment.