gson-compile/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/gprocessor/InstanceProcessor.java

205 lines
11 KiB
Java

package io.gitlab.jfronny.gson.compile.processor.gprocessor;
import com.squareup.javapoet.*;
import io.gitlab.jfronny.gson.compile.processor.SerializableClass;
import io.gitlab.jfronny.gson.compile.processor.adapter.Adapters;
import io.gitlab.jfronny.gson.compile.processor.core.TypeHelper;
import io.gitlab.jfronny.gson.compile.processor.core.value.*;
import io.gitlab.jfronny.gson.compile.processor.Cl;
import javax.annotation.processing.Messager;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import java.io.IOException;
import java.util.List;
import java.util.Set;
public class InstanceProcessor extends GProcessor {
public InstanceProcessor(ValueCreator valueCreator, Messager message, boolean hasManifold) {
super(valueCreator, message, hasManifold, false);
}
@Override
public void generateDelegatingAdapter(TypeSpec.Builder spec, TypeName classType, ClassName generatedClassName) {
spec.addType(
TypeSpec.classBuilder("Adapter")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.superclass(ParameterizedTypeName.get(Cl.TYPE_ADAPTER, classType))
.addMethod(MethodSpec.methodBuilder("write")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(Cl.GSON_WRITER, "writer")
.addParameter(classType, "value")
.addException(IOException.class)
.addCode(generatedClassName.simpleName() + ".write(value, writer);")
.build())
.addMethod(MethodSpec.methodBuilder("read")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(Cl.GSON_READER, "reader")
.addException(IOException.class)
.returns(classType)
.addCode("return " + generatedClassName.simpleName() + ".read(reader);")
.build())
.build()
);
}
// !!!WARNING!!!
// A lot of this code is common between InstanceProcessor and StaticProcessor
// Make sure they don't get out of sync!
// (Or, alternatively, create one common solution for these)
// !!!WARNING!!!
@Override
public 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();
// public static void write(JsonWriter writer, T value) throws IOException
{
CodeBlock.Builder code = CodeBlock.builder();
code.beginControlFlow("if (value == null)")
.addStatement("writer.nullValue()")
.addStatement("return")
.endControlFlow();
code.addStatement("writer.beginObject()");
for (Property.Field param : properties.fields) {
if (Properties.containsName(properties.getters, param)) continue;
Runnable writeGet = () -> code.add("value.$N", param.getCallableName());
if (param.getType().getKind().isPrimitive()) {
generateComments(param, code);
code.addStatement("writer.name($S)", getSerializedName(param));
Adapters.generateWrite(param, spec, code, typeVariables, otherAdapters, message, writeGet);
} else {
code.beginControlFlow("if (value.$N != null || writer.getSerializeNulls())", param.getCallableName());
generateComments(param, code);
code.addStatement("writer.name($S)", getSerializedName(param));
code.addStatement("if (value.$N == null) writer.nullValue()", param.getCallableName());
code.beginControlFlow("else");
Adapters.generateWrite(param, spec, code, typeVariables, otherAdapters, message, writeGet);
code.endControlFlow();
code.endControlFlow();
}
}
for (Property.Getter param : properties.getters) {
if (param.getType().getKind().isPrimitive()) {
generateComments(param, code);
code.addStatement("writer.name($S)", getSerializedName(param));
Adapters.generateWrite(param, spec, code, typeVariables, otherAdapters, message, () -> code.add("value.$N()", param.getCallableName()));
} else {
code.addStatement("$T $L$N = value.$N()", param.getType(), "$", param.getCallableName(), param.getCallableName());
code.beginControlFlow("if ($L$N != null || writer.getSerializeNulls())", "$", param.getCallableName());
generateComments(param, code);
code.addStatement("writer.name($S)", getSerializedName(param));
code.addStatement("if ($L$N == null) writer.nullValue()", "$", param.getCallableName());
code.beginControlFlow("else");
Adapters.generateWrite(param, spec, code, typeVariables, otherAdapters, message, () -> code.add("$L$N", "$", param.getCallableName()));
code.endControlFlow();
code.endControlFlow();
}
}
code.addStatement("writer.endObject()");
spec.addMethod(extension(MethodSpec.methodBuilder("write"), self.getTypeName())
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addParameter(Cl.GSON_WRITER, "writer")
.addException(IOException.class)
.addCode(code.build())
.build());
}
// public static T read(JsonReader reader) throws IOException
{
CodeBlock.Builder code = CodeBlock.builder();
code.beginControlFlow("if (reader.peek() == $T.NULL)", Cl.GSON_TOKEN)
.addStatement("reader.nextNull()")
.addStatement("return null")
.endControlFlow();
boolean isEmpty = true;
for (Property<?> param : properties.names) {
isEmpty = false;
code.addStatement("$T _$N = $L", param.getType(), param.getName(), TypeHelper.getDefaultValue(param.getType()));
}
if (isEmpty) {
code.addStatement("reader.skipValue()");
} else {
code.addStatement("reader.beginObject()")
.beginControlFlow("while (reader.hasNext())")
.beginControlFlow("switch (reader.nextName())");
for (Property<?> param : properties.names) {
if (param.getType().getKind().isPrimitive()) {
code.add("case $S -> _$N = ", getSerializedName(param), param.getName());
Adapters.generateRead(param, spec, code, typeVariables, otherAdapters, message);
code.add(";\n");
} else {
code.beginControlFlow("case $S ->", getSerializedName(param))
.beginControlFlow("if (reader.peek() == $T.NULL)", Cl.GSON_TOKEN)
.addStatement("reader.nextNull()")
.addStatement("_$N = null", param.getName());
code.unindent().add("} else _$N = ", param.getName());
Adapters.generateRead(param, spec, code, typeVariables, otherAdapters, message);
code.add(";\n")
.endControlFlow();
}
}
code.add("default -> ")
.addStatement("reader.skipValue()");
code.endControlFlow()
.endControlFlow()
.addStatement("reader.endObject()");
}
code.addStatement("$T result", self.getTypeName());
ClassName creatorName = ClassName.get((TypeElement) constructionSource.getConstructionElement().getEnclosingElement());
if (constructionSource instanceof ConstructionSource.Builder builder) {
StringBuilder args = new StringBuilder();
for (Property.ConstructorParam param : properties.constructorParams) {
args.append(", _").append(param.getName());
}
code.add("$T builder = ", builder.getBuilderClass());
if (constructionSource.isConstructor()) {
code.add("new $T($L)", builder.getBuilderClass(), args.length() > 0 ? args.substring(2) : "");
} else {
code.add("$T.$N($L)", creatorName, self.classElement().getSimpleName(), args.length() > 0 ? args.substring(2) : "");
}
code.add(";\n");
for (Property.Setter param : properties.builderParams) {
code.addStatement("builder.$N(_$N)", param.getCallableName(), param.getName());
}
code.addStatement("result = builder.$N()", builder.getBuildMethod().getSimpleName());
} else {
StringBuilder args = new StringBuilder();
for (Property.Param param : properties.params) {
args.append(", _").append(param.getName());
}
if (constructionSource.isConstructor()) {
code.addStatement("result = new $T($L)", self.getTypeName(), args.length() > 0 ? args.substring(2) : "");
} else {
code.addStatement("result = $T.$N($L)", creatorName, constructionSource.getConstructionElement().getSimpleName(), args.length() > 0 ? args.substring(2) : "");
}
}
for (Property.Setter setter : properties.setters) {
code.addStatement("result.$N(_$N)", setter.getCallableName(), setter.getName());
}
for (Property.Field field : properties.fields) {
if (Properties.containsName(properties.setters, field)) continue;
if (Properties.containsName(properties.params, field)) continue;
code.addStatement("result.$N = _$N", field.getName(), field.getCallableName());
}
code.addStatement("return result");
spec.addMethod(extension(MethodSpec.methodBuilder("read"))
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(self.getTypeName())
.addParameter(Cl.GSON_READER, "reader")
.addException(IOException.class)
.addCode(code.build())
.build());
}
}
}