Support for fully static classes
ci/woodpecker/push/woodpecker Pipeline was successful
Details
ci/woodpecker/push/woodpecker Pipeline was successful
Details
This commit is contained in:
parent
554fe8f8a7
commit
859605e06e
|
@ -24,4 +24,9 @@ public @interface GSerializable {
|
|||
* @return Whether to generate an adapter class to use with normal gson adapter resolution
|
||||
*/
|
||||
boolean generateAdapter() default false;
|
||||
|
||||
/**
|
||||
* @return Whether to serialize static fields/methods. Incompatible with generateAdapter and builder
|
||||
*/
|
||||
boolean isStatic() default false;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package io.gitlab.jfronny.gson;
|
||||
|
||||
import io.gitlab.jfronny.gson.compile.annotations.GSerializable;
|
||||
|
||||
@GSerializable(isStatic = true, configure = Main.Configuration.class)
|
||||
public class Static {
|
||||
public boolean nonStatic;
|
||||
public static boolean joe;
|
||||
}
|
|
@ -1,11 +1,35 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor.core;
|
||||
|
||||
import javax.annotation.processing.AbstractProcessor;
|
||||
import javax.annotation.processing.SupportedAnnotationTypes;
|
||||
import io.gitlab.jfronny.gson.compile.processor.core.value.ValueCreator;
|
||||
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.util.Elements;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public abstract class AbstractProcessor2 extends AbstractProcessor {
|
||||
protected Messager message;
|
||||
protected Filer filer;
|
||||
protected ValueCreator valueCreator;
|
||||
protected Elements elements;
|
||||
protected boolean hasManifold = false;
|
||||
|
||||
@Override
|
||||
public synchronized void init(ProcessingEnvironment processingEnv) {
|
||||
super.init(processingEnv);
|
||||
try {
|
||||
Class.forName("manifold.ext.Model");
|
||||
System.out.println("Detected manifold!");
|
||||
hasManifold = true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
hasManifold = false;
|
||||
}
|
||||
message = processingEnv.messager;
|
||||
filer = processingEnv.filer;
|
||||
elements = processingEnv.elementUtils;
|
||||
valueCreator = new ValueCreator(processingEnv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getSupportedAnnotationTypes() {
|
||||
return Optional.ofNullable(this.getClass().getAnnotation(SupportedAnnotationTypes.class))
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor;
|
||||
package io.gitlab.jfronny.gson.compile.processor.core;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
|
@ -66,7 +66,7 @@ public class TypeHelper {
|
|||
return false;
|
||||
}
|
||||
|
||||
static String getDefaultValue(TypeMirror type) {
|
||||
public static String getDefaultValue(TypeMirror type) {
|
||||
return switch (type.kind) {
|
||||
case BYTE, SHORT, INT, LONG, FLOAT, CHAR, DOUBLE -> "0";
|
||||
case BOOLEAN -> "false";
|
|
@ -11,7 +11,7 @@ import java.util.Locale;
|
|||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* How a [Value] can be constructed. Either a constructor, factory method, or builder.
|
||||
* How a [Value] can be constructed. Either a constructor, factory method, builder or none (static fields only)
|
||||
*/
|
||||
public sealed interface ConstructionSource {
|
||||
/**
|
||||
|
@ -31,6 +31,11 @@ public sealed interface ConstructionSource {
|
|||
*/
|
||||
boolean isBuilder();
|
||||
|
||||
/**
|
||||
* If this source is static
|
||||
*/
|
||||
boolean isStatic();
|
||||
|
||||
final class Constructor implements ConstructionSource {
|
||||
private final ExecutableElement constructor;
|
||||
private TypeElement targetClass;
|
||||
|
@ -58,6 +63,11 @@ public sealed interface ConstructionSource {
|
|||
public boolean isBuilder() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStatic() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final class Factory implements ConstructionSource {
|
||||
|
@ -89,6 +99,11 @@ public sealed interface ConstructionSource {
|
|||
public boolean isBuilder() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStatic() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
sealed abstract class Builder implements ConstructionSource {
|
||||
|
@ -99,6 +114,11 @@ public sealed interface ConstructionSource {
|
|||
public boolean isBuilder() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStatic() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final class BuilderConstructor extends Builder {
|
||||
|
@ -177,6 +197,39 @@ public sealed interface ConstructionSource {
|
|||
}
|
||||
}
|
||||
|
||||
final class Static implements ConstructionSource {
|
||||
private final TypeElement targetClass;
|
||||
|
||||
public Static(TypeElement targetClass) {
|
||||
this.targetClass = targetClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeElement getTargetClass() {
|
||||
return targetClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableElement getConstructionElement() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConstructor() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBuilder() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStatic() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
static ExecutableElement findBuildMethod(TypeElement builderClass) {
|
||||
// Ok, maybe there is just one possible builder method.
|
||||
|
|
|
@ -18,11 +18,13 @@ public class Properties extends DelegateList.Simple<Property<?>> {
|
|||
public final List<Property.ConstructorParam> constructorParams;
|
||||
public final List<Property.Setter> builderParams;
|
||||
|
||||
public static Properties build(Types types, ConstructionSource constructionSource) throws ElementException {
|
||||
Builder builder = new Builder(types);
|
||||
public static Properties build(Types types, ConstructionSource constructionSource, boolean isStatic) throws ElementException {
|
||||
Builder builder = new Builder(types, isStatic);
|
||||
// constructor params
|
||||
for (VariableElement param : constructionSource.constructionElement.parameters) {
|
||||
builder.addConstructorParam(param);
|
||||
if (constructionSource.constructionElement != null) {
|
||||
for (VariableElement param : constructionSource.constructionElement.parameters) {
|
||||
builder.addConstructorParam(param);
|
||||
}
|
||||
}
|
||||
|
||||
if (constructionSource instanceof ConstructionSource.Builder csb) {
|
||||
|
@ -75,6 +77,7 @@ public class Properties extends DelegateList.Simple<Property<?>> {
|
|||
private static class Builder {
|
||||
private static final Set<String> METHODS_TO_SKIP = Set.of("hashCode", "toString", "clone");
|
||||
|
||||
private final boolean isStatic;
|
||||
private final Types types;
|
||||
public final List<Property<?>> names = new ArrayList<>();
|
||||
public final List<Property.Param> params = new ArrayList<>();
|
||||
|
@ -84,8 +87,9 @@ public class Properties extends DelegateList.Simple<Property<?>> {
|
|||
public final List<Property.ConstructorParam> constructorParams = new ArrayList<>();
|
||||
public final List<Property.Setter> builderParams = new ArrayList<>();
|
||||
|
||||
public Builder(Types types) {
|
||||
public Builder(Types types, boolean isStatic) {
|
||||
this.types = types;
|
||||
this.isStatic = isStatic;
|
||||
}
|
||||
|
||||
public void addFieldsAndAccessors(TypeElement targetClass) {
|
||||
|
@ -113,7 +117,7 @@ public class Properties extends DelegateList.Simple<Property<?>> {
|
|||
public void addGetter(TypeElement classElement, ExecutableElement method) {
|
||||
Set<Modifier> modifiers = method.modifiers;
|
||||
if (modifiers.contains(Modifier.PRIVATE)
|
||||
|| modifiers.contains(Modifier.STATIC)
|
||||
|| (isStatic != modifiers.contains(Modifier.STATIC))
|
||||
|| method.returnType.kind == TypeKind.VOID
|
||||
|| !method.parameters.isEmpty
|
||||
|| isMethodToSkip(classElement, method)) {
|
||||
|
@ -125,7 +129,7 @@ public class Properties extends DelegateList.Simple<Property<?>> {
|
|||
public void addSetter(TypeElement classElement, ExecutableElement method) {
|
||||
Set<Modifier> modifiers = method.modifiers;
|
||||
if (modifiers.contains(Modifier.PRIVATE)
|
||||
|| modifiers.contains(Modifier.STATIC)
|
||||
|| (isStatic != modifiers.contains(Modifier.STATIC))
|
||||
|| method.returnType.kind != TypeKind.VOID
|
||||
|| method.parameters.size() != 1
|
||||
|| isMethodToSkip(classElement, method)
|
||||
|
@ -137,7 +141,7 @@ public class Properties extends DelegateList.Simple<Property<?>> {
|
|||
|
||||
public void addField(VariableElement field) {
|
||||
Set<Modifier> modifiers = field.modifiers;
|
||||
if (modifiers.contains(Modifier.STATIC)) return;
|
||||
if (isStatic != modifiers.contains(Modifier.STATIC)) return;
|
||||
fields.add(new Property.Field(field));
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ public class Value {
|
|||
}
|
||||
|
||||
public Properties getProperties() throws ElementException {
|
||||
return properties != null ? properties : (properties = Properties.build(env.typeUtils, constructionSource));
|
||||
return properties != null ? properties : (properties = Properties.build(env.typeUtils, constructionSource, constructionSource.isStatic));
|
||||
}
|
||||
|
||||
public ConstructionSource getConstructionSource() {
|
||||
|
|
|
@ -31,6 +31,10 @@ public class ValueCreator {
|
|||
} else throw new IllegalArgumentException("Expected TypeElement or ExecutableElement but got: " + element);
|
||||
}
|
||||
|
||||
public Value fromStatic(TypeElement element) throws ElementException {
|
||||
return create(new ConstructionSource.Static(element));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [Value] from the given constructor element. ex:
|
||||
* ```
|
||||
|
|
|
@ -7,50 +7,37 @@ import io.gitlab.jfronny.gson.compile.processor.adapter.Adapter;
|
|||
import io.gitlab.jfronny.gson.compile.processor.adapter.Adapters;
|
||||
import io.gitlab.jfronny.gson.compile.processor.core.AbstractProcessor2;
|
||||
import io.gitlab.jfronny.gson.compile.processor.core.SupportedAnnotationTypes2;
|
||||
import io.gitlab.jfronny.gson.compile.processor.core.value.Properties;
|
||||
import io.gitlab.jfronny.gson.compile.processor.core.value.*;
|
||||
import io.gitlab.jfronny.gson.compile.processor.core.value.ElementException;
|
||||
import io.gitlab.jfronny.gson.compile.processor.gprocessor.*;
|
||||
import io.gitlab.jfronny.gson.compile.processor.util.Cl;
|
||||
import io.gitlab.jfronny.gson.compile.processor.util.StringListComparator;
|
||||
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.tools.Diagnostic;
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_17)
|
||||
@SupportedAnnotationTypes2({GSerializable.class})
|
||||
@SupportedOptions({"gsonCompileNoReflect"})
|
||||
public class GsonCompileProcessor extends AbstractProcessor2 {
|
||||
private Messager message;
|
||||
private Filer filer;
|
||||
private Map<ClassName, TypeSpec.Builder> seen;
|
||||
private ValueCreator valueCreator;
|
||||
private Elements elements;
|
||||
private boolean hasManifold = false;
|
||||
private InstanceProcessor instanceProcessor;
|
||||
private StaticProcessor staticProcessor;
|
||||
|
||||
@Override
|
||||
public synchronized void init(ProcessingEnvironment processingEnv) {
|
||||
super.init(processingEnv);
|
||||
try {
|
||||
Class.forName("manifold.ext.Model");
|
||||
System.out.println("Detected manifold!");
|
||||
hasManifold = true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
hasManifold = false;
|
||||
}
|
||||
message = processingEnv.messager;
|
||||
filer = processingEnv.filer;
|
||||
elements = processingEnv.elementUtils;
|
||||
seen = new LinkedHashMap<>();
|
||||
valueCreator = new ValueCreator(processingEnv);
|
||||
for (Adapter adapter : Adapters.ADAPTERS) {
|
||||
adapter.init(processingEnv);
|
||||
}
|
||||
instanceProcessor = new InstanceProcessor(valueCreator, message, hasManifold);
|
||||
staticProcessor = new StaticProcessor(valueCreator, message, hasManifold);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -67,6 +54,7 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
|
|||
TypeMirror builder = null;
|
||||
TypeMirror configure = null;
|
||||
Boolean generateAdapter = null;
|
||||
Boolean isStatic = null;
|
||||
};
|
||||
elements.getElementValuesWithDefaults(mirror).forEach((executableElement, value) -> {
|
||||
String name = executableElement.simpleName.toString();
|
||||
|
@ -87,6 +75,10 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
|
|||
if (bld.generateAdapter != null) throw new IllegalArgumentException("Duplicate annotation parameter: generateAdapter");
|
||||
bld.generateAdapter = (Boolean) value.value;
|
||||
}
|
||||
case "isStatic" -> {
|
||||
if (bld.isStatic != null) throw new IllegalArgumentException("Duplicate annotation parameter: isStatic");
|
||||
bld.isStatic = (Boolean) value.value;
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Unexpected annotation parameter: " + name);
|
||||
}
|
||||
});
|
||||
|
@ -94,8 +86,9 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
|
|||
if (bld.builder == null) throw new IllegalArgumentException("Missing annotation parameter: builder");
|
||||
if (bld.configure == null) throw new IllegalArgumentException("Missing annotation parameter: configure");
|
||||
if (bld.generateAdapter == null) throw new IllegalArgumentException("Missing annotation parameter: generateAdapter");
|
||||
if (bld.isStatic == null) throw new IllegalArgumentException("Missing annotation parameter: isStatic");
|
||||
|
||||
toGenerate.add(SerializableClass.of((TypeElement) element, bld.with, bld.builder, bld.configure, bld.generateAdapter, hasManifold));
|
||||
toGenerate.add(SerializableClass.of((TypeElement) element, bld.with, bld.builder, bld.configure, bld.generateAdapter, bld.isStatic, hasManifold));
|
||||
}
|
||||
} catch (ElementException e) {
|
||||
e.printMessage(message);
|
||||
|
@ -177,7 +170,7 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
|
|||
|
||||
TypeName classType = toProcess.typeName;
|
||||
List<TypeVariableName> typeVariables = new ArrayList<>();
|
||||
if (classType instanceof ParameterizedTypeName type) {
|
||||
if (!toProcess.isStatic && classType instanceof ParameterizedTypeName type) {
|
||||
for (TypeName argument : type.typeArguments) {
|
||||
typeVariables.add(TypeVariableName.get(argument.toString()));
|
||||
}
|
||||
|
@ -190,361 +183,17 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
|
|||
|
||||
seen.put(toProcess.generatedClassName, spec);
|
||||
|
||||
GProcessor processor = toProcess.isStatic ? staticProcessor : instanceProcessor;
|
||||
|
||||
if (toProcess.adapter != null) {
|
||||
generateDelegateToAdapter(spec, classType, toProcess.adapter);
|
||||
processor.generateDelegateToAdapter(spec, classType, toProcess.adapter);
|
||||
} else {
|
||||
if (toProcess.generateAdapter) {
|
||||
generateDelegatingAdapter(spec, classType, toProcess.generatedClassName);
|
||||
processor.generateDelegatingAdapter(spec, classType, toProcess.generatedClassName);
|
||||
}
|
||||
generateSerialisation(spec, toProcess, typeVariables, other);
|
||||
processor.generateSerialisation(spec, toProcess, typeVariables, other);
|
||||
}
|
||||
|
||||
generateAuxiliary(spec, classType, toProcess.configure());
|
||||
}
|
||||
|
||||
private static 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()
|
||||
);
|
||||
}
|
||||
|
||||
private void generateDelegateToAdapter(TypeSpec.Builder spec, TypeName classType, TypeMirror adapter) {
|
||||
spec.addMethod(
|
||||
extension(MethodSpec.methodBuilder("read"))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(Cl.GSON_READER, "reader")
|
||||
.addException(IOException.class)
|
||||
.returns(classType)
|
||||
.addCode("return $T.read(reader);", adapter)
|
||||
.build()
|
||||
);
|
||||
spec.addMethod(
|
||||
extension(MethodSpec.methodBuilder("write"), classType)
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(Cl.GSON_WRITER, "writer")
|
||||
.addException(IOException.class)
|
||||
.addCode("$T.write(writer, value);", adapter)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
private 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;
|
||||
};
|
||||
|
||||
final String readStatement = "return read(reader)";
|
||||
final String writeStatement = "write(value, writer)";
|
||||
|
||||
spec.addMethod(
|
||||
extension(MethodSpec.methodBuilder("read"))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(TypeName.get(Reader.class), "in")
|
||||
.addException(IOException.class)
|
||||
.returns(classType)
|
||||
.addCode(configureReader.apply(CodeBlock.builder().beginControlFlow("try ($1T reader = new $1T(in))", Cl.GSON_READER))
|
||||
.addStatement(readStatement)
|
||||
.endControlFlow()
|
||||
.build())
|
||||
.build()
|
||||
);
|
||||
|
||||
spec.addMethod(
|
||||
extension(MethodSpec.methodBuilder("read"))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(TypeName.get(String.class), "json")
|
||||
.addException(IOException.class)
|
||||
.returns(classType)
|
||||
.addCode(CodeBlock.builder().beginControlFlow("try ($1T reader = new $1T(json))", StringReader.class)
|
||||
.addStatement(readStatement)
|
||||
.endControlFlow()
|
||||
.build())
|
||||
.build()
|
||||
);
|
||||
|
||||
spec.addMethod(
|
||||
extension(MethodSpec.methodBuilder("read"))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(Cl.GSON_ELEMENT, "tree")
|
||||
.addException(IOException.class)
|
||||
.returns(classType)
|
||||
.addCode(configureReader.apply(CodeBlock.builder().beginControlFlow("try ($1T reader = new $1T(tree))", Cl.GSON_TREE_READER))
|
||||
.addStatement(readStatement)
|
||||
.endControlFlow()
|
||||
.build())
|
||||
.build()
|
||||
);
|
||||
|
||||
spec.addMethod(
|
||||
extension(MethodSpec.methodBuilder("read"))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(Path.class, "path")
|
||||
.addException(IOException.class)
|
||||
.returns(classType)
|
||||
.addCode(CodeBlock.builder().beginControlFlow("try ($T reader = $T.newBufferedReader(path))", BufferedReader.class, Files.class)
|
||||
.addStatement(readStatement)
|
||||
.endControlFlow()
|
||||
.build())
|
||||
.build()
|
||||
);
|
||||
|
||||
spec.addMethod(
|
||||
extension(MethodSpec.methodBuilder("write"), classType)
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(Writer.class, "out")
|
||||
.addException(IOException.class)
|
||||
.addCode(configureWriter.apply(CodeBlock.builder().beginControlFlow("try ($1T writer = new $1T(out))", Cl.GSON_WRITER))
|
||||
.addStatement(writeStatement)
|
||||
.endControlFlow()
|
||||
.build())
|
||||
.build()
|
||||
);
|
||||
|
||||
spec.addMethod(
|
||||
extension(MethodSpec.methodBuilder("write"), classType)
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(Path.class, "path")
|
||||
.addException(IOException.class)
|
||||
.addCode(CodeBlock.builder().beginControlFlow("try ($1T writer = $2T.newBufferedWriter(path, $3T.CREATE, $3T.WRITE, $3T.TRUNCATE_EXISTING))", BufferedWriter.class, Files.class, StandardOpenOption.class)
|
||||
.addStatement(writeStatement)
|
||||
.endControlFlow()
|
||||
.build())
|
||||
.build()
|
||||
);
|
||||
|
||||
spec.addMethod(
|
||||
extension(MethodSpec.methodBuilder("toJson"), classType)
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addException(IOException.class)
|
||||
.returns(String.class)
|
||||
.addCode(CodeBlock.builder().beginControlFlow("try ($1T writer = new $1T())", StringWriter.class)
|
||||
.addStatement(writeStatement)
|
||||
.addStatement("return writer.toString()")
|
||||
.endControlFlow()
|
||||
.build())
|
||||
.build()
|
||||
);
|
||||
|
||||
spec.addMethod(
|
||||
extension(MethodSpec.methodBuilder("toJsonTree"), classType)
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addException(IOException.class)
|
||||
.returns(Cl.GSON_ELEMENT)
|
||||
.addCode(configureWriter.apply(CodeBlock.builder().beginControlFlow("try ($1T writer = new $1T())", Cl.GSON_TREE_WRITER))
|
||||
.addStatement(writeStatement)
|
||||
.addStatement("return writer.get()")
|
||||
.endControlFlow()
|
||||
.build())
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
private 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.constructionSource;
|
||||
Properties properties = value.properties;
|
||||
|
||||
// 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.callableName);
|
||||
if (param.type.kind.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.callableName);
|
||||
generateComments(param, code);
|
||||
code.addStatement("writer.name($S)", getSerializedName(param));
|
||||
code.addStatement("if (value.$N == null) writer.nullValue()", param.callableName);
|
||||
code.beginControlFlow("else");
|
||||
Adapters.generateWrite(param, spec, code, typeVariables, otherAdapters, message, writeGet);
|
||||
code.endControlFlow();
|
||||
code.endControlFlow();
|
||||
}
|
||||
}
|
||||
for (Property.Getter param : properties.getters) {
|
||||
if (param.type.kind.isPrimitive) {
|
||||
generateComments(param, code);
|
||||
code.addStatement("writer.name($S)", getSerializedName(param));
|
||||
Adapters.generateWrite(param, spec, code, typeVariables, otherAdapters, message, () -> code.add("value.$N()", param.callableName));
|
||||
} else {
|
||||
code.addStatement("$T $L$N = value.$N()", param.type, "$", param.callableName, param.callableName);
|
||||
code.beginControlFlow("if ($L$N != null || writer.getSerializeNulls())", "$", param.callableName);
|
||||
generateComments(param, code);
|
||||
code.addStatement("writer.name($S)", getSerializedName(param));
|
||||
code.addStatement("if ($L$N == null) writer.nullValue()", "$", param.callableName);
|
||||
code.beginControlFlow("else");
|
||||
Adapters.generateWrite(param, spec, code, typeVariables, otherAdapters, message, () -> code.add("$L$N", "$", param.callableName));
|
||||
code.endControlFlow();
|
||||
code.endControlFlow();
|
||||
}
|
||||
}
|
||||
code.addStatement("writer.endObject()");
|
||||
|
||||
spec.addMethod(extension(MethodSpec.methodBuilder("write"), self.typeName)
|
||||
.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.type, param.name, TypeHelper.getDefaultValue(param.type));
|
||||
}
|
||||
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.type.kind.isPrimitive) {
|
||||
code.add("case $S -> _$N = ", getSerializedName(param), param.name);
|
||||
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.name);
|
||||
code.unindent().add("} else _$N = ", param.name);
|
||||
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.typeName);
|
||||
ClassName creatorName = ClassName.get((TypeElement) constructionSource.constructionElement.enclosingElement);
|
||||
if (constructionSource instanceof ConstructionSource.Builder builder) {
|
||||
StringBuilder args = new StringBuilder();
|
||||
for (Property.ConstructorParam param : properties.constructorParams) {
|
||||
args.append(", _").append(param.name);
|
||||
}
|
||||
code.add("$T builder = ", builder.builderClass);
|
||||
if (constructionSource.isConstructor) {
|
||||
code.add("new $T($L)", builder.builderClass, args.length() > 0 ? args.substring(2) : "");
|
||||
} else {
|
||||
code.add("$T.$N($L)", creatorName, self.classElement.simpleName, args.length() > 0 ? args.substring(2) : "");
|
||||
}
|
||||
code.add(";\n");
|
||||
for (Property.Setter param : properties.builderParams) {
|
||||
code.addStatement("builder.$N(_$N)", param.callableName, param.name);
|
||||
}
|
||||
code.addStatement("result = builder.$N()", builder.buildMethod.simpleName);
|
||||
} else {
|
||||
StringBuilder args = new StringBuilder();
|
||||
for (Property.Param param : properties.params) {
|
||||
args.append(", _").append(param.name);
|
||||
}
|
||||
if (constructionSource.isConstructor) {
|
||||
code.addStatement("result = new $T($L)", self.typeName, args.length() > 0 ? args.substring(2) : "");
|
||||
} else {
|
||||
code.addStatement("result = $T.$N($L)", creatorName, constructionSource.constructionElement.simpleName, args.length() > 0 ? args.substring(2) : "");
|
||||
}
|
||||
}
|
||||
for (Property.Setter setter : properties.setters) {
|
||||
code.addStatement("result.$N(_$N)", setter.callableName, setter.name);
|
||||
}
|
||||
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.name, field.callableName);
|
||||
}
|
||||
code.addStatement("return result");
|
||||
|
||||
spec.addMethod(extension(MethodSpec.methodBuilder("read"))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.returns(self.typeName)
|
||||
.addParameter(Cl.GSON_READER, "reader")
|
||||
.addException(IOException.class)
|
||||
.addCode(code.build())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
private void generateComments(Property<?> prop, CodeBlock.Builder code) {
|
||||
for (AnnotationMirror annotation : prop.annotations) {
|
||||
if (annotation.annotationType.asElement().toString().equals(Cl.GCOMMENT.toString())) {
|
||||
String comment = (String) annotation.elementValues.values().iterator().next().value;
|
||||
code.addStatement("if (writer.isLenient()) writer.comment($S)", comment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getSerializedName(Property<?> property) {
|
||||
for (AnnotationMirror annotationMirror : property.annotations) {
|
||||
if (annotationMirror.annotationType.asElement().toString().equals(Cl.SERIALIZED_NAME.toString())) {
|
||||
return (String) annotationMirror.elementValues.values().iterator().next().value;
|
||||
}
|
||||
}
|
||||
return property.name;
|
||||
}
|
||||
|
||||
private MethodSpec.Builder extension(MethodSpec.Builder method) {
|
||||
if (hasManifold) method.addAnnotation(Cl.MANIFOLD_EXTENSION);
|
||||
return method;
|
||||
}
|
||||
|
||||
private MethodSpec.Builder extension(MethodSpec.Builder method, TypeName thizName) {
|
||||
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;
|
||||
processor.generateAuxiliary(spec, classType, toProcess.configure());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,12 +8,12 @@ import org.jetbrains.annotations.Nullable;
|
|||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
public record SerializableClass(TypeElement classElement, ClassName generatedClassName, @Nullable TypeMirror adapter, @Nullable TypeMirror builder, @Nullable TypeMirror configure, boolean generateAdapter) {
|
||||
public static SerializableClass of(TypeElement element, @Nullable TypeMirror with, @Nullable TypeMirror builder, @Nullable TypeMirror configure, boolean generateAdapter, boolean manifold) throws ElementException {
|
||||
public record SerializableClass(TypeElement classElement, ClassName generatedClassName, @Nullable TypeMirror adapter, @Nullable TypeMirror builder, @Nullable TypeMirror configure, boolean generateAdapter, boolean isStatic) {
|
||||
public static SerializableClass of(TypeElement element, @Nullable TypeMirror with, @Nullable TypeMirror builder, @Nullable TypeMirror configure, boolean generateAdapter, boolean isStatic, boolean manifold) throws ElementException {
|
||||
ClassName className = ClassName.get(element);
|
||||
String pkg = manifold ? "gsoncompile.extensions." + className.packageName + '.' + className.simpleNames[0] : className.packageName;
|
||||
ClassName generatedClassName = ClassName.get(pkg, "GC_" + className.simpleNames[0], className.simpleNames.subList(1, className.simpleNames.size()).toArray(String[]::new));
|
||||
return new SerializableClass(element, generatedClassName, voidToNull(with), voidToNull(builder), voidToNull(configure), generateAdapter).validate();
|
||||
return new SerializableClass(element, generatedClassName, voidToNull(with), voidToNull(builder), voidToNull(configure), generateAdapter, isStatic).validate();
|
||||
}
|
||||
|
||||
public ClassName getClassName() {
|
||||
|
@ -25,7 +25,9 @@ public record SerializableClass(TypeElement classElement, ClassName generatedCla
|
|||
}
|
||||
|
||||
private SerializableClass validate() throws ElementException {
|
||||
if (adapter != null && builder != null) throw new ElementException("@Serializable with both an adapter and a builder. This is unsupported!", classElement);
|
||||
if (adapter != null && builder != null) throw new ElementException("@GSerializable with both an adapter and a builder. This is unsupported!", classElement);
|
||||
if (isStatic && builder != null) throw new ElementException("@GSerializable which is static and has a builder. This is unsupported!", classElement);
|
||||
if (isStatic && generateAdapter) throw new ElementException("@GSerializable which is static and generates an adapter. This is unsupported!", classElement);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor.adapter;
|
||||
|
||||
import com.squareup.javapoet.*;
|
||||
import io.gitlab.jfronny.gson.compile.processor.Cl;
|
||||
import io.gitlab.jfronny.gson.compile.processor.TypeHelper;
|
||||
import io.gitlab.jfronny.gson.compile.processor.util.Cl;
|
||||
import io.gitlab.jfronny.gson.compile.processor.core.TypeHelper;
|
||||
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor.adapter.impl;
|
||||
|
||||
import com.squareup.javapoet.*;
|
||||
import io.gitlab.jfronny.gson.compile.processor.Cl;
|
||||
import io.gitlab.jfronny.gson.compile.processor.TypeHelper;
|
||||
import io.gitlab.jfronny.gson.compile.processor.util.Cl;
|
||||
import io.gitlab.jfronny.gson.compile.processor.core.TypeHelper;
|
||||
import io.gitlab.jfronny.gson.compile.processor.adapter.Adapter;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
|
|
@ -2,8 +2,8 @@ package io.gitlab.jfronny.gson.compile.processor.adapter.impl;
|
|||
|
||||
import com.squareup.javapoet.*;
|
||||
import io.gitlab.jfronny.commons.data.MutCollection;
|
||||
import io.gitlab.jfronny.gson.compile.processor.Cl;
|
||||
import io.gitlab.jfronny.gson.compile.processor.TypeHelper;
|
||||
import io.gitlab.jfronny.gson.compile.processor.util.Cl;
|
||||
import io.gitlab.jfronny.gson.compile.processor.core.TypeHelper;
|
||||
import io.gitlab.jfronny.gson.compile.processor.adapter.Adapter;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
|
|
@ -2,7 +2,7 @@ package io.gitlab.jfronny.gson.compile.processor.adapter.impl;
|
|||
|
||||
import com.squareup.javapoet.CodeBlock;
|
||||
import com.squareup.javapoet.MethodSpec;
|
||||
import io.gitlab.jfronny.gson.compile.processor.Cl;
|
||||
import io.gitlab.jfronny.gson.compile.processor.util.Cl;
|
||||
import io.gitlab.jfronny.gson.compile.processor.adapter.Adapter;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor.adapter.impl;
|
||||
|
||||
import com.squareup.javapoet.*;
|
||||
import io.gitlab.jfronny.gson.compile.processor.Cl;
|
||||
import io.gitlab.jfronny.gson.compile.processor.TypeHelper;
|
||||
import io.gitlab.jfronny.gson.compile.processor.util.Cl;
|
||||
import io.gitlab.jfronny.gson.compile.processor.core.TypeHelper;
|
||||
import io.gitlab.jfronny.gson.compile.processor.adapter.AdapterAdapter;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor.adapter.impl;
|
||||
|
||||
import com.squareup.javapoet.*;
|
||||
import io.gitlab.jfronny.gson.compile.processor.TypeHelper;
|
||||
import io.gitlab.jfronny.gson.compile.processor.core.TypeHelper;
|
||||
import io.gitlab.jfronny.gson.compile.processor.adapter.Adapter;
|
||||
|
||||
import javax.lang.model.element.ElementKind;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor.adapter.impl;
|
||||
|
||||
import com.squareup.javapoet.*;
|
||||
import io.gitlab.jfronny.gson.compile.processor.Cl;
|
||||
import io.gitlab.jfronny.gson.compile.processor.TypeHelper;
|
||||
import io.gitlab.jfronny.gson.compile.processor.util.Cl;
|
||||
import io.gitlab.jfronny.gson.compile.processor.core.TypeHelper;
|
||||
import io.gitlab.jfronny.gson.compile.processor.adapter.Adapter;
|
||||
|
||||
import javax.lang.model.element.ElementKind;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor.adapter.impl;
|
||||
|
||||
import com.squareup.javapoet.*;
|
||||
import io.gitlab.jfronny.gson.compile.processor.Cl;
|
||||
import io.gitlab.jfronny.gson.compile.processor.util.Cl;
|
||||
import io.gitlab.jfronny.gson.compile.processor.adapter.AdapterAdapter;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
|
|
@ -0,0 +1,207 @@
|
|||
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.core.value.*;
|
||||
import io.gitlab.jfronny.gson.compile.processor.util.Cl;
|
||||
|
||||
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.*;
|
||||
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 writeStatement;
|
||||
|
||||
public GProcessor(ValueCreator valueCreator, Messager message, boolean hasManifold, boolean isStatic) {
|
||||
this.valueCreator = valueCreator;
|
||||
this.message = message;
|
||||
this.hasManifold = hasManifold;
|
||||
|
||||
this.isStatic = isStatic;
|
||||
this.readStatement = isStatic ? "read(reader)" : "return read(reader)";
|
||||
this.writeStatement = isStatic ? "write(writer)" : "write(value, writer)";
|
||||
}
|
||||
|
||||
public abstract void 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 String getSerializedName(Property<?> property) {
|
||||
for (AnnotationMirror annotationMirror : property.annotations) {
|
||||
if (annotationMirror.annotationType.asElement().toString().equals(Cl.SERIALIZED_NAME.toString())) {
|
||||
return (String) annotationMirror.elementValues.values().iterator().next().value;
|
||||
}
|
||||
}
|
||||
return property.name;
|
||||
}
|
||||
|
||||
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.annotations) {
|
||||
if (annotation.annotationType.asElement().toString().equals(Cl.GCOMMENT.toString())) {
|
||||
String comment = (String) annotation.elementValues.values().iterator().next().value;
|
||||
code.addStatement("if (writer.isLenient()) writer.comment($S)", comment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void generateDelegateToAdapter(TypeSpec.Builder spec, TypeName classType, TypeMirror adapter) {
|
||||
spec.addMethod(
|
||||
extension(MethodSpec.methodBuilder("read"))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(Cl.GSON_READER, "reader")
|
||||
.addException(IOException.class)
|
||||
.returns(isStatic ? TypeName.VOID : classType)
|
||||
.addCode(isStatic ? "$T.read(reader);" : "return $T.read(reader);", adapter)
|
||||
.build()
|
||||
);
|
||||
spec.addMethod(
|
||||
extension(MethodSpec.methodBuilder("write"), isStatic ? null : classType)
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(Cl.GSON_WRITER, "writer")
|
||||
.addException(IOException.class)
|
||||
.addCode("$T.$L;", 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("read"))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(TypeName.get(Reader.class), "in")
|
||||
.addException(IOException.class)
|
||||
.returns(isStatic ? TypeName.VOID : classType)
|
||||
.addCode(configureReader.apply(CodeBlock.builder().beginControlFlow("try ($1T reader = new $1T(in))", Cl.GSON_READER))
|
||||
.addStatement(readStatement)
|
||||
.endControlFlow()
|
||||
.build())
|
||||
.build()
|
||||
);
|
||||
|
||||
spec.addMethod(
|
||||
extension(MethodSpec.methodBuilder("read"))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(TypeName.get(String.class), "json")
|
||||
.addException(IOException.class)
|
||||
.returns(isStatic ? TypeName.VOID : classType)
|
||||
.addCode(CodeBlock.builder().beginControlFlow("try ($1T reader = new $1T(json))", StringReader.class)
|
||||
.addStatement(readStatement)
|
||||
.endControlFlow()
|
||||
.build())
|
||||
.build()
|
||||
);
|
||||
|
||||
spec.addMethod(
|
||||
extension(MethodSpec.methodBuilder("read"))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(Cl.GSON_ELEMENT, "tree")
|
||||
.addException(IOException.class)
|
||||
.returns(isStatic ? TypeName.VOID : classType)
|
||||
.addCode(configureReader.apply(CodeBlock.builder().beginControlFlow("try ($1T reader = new $1T(tree))", Cl.GSON_TREE_READER))
|
||||
.addStatement(readStatement)
|
||||
.endControlFlow()
|
||||
.build())
|
||||
.build()
|
||||
);
|
||||
|
||||
spec.addMethod(
|
||||
extension(MethodSpec.methodBuilder("read"))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(Path.class, "path")
|
||||
.addException(IOException.class)
|
||||
.returns(isStatic ? TypeName.VOID : classType)
|
||||
.addCode(CodeBlock.builder().beginControlFlow("try ($T reader = $T.newBufferedReader(path))", BufferedReader.class, Files.class)
|
||||
.addStatement(readStatement)
|
||||
.endControlFlow()
|
||||
.build())
|
||||
.build()
|
||||
);
|
||||
|
||||
spec.addMethod(
|
||||
extension(MethodSpec.methodBuilder("write"), isStatic ? null : classType)
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(Writer.class, "out")
|
||||
.addException(IOException.class)
|
||||
.addCode(configureWriter.apply(CodeBlock.builder().beginControlFlow("try ($1T writer = new $1T(out))", Cl.GSON_WRITER))
|
||||
.addStatement(writeStatement)
|
||||
.endControlFlow()
|
||||
.build())
|
||||
.build()
|
||||
);
|
||||
|
||||
spec.addMethod(
|
||||
extension(MethodSpec.methodBuilder("write"), isStatic ? null : classType)
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(Path.class, "path")
|
||||
.addException(IOException.class)
|
||||
.addCode(CodeBlock.builder().beginControlFlow("try ($1T writer = $2T.newBufferedWriter(path, $3T.CREATE, $3T.WRITE, $3T.TRUNCATE_EXISTING))", BufferedWriter.class, Files.class, StandardOpenOption.class)
|
||||
.addStatement(writeStatement)
|
||||
.endControlFlow()
|
||||
.build())
|
||||
.build()
|
||||
);
|
||||
|
||||
spec.addMethod(
|
||||
extension(MethodSpec.methodBuilder("toJson"), isStatic ? null : classType)
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addException(IOException.class)
|
||||
.returns(String.class)
|
||||
.addCode(CodeBlock.builder().beginControlFlow("try ($1T writer = new $1T())", StringWriter.class)
|
||||
.addStatement(writeStatement)
|
||||
.addStatement("return writer.toString()")
|
||||
.endControlFlow()
|
||||
.build())
|
||||
.build()
|
||||
);
|
||||
|
||||
spec.addMethod(
|
||||
extension(MethodSpec.methodBuilder("toJsonTree"), isStatic ? null : classType)
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addException(IOException.class)
|
||||
.returns(Cl.GSON_ELEMENT)
|
||||
.addCode(configureWriter.apply(CodeBlock.builder().beginControlFlow("try ($1T writer = new $1T())", Cl.GSON_TREE_WRITER))
|
||||
.addStatement(writeStatement)
|
||||
.addStatement("return writer.get()")
|
||||
.endControlFlow()
|
||||
.build())
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
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.util.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.constructionSource;
|
||||
Properties properties = value.properties;
|
||||
|
||||
// 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.callableName);
|
||||
if (param.type.kind.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.callableName);
|
||||
generateComments(param, code);
|
||||
code.addStatement("writer.name($S)", getSerializedName(param));
|
||||
code.addStatement("if (value.$N == null) writer.nullValue()", param.callableName);
|
||||
code.beginControlFlow("else");
|
||||
Adapters.generateWrite(param, spec, code, typeVariables, otherAdapters, message, writeGet);
|
||||
code.endControlFlow();
|
||||
code.endControlFlow();
|
||||
}
|
||||
}
|
||||
for (Property.Getter param : properties.getters) {
|
||||
if (param.type.kind.isPrimitive) {
|
||||
generateComments(param, code);
|
||||
code.addStatement("writer.name($S)", getSerializedName(param));
|
||||
Adapters.generateWrite(param, spec, code, typeVariables, otherAdapters, message, () -> code.add("value.$N()", param.callableName));
|
||||
} else {
|
||||
code.addStatement("$T $L$N = value.$N()", param.type, "$", param.callableName, param.callableName);
|
||||
code.beginControlFlow("if ($L$N != null || writer.getSerializeNulls())", "$", param.callableName);
|
||||
generateComments(param, code);
|
||||
code.addStatement("writer.name($S)", getSerializedName(param));
|
||||
code.addStatement("if ($L$N == null) writer.nullValue()", "$", param.callableName);
|
||||
code.beginControlFlow("else");
|
||||
Adapters.generateWrite(param, spec, code, typeVariables, otherAdapters, message, () -> code.add("$L$N", "$", param.callableName));
|
||||
code.endControlFlow();
|
||||
code.endControlFlow();
|
||||
}
|
||||
}
|
||||
code.addStatement("writer.endObject()");
|
||||
|
||||
spec.addMethod(extension(MethodSpec.methodBuilder("write"), self.typeName)
|
||||
.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.type, param.name, TypeHelper.getDefaultValue(param.type));
|
||||
}
|
||||
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.type.kind.isPrimitive) {
|
||||
code.add("case $S -> _$N = ", getSerializedName(param), param.name);
|
||||
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.name);
|
||||
code.unindent().add("} else _$N = ", param.name);
|
||||
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.typeName);
|
||||
ClassName creatorName = ClassName.get((TypeElement) constructionSource.constructionElement.enclosingElement);
|
||||
if (constructionSource instanceof ConstructionSource.Builder builder) {
|
||||
StringBuilder args = new StringBuilder();
|
||||
for (Property.ConstructorParam param : properties.constructorParams) {
|
||||
args.append(", _").append(param.name);
|
||||
}
|
||||
code.add("$T builder = ", builder.builderClass);
|
||||
if (constructionSource.isConstructor) {
|
||||
code.add("new $T($L)", builder.builderClass, args.length() > 0 ? args.substring(2) : "");
|
||||
} else {
|
||||
code.add("$T.$N($L)", creatorName, self.classElement.simpleName, args.length() > 0 ? args.substring(2) : "");
|
||||
}
|
||||
code.add(";\n");
|
||||
for (Property.Setter param : properties.builderParams) {
|
||||
code.addStatement("builder.$N(_$N)", param.callableName, param.name);
|
||||
}
|
||||
code.addStatement("result = builder.$N()", builder.buildMethod.simpleName);
|
||||
} else {
|
||||
StringBuilder args = new StringBuilder();
|
||||
for (Property.Param param : properties.params) {
|
||||
args.append(", _").append(param.name);
|
||||
}
|
||||
if (constructionSource.isConstructor) {
|
||||
code.addStatement("result = new $T($L)", self.typeName, args.length() > 0 ? args.substring(2) : "");
|
||||
} else {
|
||||
code.addStatement("result = $T.$N($L)", creatorName, constructionSource.constructionElement.simpleName, args.length() > 0 ? args.substring(2) : "");
|
||||
}
|
||||
}
|
||||
for (Property.Setter setter : properties.setters) {
|
||||
code.addStatement("result.$N(_$N)", setter.callableName, setter.name);
|
||||
}
|
||||
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.name, field.callableName);
|
||||
}
|
||||
code.addStatement("return result");
|
||||
|
||||
spec.addMethod(extension(MethodSpec.methodBuilder("read"))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.returns(self.typeName)
|
||||
.addParameter(Cl.GSON_READER, "reader")
|
||||
.addException(IOException.class)
|
||||
.addCode(code.build())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
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.util.Cl;
|
||||
|
||||
import javax.annotation.processing.Messager;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class StaticProcessor extends GProcessor {
|
||||
public StaticProcessor(ValueCreator valueCreator, Messager message, boolean hasManifold) {
|
||||
super(valueCreator, message, hasManifold, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateDelegatingAdapter(TypeSpec.Builder spec, TypeName classType, ClassName generatedClassName) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
// !!!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 = valueCreator.fromStatic(self.classElement);
|
||||
Properties properties = value.properties;
|
||||
|
||||
// public static void write(JsonWriter writer) throws IOException
|
||||
{
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
code.addStatement("writer.beginObject()");
|
||||
for (Property.Field param : properties.fields) {
|
||||
if (Properties.containsName(properties.getters, param)) continue;
|
||||
Runnable writeGet = () -> code.add("$T.$N", self.typeName, param.callableName);
|
||||
if (param.type.kind.isPrimitive) {
|
||||
generateComments(param, code);
|
||||
code.addStatement("writer.name($S)", getSerializedName(param));
|
||||
Adapters.generateWrite(param, spec, code, typeVariables, otherAdapters, message, writeGet);
|
||||
} else {
|
||||
code.beginControlFlow("if ($T.$N != null || writer.getSerializeNulls())", self.typeName, param.callableName);
|
||||
generateComments(param, code);
|
||||
code.addStatement("writer.name($S)", getSerializedName(param));
|
||||
code.addStatement("if ($T.$N == null) writer.nullValue()", self.typeName, param.callableName);
|
||||
code.beginControlFlow("else");
|
||||
Adapters.generateWrite(param, spec, code, typeVariables, otherAdapters, message, writeGet);
|
||||
code.endControlFlow();
|
||||
code.endControlFlow();
|
||||
}
|
||||
}
|
||||
for (Property.Getter param : properties.getters) {
|
||||
if (param.type.kind.isPrimitive) {
|
||||
generateComments(param, code);
|
||||
code.addStatement("writer.name($S)", getSerializedName(param));
|
||||
Adapters.generateWrite(param, spec, code, typeVariables, otherAdapters, message, () -> code.add("$T.$N()", self.typeName, param.callableName));
|
||||
} else {
|
||||
code.addStatement("$T $L$N = $T.$N()", param.type, "$", param.callableName, self.typeName, param.callableName);
|
||||
code.beginControlFlow("if ($L$N != null || writer.getSerializeNulls())", "$", param.callableName);
|
||||
generateComments(param, code);
|
||||
code.addStatement("writer.name($S)", getSerializedName(param));
|
||||
code.addStatement("if ($L$N == null) writer.nullValue()", "$", param.callableName);
|
||||
code.beginControlFlow("else");
|
||||
Adapters.generateWrite(param, spec, code, typeVariables, otherAdapters, message, () -> code.add("$L$N", "$", param.callableName));
|
||||
code.endControlFlow();
|
||||
code.endControlFlow();
|
||||
}
|
||||
}
|
||||
code.addStatement("writer.endObject()");
|
||||
|
||||
spec.addMethod(extension(MethodSpec.methodBuilder("write"))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(Cl.GSON_WRITER, "writer")
|
||||
.addException(IOException.class)
|
||||
.addCode(code.build())
|
||||
.build());
|
||||
}
|
||||
|
||||
// public static void 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")
|
||||
.endControlFlow();
|
||||
|
||||
boolean isEmpty = true;
|
||||
for (Property<?> param : properties.names) {
|
||||
isEmpty = false;
|
||||
code.addStatement("$T.$N = $L", self.typeName, param.name, TypeHelper.getDefaultValue(param.type));
|
||||
}
|
||||
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.type.kind.isPrimitive) {
|
||||
code.add("case $S -> $T.$N = ", getSerializedName(param), self.typeName, param.name);
|
||||
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("$T.$N = null", self.typeName, param.name);
|
||||
code.unindent().add("} else $T.$N = ", self.typeName, param.name);
|
||||
Adapters.generateRead(param, spec, code, typeVariables, otherAdapters, message);
|
||||
code.add(";\n")
|
||||
.endControlFlow();
|
||||
}
|
||||
}
|
||||
code.add("default -> ")
|
||||
.addStatement("reader.skipValue()");
|
||||
|
||||
code.endControlFlow()
|
||||
.endControlFlow()
|
||||
.addStatement("reader.endObject()");
|
||||
}
|
||||
|
||||
spec.addMethod(extension(MethodSpec.methodBuilder("read"))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(Cl.GSON_READER, "reader")
|
||||
.addException(IOException.class)
|
||||
.addCode(code.build())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor;
|
||||
package io.gitlab.jfronny.gson.compile.processor.util;
|
||||
|
||||
import com.squareup.javapoet.ClassName;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor;
|
||||
package io.gitlab.jfronny.gson.compile.processor.util;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
Loading…
Reference in New Issue