package io.gitlab.jfronny.commons.serialize.generator.gprocessor; import com.squareup.javapoet.*; import io.gitlab.jfronny.commons.serialize.generator.Cl; import io.gitlab.jfronny.commons.serialize.generator.SerializableClass; import io.gitlab.jfronny.commons.serialize.generator.adapter.Adapters; import io.gitlab.jfronny.commons.serialize.generator.core.TypeHelper; import io.gitlab.jfronny.commons.serialize.generator.core.value.*; import javax.annotation.processing.Messager; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import java.util.List; import java.util.Set; public class InstanceProcessor extends GProcessor { public InstanceProcessor(ValueCreator valueCreator, Messager message, boolean hasManifold, boolean disableSafe) { super(valueCreator, message, hasManifold, false, disableSafe); } @Override public ClassName generateDelegatingAdapter(TypeSpec.Builder spec, TypeName classType, ClassName generatedClassName) { spec.addType( TypeSpec.classBuilder("Adapter") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addAnnotation(AnnotationSpec.builder(Cl.SERIALIZER_FOR).addMember("targets", CodeBlock.builder().add("$T.class", classType).build()).build()) .superclass(ParameterizedTypeName.get(Cl.TYPE_ADAPTER, classType)) .addMethod(MethodSpec.methodBuilder("serialize") .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) .addTypeVariable(TypeVariableName.get("TEx", Exception.class)) .addTypeVariable(TypeVariableName.get("Writer", ParameterizedTypeName.get(Cl.SERIALIZE_WRITER, TypeVariableName.get("TEx"), TypeVariableName.get("Writer")))) .addParameter(classType, "value") .addParameter(TypeVariableName.get("Writer"), "writer") .addException(TypeVariableName.get("TEx")) .addException(Cl.MALFORMED_DATA_EXCEPTION) .addCode(generatedClassName.simpleName() + ".serialize(value, writer);") .build()) .addMethod(MethodSpec.methodBuilder("deserialize") .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) .addTypeVariable(TypeVariableName.get("TEx", Exception.class)) .addTypeVariable(TypeVariableName.get("Reader", ParameterizedTypeName.get(Cl.SERIALIZE_READER, TypeVariableName.get("TEx"), TypeVariableName.get("Reader")))) .addParameter(TypeVariableName.get("Reader"), "reader") .addException(TypeVariableName.get("TEx")) .addException(Cl.MALFORMED_DATA_EXCEPTION) .returns(classType) .addCode("return " + generatedClassName.simpleName() + ".deserialize(reader);") .build()) .build() ); return generatedClassName.nestedClass("Adapter"); } // !!!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) { Property altGetter = Properties.findName(properties.getters, param); if (altGetter != null && !isIgnored(altGetter)) continue; if (isIgnored(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.isSerializeNulls())", 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 (isIgnored(param)) continue; 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.isSerializeNulls())", "$", 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("serialize"), self.getTypeName()) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addTypeVariable(TypeVariableName.get("TEx", Exception.class)) .addTypeVariable(TypeVariableName.get("Writer", ParameterizedTypeName.get(Cl.SERIALIZE_WRITER, TypeVariableName.get("TEx"), TypeVariableName.get("Writer")))) .addParameter(TypeVariableName.get("Writer"), "writer") .addException(TypeVariableName.get("TEx")) .addException(Cl.MALFORMED_DATA_EXCEPTION) .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) { if (isIgnored(param) && getAlternative(properties, param) == null) continue; isEmpty = false; code.addStatement("$T _$N = $L", param.getType(), param.getName(), TypeHelper.getDefaultValue(param.getType())); code.addStatement("boolean has_$N = false", param.getName()); } 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 (isIgnored(param) && getAlternative(properties, param) == null) continue; code.beginControlFlow("case $S ->", getSerializedName(param)); if (param.getType().getKind().isPrimitive()) { code.add("_$N = ", param.getName()); Adapters.generateRead(param, spec, code, typeVariables, otherAdapters, message); code.add(";\n"); } else { code.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"); } code.addStatement("has_$N = true", param.getName()); code.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.isEmpty() ? args.substring(2) : ""); } else { code.add("$T.$N($L)", creatorName, self.classElement().getSimpleName(), !args.isEmpty() ? args.substring(2) : ""); } code.add(";\n"); for (Property.Setter param : properties.builderParams) { if (isIgnored(param)) continue; code.addStatement("if (has_$N) builder.$N(_$N)", param.getName(), 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.isEmpty() ? args.substring(2) : ""); } else { code.addStatement("result = $T.$N($L)", creatorName, constructionSource.getConstructionElement().getSimpleName(), !args.isEmpty() ? args.substring(2) : ""); } } for (Property.Setter setter : properties.setters) { if (isIgnored(setter)) continue; code.addStatement("if (has_$N) result.$N(_$N)", setter.getName(), setter.getCallableName(), setter.getName()); } for (Property.Field field : properties.fields) { if (Properties.containsName(properties.setters, field)) continue; if (Properties.containsName(properties.params, field)) continue; if (isIgnored(field)) continue; code.addStatement("if (has_$N) result.$N = _$N", field.getName(), field.getCallableName(), field.getName()); } code.addStatement("return result"); spec.addMethod(extension(MethodSpec.methodBuilder("deserialize")) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(self.getTypeName()) .addTypeVariable(TypeVariableName.get("TEx", Exception.class)) .addTypeVariable(TypeVariableName.get("Reader", ParameterizedTypeName.get(Cl.SERIALIZE_READER, TypeVariableName.get("TEx"), TypeVariableName.get("Reader")))) .addParameter(TypeVariableName.get("Reader"), "reader") .addException(TypeVariableName.get("TEx")) .addException(Cl.MALFORMED_DATA_EXCEPTION) .addCode(code.build()) .build()); } } }