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 typeVariables, Set 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()); } } }