Support builders

This commit is contained in:
Johannes Frohnmeyer 2022-11-01 15:39:50 +01:00
parent cf795cdedc
commit 47c77a116b
Signed by: Johannes
GPG Key ID: E76429612C2929F4
4 changed files with 85 additions and 33 deletions

View File

@ -10,6 +10,11 @@ public @interface GSerializable {
*/
Class<?> with() default void.class;
/**
* @return The builder class to use for creating this type. Incompatible with custom serialization
*/
Class<?> builder() default void.class;
/**
* @return Whether to generate an adapter class to use with normal gson adapter resolution
*/

View File

@ -53,4 +53,30 @@ public class Main {
this(yes, null);
}
}
@GSerializable(builder = Example4.Builder.class)
public static class Example4 {
public String someField;
public boolean shesh;
public static class Builder {
private String someField;
private boolean shesh;
public Builder(String someField) {
this.someField = someField;
}
public Builder setShesh(boolean shesh) {
this.shesh = shesh;
return this;
}
public Example4 build() {
Example4 e = new Example4();
e.someField = someField;
return e;
}
}
}
}

View File

@ -50,30 +50,39 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
for (TypeElement annotation : annotations) {
for (Element element : roundEnvironment.getElementsAnnotatedWith(annotation)) {
for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
if (mirror.getAnnotationType().toString().equals(GSerializable.class.getCanonicalName())) {
var bld = new Object() {
TypeMirror with = null;
Boolean generateAdapter = null;
};
elements.getElementValuesWithDefaults(mirror).forEach((executableElement, value) -> {
String name = executableElement.getSimpleName().toString();
switch (name) {
case "with" -> {
if (bld.with != null) throw new IllegalArgumentException("Duplicate annotation parameter: with");
bld.with = (TypeMirror) value.getValue();
try {
if (mirror.getAnnotationType().toString().equals(GSerializable.class.getCanonicalName())) {
var bld = new Object() {
TypeMirror with = null;
TypeMirror builder = null;
Boolean generateAdapter = null;
};
elements.getElementValuesWithDefaults(mirror).forEach((executableElement, value) -> {
String name = executableElement.getSimpleName().toString();
switch (name) {
case "with" -> {
if (bld.with != null) throw new IllegalArgumentException("Duplicate annotation parameter: with");
bld.with = (TypeMirror) value.getValue();
}
case "builder" -> {
if (bld.builder != null) throw new IllegalArgumentException("Duplicate annotation parameter: builder");
bld.builder = (TypeMirror) value.getValue();
}
case "generateAdapter" -> {
if (bld.generateAdapter != null) throw new IllegalArgumentException("Duplicate annotation parameter: generateAdapter");
bld.generateAdapter = (Boolean) value.getValue();
}
default -> throw new IllegalArgumentException("Unexpected annotation parameter: " + name);
}
case "generateAdapter" -> {
if (bld.generateAdapter != null) throw new IllegalArgumentException("Duplicate annotation parameter: generateAdapter");
bld.generateAdapter = (Boolean) value.getValue();
}
default -> throw new IllegalArgumentException("Unexpected annotation parameter: " + name);
}
});
if (bld.with == null) throw new IllegalArgumentException("Missing annotation parameter: with");
if (bld.generateAdapter == null) throw new IllegalArgumentException("Missing annotation parameter: generateAdapter");
if (bld.with.toString().equals("void")) bld.with = null;
});
if (bld.with == null) throw new IllegalArgumentException("Missing annotation parameter: with");
if (bld.builder == null) throw new IllegalArgumentException("Missing annotation parameter: with");
if (bld.generateAdapter == null) throw new IllegalArgumentException("Missing annotation parameter: generateAdapter");
toGenerate.add(SerializableClass.of((TypeElement) element, bld.with, bld.generateAdapter));
toGenerate.add(SerializableClass.of((TypeElement) element, bld.with, bld.builder, bld.generateAdapter));
}
} catch (ElementException e) {
e.printMessage(message);
}
}
}
@ -84,8 +93,10 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
for (SerializableClass toProcess : toGenerate) {
try {
process(toProcess, toGenerate);
} catch (IOException | ElementException e) {
} catch (IOException e) {
message.printMessage(Diagnostic.Kind.ERROR, "GsonCompile threw an exception: " + StringFormatter.toString(e), toProcess.classElement());
} catch (ElementException e) {
e.printMessage(message);
}
}
return false;
@ -114,7 +125,7 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
if (toProcess.generateAdapter()) {
generateDelegatingAdapter(spec, classType, toProcess.generatedClassName());
}
generateSerialisation(spec, classType, toProcess.classElement(), typeVariables, other);
generateSerialisation(spec, toProcess, typeVariables, other);
}
generateAuxiliary(spec, classType);
@ -256,8 +267,8 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
);
}
private void generateSerialisation(TypeSpec.Builder spec, TypeName classType, TypeElement classElement, List<TypeVariableName> typeVariables, Set<SerializableClass> otherAdapters) throws ElementException {
Value value = valueCreator.from(classElement, false);
private void generateSerialisation(TypeSpec.Builder spec, SerializableClass self, List<TypeVariableName> typeVariables, Set<SerializableClass> otherAdapters) throws ElementException {
Value value = self.builder() == null ? valueCreator.from(self.classElement(), false) : valueCreator.from(TypeHelper.asDeclaredType(self.builder()).asElement(), true);
ConstructionSource constructionSource = value.getConstructionSource();
Properties properties = value.getProperties();
@ -303,7 +314,7 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
spec.addMethod(MethodSpec.methodBuilder("write")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addParameter(Cl.GSON_WRITER, "writer")
.addParameter(classType, "value")
.addParameter(self.getTypeName(), "value")
.addException(IOException.class)
.addCode(code.build())
.build());
@ -358,7 +369,7 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
if (constructionSource.isConstructor()) {
code.add("return new $T($L)", builder.getBuilderClass(), args);
} else {
code.add("return $T.$N($L)", creatorName, classElement.getSimpleName(), args);
code.add("return $T.$N($L)", creatorName, self.classElement().getSimpleName(), args);
}
code.add("\n").indent();
for (Property.BuilderParam param : properties.builderParams) {
@ -368,7 +379,7 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
} else {
String args = properties.params.stream().map(s -> "_" + s.getName()).collect(Collectors.joining(", "));
if (constructionSource.isConstructor()) {
code.addStatement("return new $T($L)", classType, args);
code.addStatement("return new $T($L)", self.getTypeName(), args);
} else {
code.addStatement("return $T.$N($L)", creatorName, constructionSource.getConstructionElement().getSimpleName(), args);
}
@ -377,7 +388,7 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
spec.addMethod(MethodSpec.methodBuilder("read")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(classType)
.returns(self.getTypeName())
.addParameter(Cl.GSON_READER, "reader")
.addException(IOException.class)
.addCode(code.build())

View File

@ -2,16 +2,17 @@ package io.gitlab.jfronny.gson.compile.processor;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.TypeName;
import io.gitlab.jfronny.gson.compile.processor.util.valueprocessor.ElementException;
import org.jetbrains.annotations.Nullable;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
public record SerializableClass(TypeElement classElement, ClassName generatedClassName, @Nullable TypeMirror adapter, boolean generateAdapter) {
public static SerializableClass of(TypeElement element, @Nullable TypeMirror with, boolean generateAdapter) {
public record SerializableClass(TypeElement classElement, ClassName generatedClassName, @Nullable TypeMirror adapter, @Nullable TypeMirror builder, boolean generateAdapter) {
public static SerializableClass of(TypeElement element, @Nullable TypeMirror with, @Nullable TypeMirror builder, boolean generateAdapter) throws ElementException {
ClassName className = ClassName.get(element);
ClassName generatedClassName = ClassName.get(className.packageName(), "GC_" + String.join("_", className.simpleNames()));
return new SerializableClass(element, generatedClassName, with, generateAdapter);
return new SerializableClass(element, generatedClassName, voidToNull(with), voidToNull(builder), generateAdapter).validate();
}
public ClassName getClassName() {
@ -21,4 +22,13 @@ public record SerializableClass(TypeElement classElement, ClassName generatedCla
public TypeName getTypeName() {
return TypeName.get(classElement.asType());
}
private SerializableClass validate() throws ElementException {
if (adapter != null && builder != null) throw new ElementException("@Serializable with both an adapter and a builder. This is unsupported!", classElement);
return this;
}
private static TypeMirror voidToNull(TypeMirror type) {
return type == null || type.toString().equals("void") ? null : type;
}
}