Support for fully static classes
ci/woodpecker/push/woodpecker Pipeline was successful Details

This commit is contained in:
Johannes Frohnmeyer 2022-12-14 14:55:45 +01:00
parent 554fe8f8a7
commit 859605e06e
Signed by: Johannes
GPG Key ID: E76429612C2929F4
23 changed files with 704 additions and 407 deletions

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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))

View File

@ -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";

View File

@ -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.

View File

@ -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));
}

View File

@ -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() {

View File

@ -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:
* ```

View File

@ -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());
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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.*;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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()
);
}
}

View File

@ -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());
}
}
}

View File

@ -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());
}
}
}

View File

@ -1,4 +1,4 @@
package io.gitlab.jfronny.gson.compile.processor;
package io.gitlab.jfronny.gson.compile.processor.util;
import com.squareup.javapoet.ClassName;

View File

@ -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;