java-commons/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/gprocessor/GProcessor.java

288 lines
18 KiB
Java

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.Properties;
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<TypeVariableName> typeVariables, Set<SerializableClass> otherAdapters) throws ElementException;
protected boolean isIgnored(Property<?> property) {
for (AnnotationMirror annotationMirror : property.getAnnotations()) {
if (annotationMirror.getAnnotationType().asElement().toString().equals(Cl.IGNORE.toString())) {
return true;
}
}
return false;
}
protected Property<?> getAlternative(Properties properties, Property<?> ignored) {
List<List<? extends Property<?>>> toSearch = List.of(properties.params, properties.getters, properties.setters, properties.fields);
for (List<? extends Property<?>> search : toSearch) {
Property<?> prop = Properties.findName(search, ignored);
if (prop == null || isIgnored(prop)) continue;
return prop;
}
return null;
}
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<CodeBlock.Builder> configureReader = cb -> {
if (configure != null) cb.addStatement("$T.configure(reader)", configure);
return cb;
};
final UnaryOperator<CodeBlock.Builder> 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()
);
}
}