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.core.value.ElementException; import io.gitlab.jfronny.commons.serialize.generator.core.value.Property; import io.gitlab.jfronny.commons.serialize.generator.core.value.ValueCreator; import javax.annotation.processing.Messager; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Modifier; import javax.lang.model.type.TypeMirror; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.util.List; import java.util.Set; import java.util.function.UnaryOperator; public abstract class GProcessor { protected final ValueCreator valueCreator; protected final Messager message; protected final boolean hasManifold; private final boolean isStatic; private final String readStatement; private final String readStatementT; private final String writeStatement; private final String writeStatementT; private final boolean disableSafe; public GProcessor(ValueCreator valueCreator, Messager message, boolean hasManifold, boolean isStatic, boolean disableSafe) { this.valueCreator = valueCreator; this.message = message; this.hasManifold = hasManifold; this.disableSafe = disableSafe; this.isStatic = isStatic; this.readStatement = isStatic ? "deserialize(reader)" : "return deserialize(reader)"; this.readStatementT = isStatic ? "deserialize(reader, transport)" : "return deserialize(reader, transport)"; this.writeStatement = isStatic ? "serialize(writer)" : "serialize(value, writer)"; this.writeStatementT = isStatic ? "serialize(writer, transport)" : "serialize(value, writer, transport)"; } public abstract ClassName generateDelegatingAdapter(TypeSpec.Builder spec, TypeName classType, ClassName generatedClassName); public abstract void generateSerialisation(TypeSpec.Builder spec, SerializableClass self, List typeVariables, Set otherAdapters) throws ElementException; protected String getSerializedName(Property property) { for (AnnotationMirror annotationMirror : property.getAnnotations()) { if (annotationMirror.getAnnotationType().asElement().toString().equals(Cl.SERIALIZED_NAME.toString())) { return (String) annotationMirror.getElementValues().values().iterator().next().getValue(); } } return property.getName(); } protected MethodSpec.Builder extension(MethodSpec.Builder method) { if (hasManifold) method.addAnnotation(Cl.MANIFOLD_EXTENSION); return method; } protected MethodSpec.Builder extension(MethodSpec.Builder method, TypeName thizName) { if (thizName == null) return extension(method); if (hasManifold) { method.addAnnotation(Cl.MANIFOLD_EXTENSION); method.addParameter(ParameterSpec.builder(thizName, "value").addAnnotation(Cl.MANIFOLD_THIS).build()); } else { method.addParameter(thizName, "value"); } return method; } protected void generateComments(Property prop, CodeBlock.Builder code) { for (AnnotationMirror annotation : prop.getAnnotations()) { if (annotation.getAnnotationType().asElement().toString().equals(Cl.GCOMMENT.toString())) { String comment = (String) annotation.getElementValues().values().iterator().next().getValue(); code.addStatement("if (writer.isLenient()) writer.comment($S)", comment); } } } public void generateDelegateToAdapter(TypeSpec.Builder spec, TypeName classType, TypeMirror adapter, boolean isStatic) { if (!isStatic) { spec.addField( FieldSpec.builder(TypeName.get(adapter), "adapter", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) .initializer("new $T()", adapter) .build() ); } spec.addMethod( extension(MethodSpec.methodBuilder("deserialize")) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .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(isStatic ? TypeName.VOID : classType) .addCode(isStatic ? this.isStatic ? "$T.deserialize(reader);" : "return $T.deserialize(reader);" : this.isStatic ? "$L.deserialize(reader);" : "return $L.deserialize(reader);", isStatic ? adapter : "adapter") .build() ); spec.addMethod( extension(MethodSpec.methodBuilder("serialize"), this.isStatic ? null : classType) .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(isStatic ? "$T.$L;" : "$L.$L", isStatic ? adapter : "adapter", writeStatement) .build() ); } public void generateAuxiliary(TypeSpec.Builder spec, TypeName classType, TypeMirror configure) { final UnaryOperator configureReader = cb -> { if (configure != null) cb.addStatement("$T.configure(reader)", configure); return cb; }; final UnaryOperator configureWriter = cb -> { if (configure != null) cb.addStatement("$T.configure(writer)", configure); return cb; }; spec.addMethod( extension(MethodSpec.methodBuilder("deserialize")) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addTypeVariable(TypeVariableName.get("TEx", Exception.class)) .addTypeVariable(TypeVariableName.get("Reader", ParameterizedTypeName.get(Cl.SERIALIZE_READER, TypeVariableName.get("TEx"), TypeVariableName.get("Reader")))) .addTypeVariable(TypeVariableName.get("Writer", ParameterizedTypeName.get(Cl.SERIALIZE_WRITER, TypeVariableName.get("TEx"), TypeVariableName.get("Writer")))) .addParameter(TypeName.get(Reader.class), "in") .addParameter(ParameterizedTypeName.get(Cl.TRANSPORT, TypeVariableName.get("TEx"), TypeVariableName.get("Reader"), TypeVariableName.get("Writer")), "transport") .addException(TypeVariableName.get("TEx")) .addException(Cl.MALFORMED_DATA_EXCEPTION) .returns(isStatic ? TypeName.VOID : classType) .addCode(configureReader.apply(CodeBlock.builder().beginControlFlow((isStatic ? "" : "return ") + "transport.read(in, reader -> ")) .addStatement(readStatement) .addStatement("$<})") .build()) .build() ); spec.addMethod( extension(MethodSpec.methodBuilder("deserialize")) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addParameter(TypeName.get(String.class), "serialized") .addTypeVariable(TypeVariableName.get("TEx", Exception.class)) .addTypeVariable(TypeVariableName.get("Reader", ParameterizedTypeName.get(Cl.SERIALIZE_READER, TypeVariableName.get("TEx"), TypeVariableName.get("Reader")))) .addTypeVariable(TypeVariableName.get("Writer", ParameterizedTypeName.get(Cl.SERIALIZE_WRITER, TypeVariableName.get("TEx"), TypeVariableName.get("Writer")))) .addParameter(ParameterizedTypeName.get(Cl.TRANSPORT, TypeVariableName.get("TEx"), TypeVariableName.get("Reader"), TypeVariableName.get("Writer")), "transport") .addException(TypeVariableName.get("TEx")) .addException(Cl.MALFORMED_DATA_EXCEPTION) .returns(isStatic ? TypeName.VOID : classType) .addCode(CodeBlock.builder().beginControlFlow("try ($1T reader = new $1T(serialized))", StringReader.class) .addStatement(readStatementT) .endControlFlow() .build()) .build() ); spec.addMethod( extension(MethodSpec.methodBuilder("deserialize")) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addParameter(Cl.GSON_ELEMENT, "tree") .addException(Cl.MALFORMED_DATA_EXCEPTION) .returns(isStatic ? TypeName.VOID : classType) .addCode(configureReader.apply(CodeBlock.builder().beginControlFlow("try ($1T reader = new $1T(tree))", Cl.EMULATED_READER)) .addStatement(readStatement) .endControlFlow() .build()) .build() ); spec.addMethod( extension(MethodSpec.methodBuilder("deserialize")) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addParameter(Path.class, "path") .addTypeVariable(TypeVariableName.get("TEx", Exception.class)) .addTypeVariable(TypeVariableName.get("Reader", ParameterizedTypeName.get(Cl.SERIALIZE_READER, TypeVariableName.get("TEx"), TypeVariableName.get("Reader")))) .addTypeVariable(TypeVariableName.get("Writer", ParameterizedTypeName.get(Cl.SERIALIZE_WRITER, TypeVariableName.get("TEx"), TypeVariableName.get("Writer")))) .addParameter(ParameterizedTypeName.get(Cl.TRANSPORT, TypeVariableName.get("TEx"), TypeVariableName.get("Reader"), TypeVariableName.get("Writer")), "transport") .addException(TypeVariableName.get("TEx")) .addException(Cl.MALFORMED_DATA_EXCEPTION) .addException(IOException.class) .returns(isStatic ? TypeName.VOID : classType) .addCode(CodeBlock.builder().beginControlFlow("try ($T reader = $T.newBufferedReader(path))", BufferedReader.class, Files.class) .addStatement(readStatementT) .endControlFlow() .build()) .build() ); spec.addMethod( extension(MethodSpec.methodBuilder("serialize"), isStatic ? null : classType) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addParameter(Writer.class, "out") .addTypeVariable(TypeVariableName.get("TEx", Exception.class)) .addTypeVariable(TypeVariableName.get("Reader", ParameterizedTypeName.get(Cl.SERIALIZE_READER, TypeVariableName.get("TEx"), TypeVariableName.get("Reader")))) .addTypeVariable(TypeVariableName.get("Writer", ParameterizedTypeName.get(Cl.SERIALIZE_WRITER, TypeVariableName.get("TEx"), TypeVariableName.get("Writer")))) .addParameter(ParameterizedTypeName.get(Cl.TRANSPORT, TypeVariableName.get("TEx"), TypeVariableName.get("Reader"), TypeVariableName.get("Writer")), "transport") .addException(TypeVariableName.get("TEx")) .addException(Cl.MALFORMED_DATA_EXCEPTION) .addCode(configureWriter.apply(CodeBlock.builder().beginControlFlow("transport.write(out, writer -> ", Cl.SERIALIZE_WRITER)) .addStatement(writeStatement) .addStatement("$<})") .build()) .build() ); spec.addMethod( extension(MethodSpec.methodBuilder("serialize"), isStatic ? null : classType) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addParameter(Path.class, "path") .addTypeVariable(TypeVariableName.get("TEx", Exception.class)) .addTypeVariable(TypeVariableName.get("Reader", ParameterizedTypeName.get(Cl.SERIALIZE_READER, TypeVariableName.get("TEx"), TypeVariableName.get("Reader")))) .addTypeVariable(TypeVariableName.get("Writer", ParameterizedTypeName.get(Cl.SERIALIZE_WRITER, TypeVariableName.get("TEx"), TypeVariableName.get("Writer")))) .addParameter(ParameterizedTypeName.get(Cl.TRANSPORT, TypeVariableName.get("TEx"), TypeVariableName.get("Reader"), TypeVariableName.get("Writer")), "transport") .addException(TypeVariableName.get("TEx")) .addException(Cl.MALFORMED_DATA_EXCEPTION) .addException(IOException.class) .addCode(disableSafe ? CodeBlock.builder().beginControlFlow("try ($1T writer = $2T.newBufferedWriter(path, $3T.CREATE, $3T.WRITE, $3T.TRUNCATE_EXISTING))", BufferedWriter.class, Files.class, StandardOpenOption.class) .addStatement(writeStatementT) .endControlFlow() .build() : CodeBlock.builder() .addStatement("$T temp = $T.createTempFile($S, $S)", Path.class, Files.class, "serializegenerator-", ".json") .beginControlFlow("try ($1T writer = $2T.newBufferedWriter(temp, $3T.CREATE, $3T.WRITE, $3T.TRUNCATE_EXISTING))", BufferedWriter.class, Files.class, StandardOpenOption.class) .addStatement(writeStatementT) .addStatement("$T.move(temp, path, $T.REPLACE_EXISTING)", Files.class, StandardCopyOption.class) .nextControlFlow("finally") .addStatement("$T.deleteIfExists(temp)", Files.class) .endControlFlow() .build()) .build() ); spec.addMethod( extension(MethodSpec.methodBuilder("serializeToString"), isStatic ? null : classType) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addTypeVariable(TypeVariableName.get("TEx", Exception.class)) .addTypeVariable(TypeVariableName.get("Reader", ParameterizedTypeName.get(Cl.SERIALIZE_READER, TypeVariableName.get("TEx"), TypeVariableName.get("Reader")))) .addTypeVariable(TypeVariableName.get("Writer", ParameterizedTypeName.get(Cl.SERIALIZE_WRITER, TypeVariableName.get("TEx"), TypeVariableName.get("Writer")))) .addParameter(ParameterizedTypeName.get(Cl.TRANSPORT, TypeVariableName.get("TEx"), TypeVariableName.get("Reader"), TypeVariableName.get("Writer")), "transport") .addException(TypeVariableName.get("TEx")) .addException(Cl.MALFORMED_DATA_EXCEPTION) .addException(IOException.class) .returns(String.class) .addCode("return transport.write(writer -> "+ writeStatement + ");") .build() ); spec.addMethod( extension(MethodSpec.methodBuilder("toDataTree"), isStatic ? null : classType) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addException(Cl.MALFORMED_DATA_EXCEPTION) .returns(Cl.GSON_ELEMENT) .addCode(configureWriter.apply(CodeBlock.builder().beginControlFlow("try ($1T writer = new $1T())", Cl.EMULATED_WRITER)) .addStatement(writeStatement) .addStatement("return writer.get()") .endControlFlow() .build()) .build() ); } }