diff --git a/README.md b/README.md index c886cd4..ebde648 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,12 @@ The goal of this AP is to - Comments via `@GComment` - Records - Nested serializable types +- Arrays ## TODO -- Arrays - Collections (Sets, Lists, Queues, Deques) -- Maps -- Date +- Maps with string, primitive or enum keys +- Date via ISO8601Utils - Enums - Support for nested types from libraries - Static classes (for configs) diff --git a/gson-compile-core/src/main/java/io/gitlab/jfronny/gson/compile/core/Test.java b/gson-compile-core/src/main/java/io/gitlab/jfronny/gson/compile/core/Test.java deleted file mode 100644 index d84857a..0000000 --- a/gson-compile-core/src/main/java/io/gitlab/jfronny/gson/compile/core/Test.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.gitlab.jfronny.gson.compile.core; - -import io.gitlab.jfronny.gson.TypeAdapter; -import io.gitlab.jfronny.gson.stream.JsonReader; -import io.gitlab.jfronny.gson.stream.JsonWriter; - -import java.io.IOException; - -public class Test { - public static void main(String[] args) { - new TypeAdapter() { - @Override - public void write(JsonWriter jsonWriter, String s) throws IOException { - - } - - @Override - public String read(JsonReader jsonReader) throws IOException { - return null; - } - }; - } -} diff --git a/gson-compile-example/src/main/java/io/gitlab/jfronny/gson/Main.java b/gson-compile-example/src/main/java/io/gitlab/jfronny/gson/Main.java index c813920..109ae93 100644 --- a/gson-compile-example/src/main/java/io/gitlab/jfronny/gson/Main.java +++ b/gson-compile-example/src/main/java/io/gitlab/jfronny/gson/Main.java @@ -29,9 +29,10 @@ public class Main { public static class ExamplePojo2 { @GComment("Yes!") public boolean primitive; + public ExamplePojo2[] recursiveTest; } @GSerializable - public record ExampleRecord(String hello, ExamplePojo2 pojo) { + public record ExampleRecord(String hello, @GComment("Sheesh") ExamplePojo2 pojo) { } } \ No newline at end of file diff --git a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/Const.java b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/Cl.java similarity index 85% rename from gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/Const.java rename to gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/Cl.java index 79138b8..64104e7 100644 --- a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/Const.java +++ b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/Cl.java @@ -2,13 +2,7 @@ package io.gitlab.jfronny.gson.compile.processor; import com.squareup.javapoet.ClassName; -public class Const { - public static final String PREFIX = "GC_"; - public static final String ADAPTER_PREFIX = "adapter_"; - public static final String ARG_PREFIX = "_"; - public static final String READ = "read"; - public static final String WRITE = "write"; - +public class Cl { public static final ClassName TYPE_ADAPTER = ClassName.get("io.gitlab.jfronny.gson", "TypeAdapter"); public static final ClassName TYPE_ADAPTER_FACTORY = ClassName.get("io.gitlab.jfronny.gson", "TypeAdapterFactory"); public static final ClassName GSON_ELEMENT = ClassName.get("io.gitlab.jfronny.gson", "JsonElement"); diff --git a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/GsonCompileProcessor.java b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/GsonCompileProcessor.java index dbd68eb..0355b5d 100644 --- a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/GsonCompileProcessor.java +++ b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/GsonCompileProcessor.java @@ -3,7 +3,8 @@ package io.gitlab.jfronny.gson.compile.processor; import com.squareup.javapoet.*; import io.gitlab.jfronny.commons.StringFormatter; import io.gitlab.jfronny.gson.compile.annotations.GSerializable; -import io.gitlab.jfronny.gson.compile.processor.adapter.*; +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.util.AbstractProcessor2; import io.gitlab.jfronny.gson.compile.processor.util.SupportedAnnotationTypes2; import io.gitlab.jfronny.gson.compile.processor.util.valueprocessor.Properties; @@ -17,22 +18,12 @@ import javax.lang.model.util.Elements; import javax.tools.Diagnostic; import java.io.*; import java.util.*; -import java.util.function.Consumer; import java.util.stream.Collectors; @SupportedSourceVersion(SourceVersion.RELEASE_17) @SupportedAnnotationTypes2({GSerializable.class}) @SupportedOptions({"gsonCompileNoReflect"}) public class GsonCompileProcessor extends AbstractProcessor2 { - //TODO handle lists, sets, arrays, other common types (-> https://github.com/bluelinelabs/LoganSquare/blob/development/docs/TypeConverters.md) - private static final List ADAPTERS = List.of( - new DeclaredAdapter(), - new PrimitiveAdapter(), - new StringAdapter(), - new OtherSerializableAdapter(), - new ReflectAdapter() - ); - private Messager message; private Filer filer; private Set seen; @@ -47,7 +38,7 @@ public class GsonCompileProcessor extends AbstractProcessor2 { elements = processingEnv.getElementUtils(); seen = new LinkedHashSet<>(); valueCreator = new ValueCreator(processingEnv); - for (Adapter adapter : ADAPTERS) { + for (Adapter adapter : Adapters.ADAPTERS) { adapter.init(processingEnv); } } @@ -141,11 +132,11 @@ public class GsonCompileProcessor extends AbstractProcessor2 { spec.addType( TypeSpec.classBuilder("Adapter") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) - .superclass(ParameterizedTypeName.get(Const.TYPE_ADAPTER, classType)) + .superclass(ParameterizedTypeName.get(Cl.TYPE_ADAPTER, classType)) .addMethod(MethodSpec.methodBuilder("write") .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) - .addParameter(Const.GSON_WRITER, "writer") + .addParameter(Cl.GSON_WRITER, "writer") .addParameter(classType, "value") .addException(IOException.class) .addCode(generatedClassName.simpleName() + ".write(writer, value);") @@ -153,7 +144,7 @@ public class GsonCompileProcessor extends AbstractProcessor2 { .addMethod(MethodSpec.methodBuilder("read") .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) - .addParameter(Const.GSON_READER, "reader") + .addParameter(Cl.GSON_READER, "reader") .addException(IOException.class) .returns(classType) .addCode("return " + generatedClassName.simpleName() + ".read(reader);") @@ -170,18 +161,18 @@ public class GsonCompileProcessor extends AbstractProcessor2 { .build() ); spec.addMethod( - MethodSpec.methodBuilder(Const.READ) + MethodSpec.methodBuilder("read") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) - .addParameter(Const.GSON_READER, "reader") + .addParameter(Cl.GSON_READER, "reader") .addException(IOException.class) .returns(classType) .addCode("return ADAPTER.read(reader);") .build() ); spec.addMethod( - MethodSpec.methodBuilder(Const.READ) + MethodSpec.methodBuilder("read") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) - .addParameter(Const.GSON_READER, "writer") + .addParameter(Cl.GSON_READER, "writer") .addParameter(classType, "value") .addException(IOException.class) .returns(classType) @@ -192,7 +183,7 @@ public class GsonCompileProcessor extends AbstractProcessor2 { private static void generateAuxiliary(TypeSpec.Builder spec, TypeName classType) { spec.addMethod( - MethodSpec.methodBuilder(Const.READ) + MethodSpec.methodBuilder("read") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addParameter(TypeName.get(Reader.class), "in") .addException(IOException.class) @@ -200,12 +191,12 @@ public class GsonCompileProcessor extends AbstractProcessor2 { .addCode(""" try ($T reader = $T.HOLDER.getGson().newJsonReader(in)) { return read(reader); - }""", Const.GSON_READER, Const.CCORE) + }""", Cl.GSON_READER, Cl.CCORE) .build() ); spec.addMethod( - MethodSpec.methodBuilder(Const.READ) + MethodSpec.methodBuilder("read") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addParameter(TypeName.get(String.class), "json") .addException(IOException.class) @@ -218,20 +209,20 @@ public class GsonCompileProcessor extends AbstractProcessor2 { ); spec.addMethod( - MethodSpec.methodBuilder(Const.READ) + MethodSpec.methodBuilder("read") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) - .addParameter(Const.GSON_ELEMENT, "tree") + .addParameter(Cl.GSON_ELEMENT, "tree") .addException(IOException.class) .returns(classType) .addCode(""" try ($1T reader = new $1T(tree)) { return read(reader); - }""", Const.GSON_TREE_READER) + }""", Cl.GSON_TREE_READER) .build() ); spec.addMethod( - MethodSpec.methodBuilder(Const.WRITE) + MethodSpec.methodBuilder("write") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addParameter(Writer.class, "out") .addParameter(classType, "value") @@ -239,7 +230,7 @@ public class GsonCompileProcessor extends AbstractProcessor2 { .addCode(""" try ($T writer = $T.HOLDER.getGson().newJsonWriter(out)) { write(writer, value); - }""", Const.GSON_WRITER, Const.CCORE) + }""", Cl.GSON_WRITER, Cl.CCORE) .build() ); @@ -262,12 +253,12 @@ public class GsonCompileProcessor extends AbstractProcessor2 { .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addParameter(classType, "value") .addException(IOException.class) - .returns(Const.GSON_ELEMENT) + .returns(Cl.GSON_ELEMENT) .addCode(""" try ($1T writer = new $1T()) { write(writer, value); return writer.get(); - }""", Const.GSON_TREE_WRITER) + }""", Cl.GSON_TREE_WRITER) .build() ); } @@ -286,21 +277,39 @@ public class GsonCompileProcessor extends AbstractProcessor2 { .endControlFlow(); code.addStatement("writer.beginObject()"); - for (Property.Field field : properties.fields) { - generateComments(field, code); - code.addStatement("writer.name($S)", getSerializedName(field)); - withAdapter(field, spec, code, typeVariables, otherAdapters, adapter -> adapter.generateWrite(() -> code.add("value.$N", field.getCallableName()))); + for (Property.Field param : properties.fields) { + generateComments(param, code); + code.addStatement("writer.name($S)", getSerializedName(param)); + Runnable writeGet = () -> code.add("value.$N", param.getCallableName()); + if (param.getType().getKind().isPrimitive()) { + Adapters.generateWrite(param, spec, code, typeVariables, otherAdapters, message, writeGet); + } else { + code.beginControlFlow("if (value.$N == null)", param.getCallableName()) + .addStatement("if (writer.getSerializeNulls()) writer.nullValue()") + .nextControlFlow("else"); + Adapters.generateWrite(param, spec, code, typeVariables, otherAdapters, message, writeGet); + code.endControlFlow(); + } } - for (Property.Getter getter : properties.getters) { - generateComments(getter, code); - code.addStatement("writer.name($S)", getSerializedName(getter)); - withAdapter(getter, spec, code, typeVariables, otherAdapters, adapter -> adapter.generateWrite(() -> code.add("value.$N()", getter.getCallableName()))); + for (Property.Getter param : properties.getters) { + generateComments(param, code); + code.addStatement("writer.name($S)", getSerializedName(param)); + if (param.getType().getKind().isPrimitive()) { + Adapters.generateWrite(param, spec, code, typeVariables, otherAdapters, message, () -> code.add("value.$N()", param.getCallableName())); + } else { + code.addStatement("$T $L$N = value.$N()", param.getType(), "$", param.getCallableName(), param.getCallableName()) + .beginControlFlow("if ($L$N == null)", "$", param.getCallableName()) + .addStatement("if (writer.getSerializeNulls()) writer.nullValue()") + .nextControlFlow("else"); + Adapters.generateWrite(param, spec, code, typeVariables, otherAdapters, message, () -> code.add("$L$N", "$", param.getCallableName())); + code.endControlFlow(); + } } code.addStatement("writer.endObject()"); spec.addMethod(MethodSpec.methodBuilder("write") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) - .addParameter(Const.GSON_WRITER, "writer") + .addParameter(Cl.GSON_WRITER, "writer") .addParameter(classType, "value") .addException(IOException.class) .addCode(code.build()) @@ -310,7 +319,7 @@ public class GsonCompileProcessor extends AbstractProcessor2 { // public static T read(JsonReader reader) throws IOException { CodeBlock.Builder code = CodeBlock.builder(); - code.beginControlFlow("if (reader.peek() == $T.NULL)", Const.GSON_TOKEN) + code.beginControlFlow("if (reader.peek() == $T.NULL)", Cl.GSON_TOKEN) .addStatement("reader.nextNull()") .addStatement("return null") .endControlFlow(); @@ -318,7 +327,7 @@ public class GsonCompileProcessor extends AbstractProcessor2 { boolean isEmpty = true; for (Property param : properties.names) { isEmpty = false; - code.addStatement("$T $L = $L", param.getType(), Const.ARG_PREFIX + param.getName(), getDefaultValue(param.getType())); + code.addStatement("$T _$N = $L", param.getType(), param.getName(), TypeHelper.getDefaultValue(param.getType())); } if (isEmpty) { code.addStatement("reader.skipValue()"); @@ -328,16 +337,16 @@ public class GsonCompileProcessor extends AbstractProcessor2 { .beginControlFlow("switch (reader.nextName())"); for (Property param : properties.names) { if (param.getType().getKind().isPrimitive()) { - code.add("case $S -> $L = ", getSerializedName(param), Const.ARG_PREFIX + param.getName()); - withAdapter(param, spec, code, typeVariables, otherAdapters, Adapter::generateRead); + code.add("case $S -> _$N = ", getSerializedName(param), param.getName()); + Adapters.generateRead(param, spec, code, typeVariables, otherAdapters, message); code.add(";\n"); } else { code.beginControlFlow("case $S ->", getSerializedName(param)) - .beginControlFlow("if (reader.peek() == $T.NULL)", Const.GSON_TOKEN) + .beginControlFlow("if (reader.peek() == $T.NULL)", Cl.GSON_TOKEN) .addStatement("reader.nextNull()") - .addStatement("$L = null", Const.ARG_PREFIX + param.getName()); - code.unindent().add("} else $L = ", Const.ARG_PREFIX + param.getName()); - withAdapter(param, spec, code, typeVariables, otherAdapters, Adapter::generateRead); + .addStatement("_$N = null", param.getName()); + code.unindent().add("} else _$N = ", param.getName()); + Adapters.generateRead(param, spec, code, typeVariables, otherAdapters, message); code.add(";\n") .endControlFlow(); } @@ -352,30 +361,30 @@ public class GsonCompileProcessor extends AbstractProcessor2 { ClassName creatorName = ClassName.get((TypeElement) constructionSource.getConstructionElement().getEnclosingElement()); if (constructionSource instanceof ConstructionSource.Builder builder) { - String args = properties.constructorParams.stream().map(s -> Const.ARG_PREFIX + s.getName()).collect(Collectors.joining(", ")); + String args = properties.constructorParams.stream().map(s -> "_" + s.getName()).collect(Collectors.joining(", ")); if (constructionSource.isConstructor()) { code.add("return new $T($L)", builder.getBuilderClass(), args); } else { - code.add("return $T.$L($L)", creatorName, classElement.getSimpleName(), args); + code.add("return $T.$N($L)", creatorName, classElement.getSimpleName(), args); } code.add("\n").indent(); for (Property.BuilderParam param : properties.builderParams) { - code.add(".$L($L)\n", param.getCallableName(), Const.ARG_PREFIX + param.getName()); + code.add(".$N(_$L)\n", param.getCallableName(), param.getName()); } - code.add(".$L();\n", builder.getBuildMethod().getSimpleName()).unindent(); + code.add(".$N();\n", builder.getBuildMethod().getSimpleName()).unindent(); } else { - String args = properties.params.stream().map(s -> Const.ARG_PREFIX + s.getName()).collect(Collectors.joining(", ")); + String args = properties.params.stream().map(s -> "_" + s.getName()).collect(Collectors.joining(", ")); if (constructionSource.isConstructor()) { code.addStatement("return new $T($L)", classType, args); } else { - code.addStatement("return $T.$L($L)", creatorName, constructionSource.getConstructionElement().getSimpleName(), args); + code.addStatement("return $T.$N($L)", creatorName, constructionSource.getConstructionElement().getSimpleName(), args); } } spec.addMethod(MethodSpec.methodBuilder("read") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(classType) - .addParameter(Const.GSON_READER, "reader") + .addParameter(Cl.GSON_READER, "reader") .addException(IOException.class) .addCode(code.build()) .build()); @@ -384,36 +393,16 @@ public class GsonCompileProcessor extends AbstractProcessor2 { private void generateComments(Property prop, CodeBlock.Builder code) { for (AnnotationMirror annotation : prop.getAnnotations()) { - if (annotation.getAnnotationType().asElement().toString().equals(Const.GCOMMENT.toString())) { + if (annotation.getAnnotationType().asElement().toString().equals(Cl.GCOMMENT.toString())) { String comment = (String) annotation.getElementValues().values().iterator().next().getValue(); - code.addStatement("writer.comment($S)", comment); + code.addStatement("if (writer.isLenient()) writer.comment($S)", comment); } } } - private void withAdapter(Property prop, TypeSpec.Builder klazz, CodeBlock.Builder code, List typeVariables, Set otherAdapters, Consumer action) { - for (Adapter adapter : ADAPTERS) { - adapter.hydrate(prop, klazz, code, typeVariables, otherAdapters); - if (adapter.applies()) { - action.accept(adapter); - adapter.dehydrate(); - return; - } else adapter.dehydrate(); - } - message.printMessage(Diagnostic.Kind.ERROR, "Could not find applicable adapter for property " + prop); - } - - private static String getDefaultValue(TypeMirror type) { - return switch (type.getKind()) { - case BYTE, SHORT, INT, LONG, FLOAT, CHAR, DOUBLE -> "0"; - case BOOLEAN -> "false"; - default -> "null"; - }; - } - private static String getSerializedName(Property property) { for (AnnotationMirror annotationMirror : property.getAnnotations()) { - if (annotationMirror.getAnnotationType().asElement().toString().equals(Const.SERIALIZED_NAME.toString())) { + if (annotationMirror.getAnnotationType().asElement().toString().equals(Cl.SERIALIZED_NAME.toString())) { return (String) annotationMirror.getElementValues().values().iterator().next().getValue(); } } diff --git a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/SerializableClass.java b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/SerializableClass.java index d334950..f757e03 100644 --- a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/SerializableClass.java +++ b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/SerializableClass.java @@ -10,7 +10,7 @@ import javax.lang.model.type.TypeMirror; public record SerializableClass(TypeElement classElement, ClassName generatedClassName, @Nullable TypeMirror adapter, boolean generateAdapter) { public static SerializableClass of(TypeElement element, @Nullable TypeMirror with, boolean generateAdapter) { ClassName className = ClassName.get(element); - ClassName generatedClassName = ClassName.get(className.packageName(), Const.PREFIX + String.join("_", className.simpleNames())); + ClassName generatedClassName = ClassName.get(className.packageName(), "GC_" + String.join("_", className.simpleNames())); return new SerializableClass(element, generatedClassName, with, generateAdapter); } diff --git a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/TypeHelper.java b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/TypeHelper.java index af17d3f..9424611 100644 --- a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/TypeHelper.java +++ b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/TypeHelper.java @@ -3,8 +3,7 @@ package io.gitlab.jfronny.gson.compile.processor; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.type.*; -import javax.lang.model.util.SimpleTypeVisitor14; -import javax.lang.model.util.Types; +import javax.lang.model.util.*; import java.util.*; public class TypeHelper { @@ -41,6 +40,15 @@ public class TypeHelper { }, null); } + public static ArrayType asArrayType(TypeMirror type) { + return type.accept(new TypeKindVisitor14<>() { + @Override + public ArrayType visitArray(ArrayType t, Object o) { + return t; + } + }, null); + } + public static boolean isInstance(DeclaredType type, String parentClassName, Types typeUtils) { if (type == null) return false; TypeElement element = (TypeElement) type.asElement(); @@ -57,4 +65,12 @@ public class TypeHelper { } return false; } + + static String getDefaultValue(TypeMirror type) { + return switch (type.getKind()) { + case BYTE, SHORT, INT, LONG, FLOAT, CHAR, DOUBLE -> "0"; + case BOOLEAN -> "false"; + default -> "null"; + }; + } } diff --git a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/Adapter.java b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/Adapter.java index 50c06eb..7eb1cb8 100644 --- a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/Adapter.java +++ b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/Adapter.java @@ -1,33 +1,23 @@ package io.gitlab.jfronny.gson.compile.processor.adapter; import com.squareup.javapoet.*; -import io.gitlab.jfronny.gson.compile.processor.Const; import io.gitlab.jfronny.gson.compile.processor.SerializableClass; -import io.gitlab.jfronny.gson.compile.processor.util.valueprocessor.Property; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; import java.util.*; +import java.util.function.Consumer; -public abstract class Adapter { - protected Property property; - protected TypeSpec.Builder klazz; - protected List typeVariables; - protected Set other; - protected TypeMirror type; - protected TypeMirror unboxedType; - protected CodeBlock.Builder code; - protected String argName; +public abstract class Adapter.Hydrated> { protected Messager message; protected Types typeUtils; protected Map options; - protected TypeName typeName; - public abstract boolean applies(); - public abstract void generateWrite(Runnable writeGet); - public abstract void generateRead(); + public abstract T instantiate(); public void init(ProcessingEnvironment env) { this.message = env.getMessager(); @@ -35,31 +25,53 @@ public abstract class Adapter { this.options = env.getOptions(); } - public void hydrate(Property property, TypeSpec.Builder klazz, CodeBlock.Builder code, List typeVariables, Set other) { - this.property = property; - this.klazz = klazz; - this.typeVariables = typeVariables; - this.other = other; - this.type = property.getType(); - this.code = code; + public final T hydrate(TypeSpec.Builder klazz, CodeBlock.Builder code, List typeVariables, Set other, TypeMirror type, String propName, List annotations, Element sourceElement) { + T instance = instantiate(); + instance.klazz = klazz; + instance.typeVariables = typeVariables; + instance.other = other; + instance.type = type; + instance.code = code; try { - this.unboxedType = typeUtils.unboxedType(type); + instance.unboxedType = typeUtils.unboxedType(type); } catch (IllegalArgumentException e) { - this.unboxedType = type; + instance.unboxedType = type; } - this.argName = Const.ARG_PREFIX + this.property.getName(); - this.typeName = TypeName.get(type).box(); + instance.name = propName; + instance.argName = "_" + propName; + instance.adapterName = "adapter_" + propName; + instance.typeName = TypeName.get(type).box(); + instance.annotations = annotations; + instance.sourceElement = sourceElement; + instance.afterHydrate(); + return instance; } - public void dehydrate() { - this.property = null; - this.klazz = null; - this.typeVariables = null; - this.other = null; - this.type = null; - this.code = null; - this.unboxedType = null; - this.argName = null; - this.typeName = null; + public abstract class Hydrated { + protected TypeSpec.Builder klazz; + protected List typeVariables; + protected Set other; + protected TypeMirror type; + protected TypeMirror unboxedType; + protected CodeBlock.Builder code; + protected String name; + protected String argName; + protected String adapterName; + protected TypeName typeName; + protected List annotations; + protected Element sourceElement; + + public abstract boolean applies(); + public abstract void generateWrite(Runnable writeGet); + public abstract void generateRead(); + protected void afterHydrate() {} + + protected void generateRead(CodeBlock.Builder code, TypeMirror type, String name, List annotations) { + Adapters.generateRead(klazz, code, typeVariables, other, type, name, annotations, sourceElement, message); + } + + protected void generateWrite(CodeBlock.Builder code, TypeMirror type, String name, List annotations, Runnable writeGet) { + Adapters.generateWrite(klazz, code, typeVariables, other, type, name, annotations, sourceElement, message, writeGet); + } } } diff --git a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/AdapterAdapter.java b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/AdapterAdapter.java index afa8e6e..d597e91 100644 --- a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/AdapterAdapter.java +++ b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/AdapterAdapter.java @@ -1,7 +1,7 @@ package io.gitlab.jfronny.gson.compile.processor.adapter; import com.squareup.javapoet.*; -import io.gitlab.jfronny.gson.compile.processor.Const; +import io.gitlab.jfronny.gson.compile.processor.Cl; import io.gitlab.jfronny.gson.compile.processor.TypeHelper; import javax.lang.model.type.TypeMirror; @@ -9,60 +9,60 @@ import java.lang.reflect.ParameterizedType; import java.util.Iterator; import java.util.List; -public abstract class AdapterAdapter extends Adapter { - @Override - public void generateWrite(Runnable writeGet) { - code.add(getAdapter() + ".write(writer, "); - writeGet.run(); - code.add(");\n"); - } - - @Override - public void generateRead() { - code.add(getAdapter() + ".read(reader)"); - } - - private String getAdapter() { - String typeAdapterName = Const.ADAPTER_PREFIX + property.getName(); - for (FieldSpec spec : klazz.fieldSpecs) { - if (spec.name.equals(typeAdapterName)) return typeAdapterName; +public abstract class AdapterAdapter.Hydrated> extends Adapter { + public abstract class Hydrated extends Adapter.Hydrated { + @Override + public void generateWrite(Runnable writeGet) { + code.add(getAdapter() + ".write(writer, "); + writeGet.run(); + code.add(");\n"); } - return createAdapter(typeAdapterName); - } - protected abstract String createAdapter(String name); + @Override + public void generateRead() { + code.add(getAdapter() + ".read(reader)"); + } - protected void appendFieldTypeToken(boolean allowClassType) { - TypeMirror type = property.getType(); - TypeName typeName = TypeName.get(type); - - if (TypeHelper.isComplexType(type, typeUtils)) { - TypeName typeTokenType = ParameterizedTypeName.get(Const.TYPE_TOKEN, typeName); - List typeParams = TypeHelper.getGenericTypes(type); - if (typeParams.isEmpty()) { - code.add("new $T() {}", typeTokenType); - } else { - code.add("($T) $T.getParameterized($T.class, ", typeTokenType, Const.TYPE_TOKEN, typeUtils.erasure(type)); - for (Iterator iterator = typeParams.iterator(); iterator.hasNext(); ) { - TypeMirror typeParam = iterator.next(); - int typeIndex = typeVariables.indexOf(TypeVariableName.get(typeParam.toString())); - code.add("(($T)typeToken.getType()).getActualTypeArguments()[$L]", ParameterizedType.class, typeIndex); - if (iterator.hasNext()) { - code.add(", "); - } - } - code.add(")"); + private String getAdapter() { + for (FieldSpec spec : klazz.fieldSpecs) { + if (spec.name.equals(adapterName)) return adapterName; } - } else if (TypeHelper.isGenericType(type)) { - TypeName typeTokenType = ParameterizedTypeName.get(Const.TYPE_TOKEN, typeName); - int typeIndex = typeVariables.indexOf(TypeVariableName.get(property.getType().toString())); - code.add("($T) $T.get((($T)typeToken.getType()).getActualTypeArguments()[$L])", - typeTokenType, Const.TYPE_TOKEN, ParameterizedType.class, typeIndex); - } else { - if (allowClassType) { - code.add("$T.class", typeName); + return createAdapter(adapterName); + } + + protected abstract String createAdapter(String name); + + protected void appendFieldTypeToken(boolean allowClassType) { + TypeName typeName = TypeName.get(type); + + if (TypeHelper.isComplexType(type, typeUtils)) { + TypeName typeTokenType = ParameterizedTypeName.get(Cl.TYPE_TOKEN, typeName); + List typeParams = TypeHelper.getGenericTypes(type); + if (typeParams.isEmpty()) { + code.add("new $T() {}", typeTokenType); + } else { + code.add("($T) $T.getParameterized($T.class, ", typeTokenType, Cl.TYPE_TOKEN, typeUtils.erasure(type)); + for (Iterator iterator = typeParams.iterator(); iterator.hasNext(); ) { + TypeMirror typeParam = iterator.next(); + int typeIndex = typeVariables.indexOf(TypeVariableName.get(typeParam.toString())); + code.add("(($T)typeToken.getType()).getActualTypeArguments()[$L]", ParameterizedType.class, typeIndex); + if (iterator.hasNext()) { + code.add(", "); + } + } + code.add(")"); + } + } else if (TypeHelper.isGenericType(type)) { + TypeName typeTokenType = ParameterizedTypeName.get(Cl.TYPE_TOKEN, typeName); + int typeIndex = typeVariables.indexOf(TypeVariableName.get(type.toString())); + code.add("($T) $T.get((($T)typeToken.getType()).getActualTypeArguments()[$L])", + typeTokenType, Cl.TYPE_TOKEN, ParameterizedType.class, typeIndex); } else { - code.add("TypeToken.get($T.class)", typeName); + if (allowClassType) { + code.add("$T.class", typeName); + } else { + code.add("TypeToken.get($T.class)", typeName); + } } } } diff --git a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/Adapters.java b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/Adapters.java new file mode 100644 index 0000000..182ed52 --- /dev/null +++ b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/Adapters.java @@ -0,0 +1,57 @@ +package io.gitlab.jfronny.gson.compile.processor.adapter; + +import com.squareup.javapoet.*; +import io.gitlab.jfronny.gson.compile.processor.SerializableClass; +import io.gitlab.jfronny.gson.compile.processor.adapter.impl.*; +import io.gitlab.jfronny.gson.compile.processor.util.valueprocessor.Property; + +import javax.annotation.processing.Messager; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; + +public class Adapters { + public static final List> ADAPTERS = List.of( + new DeclaredAdapter(), + new PrimitiveAdapter(), + new StringAdapter(), + new ArrayAdapter(), + new OtherSerializableAdapter(), + new ReflectAdapter() + ); + + public static void generateRead(Property prop, TypeSpec.Builder klazz, CodeBlock.Builder code, List typeVariables, Set otherAdapters, Messager message) { + withAdapter(prop, klazz, code, typeVariables, otherAdapters, message, Adapter.Hydrated::generateRead); + } + + public static void generateWrite(Property prop, TypeSpec.Builder klazz, CodeBlock.Builder code, List typeVariables, Set otherAdapters, Messager message, Runnable writeGet) { + withAdapter(prop, klazz, code, typeVariables, otherAdapters, message, adapter -> adapter.generateWrite(writeGet)); + } + + private static void withAdapter(Property prop, TypeSpec.Builder klazz, CodeBlock.Builder code, List typeVariables, Set otherAdapters, Messager message, Consumer.Hydrated> action) { + withAdapter(klazz, code, typeVariables, otherAdapters, prop.getType(), prop.getName(), prop.getAnnotations(), prop.getElement(), message, action); + } + + public static void generateRead(TypeSpec.Builder klazz, CodeBlock.Builder code, List typeVariables, Set other, TypeMirror type, String propName, List annotations, Element sourceElement, Messager message) { + withAdapter(klazz, code, typeVariables, other, type, propName, annotations, sourceElement, message, Adapter.Hydrated::generateRead); + } + + public static void generateWrite(TypeSpec.Builder klazz, CodeBlock.Builder code, List typeVariables, Set other, TypeMirror type, String propName, List annotations, Element sourceElement, Messager message, Runnable writeGet) { + withAdapter(klazz, code, typeVariables, other, type, propName, annotations, sourceElement, message, adapter -> adapter.generateWrite(writeGet)); + } + + private static void withAdapter(TypeSpec.Builder klazz, CodeBlock.Builder code, List typeVariables, Set other, TypeMirror type, String propName, List annotations, Element sourceElement, Messager message, Consumer.Hydrated> action) { + for (Adapter adapter : Adapters.ADAPTERS) { + Adapter.Hydrated hydrated = adapter.hydrate(klazz, code, typeVariables, other, type, propName, annotations, sourceElement); + if (hydrated.applies()) { + action.accept(hydrated); + return; + } + } + message.printMessage(Diagnostic.Kind.ERROR, "Could not find applicable adapter for property " + propName, sourceElement); + } +} diff --git a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/DeclaredAdapter.java b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/DeclaredAdapter.java deleted file mode 100644 index e39f103..0000000 --- a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/DeclaredAdapter.java +++ /dev/null @@ -1,70 +0,0 @@ -package io.gitlab.jfronny.gson.compile.processor.adapter; - -import com.squareup.javapoet.*; -import io.gitlab.jfronny.gson.compile.processor.*; -import io.gitlab.jfronny.gson.compile.processor.util.valueprocessor.Property; - -import javax.lang.model.element.*; -import javax.lang.model.type.DeclaredType; -import javax.tools.Diagnostic; -import java.util.*; - -public class DeclaredAdapter extends AdapterAdapter { - private DeclaredType typeAdapterClass; - - @Override - public boolean applies() { - return typeAdapterClass != null; - } - - @Override - public void hydrate(Property property, TypeSpec.Builder klazz, CodeBlock.Builder code, List typeVariables, Set other) { - super.hydrate(property, klazz, code, typeVariables, other); - this.typeAdapterClass = findTypeAdapterClass(property.getAnnotations()); - } - - @Override - public void dehydrate() { - super.dehydrate(); - typeAdapterClass = null; - } - - @Override - protected String createAdapter(String typeAdapterName) { - if (TypeHelper.isInstance(typeAdapterClass, Const.TYPE_ADAPTER.toString(), typeUtils)) { - klazz.addField( - FieldSpec.builder(TypeName.get(typeAdapterClass), typeAdapterName) - .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) - .initializer("new $T()", typeAdapterClass) - .build() - ); - } else if (TypeHelper.isInstance(typeAdapterClass, Const.TYPE_ADAPTER_FACTORY.toString(), typeUtils)) { - TypeName typeAdapterType = ParameterizedTypeName.get(Const.TYPE_ADAPTER, TypeName.get(type).box()); - CodeBlock.Builder block = CodeBlock.builder(); - block.add("new $T().create($T.HOLDER.getGson(), ", typeAdapterClass, Const.CCORE); - appendFieldTypeToken(false); - block.add(")"); - klazz.addField( - FieldSpec.builder(typeAdapterType, typeAdapterName) - .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) - .initializer(block.build()) - .build() - ); - } else message.printMessage(Diagnostic.Kind.ERROR, "@JsonAdapter value must by TypeAdapter or TypeAdapterFactory reference.", property.getElement()); - return typeAdapterName; - } - - private DeclaredType findTypeAdapterClass(List annotations) { - for (AnnotationMirror annotation : annotations) { - String typeName = annotation.getAnnotationType().toString(); - if (typeName.equals(Const.JSON_ADAPTER.toString())) { - Map elements = annotation.getElementValues(); - if (!elements.isEmpty()) { - AnnotationValue value = elements.values().iterator().next(); - return (DeclaredType) value.getValue(); - } - } - } - return null; - } -} diff --git a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/OtherSerializableAdapter.java b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/OtherSerializableAdapter.java deleted file mode 100644 index 37cea6c..0000000 --- a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/OtherSerializableAdapter.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.gitlab.jfronny.gson.compile.processor.adapter; - -import com.squareup.javapoet.*; -import io.gitlab.jfronny.gson.compile.processor.SerializableClass; -import io.gitlab.jfronny.gson.compile.processor.util.valueprocessor.Property; - -import java.util.List; -import java.util.Set; - -public class OtherSerializableAdapter extends AdapterAdapter { - private String adapter; - - @Override - public boolean applies() { - return adapter != null; - } - - @Override - protected String createAdapter(String name) { - return adapter; - } - - @Override - public void hydrate(Property property, TypeSpec.Builder klazz, CodeBlock.Builder code, List typeVariables, Set other) { - super.hydrate(property, klazz, code, typeVariables, other); - for (SerializableClass adapter : other) { - if (TypeName.get(adapter.classElement().asType()).equals(typeName)) { - // Use self-made adapter - this.adapter = adapter.generatedClassName().toString(); - } - } - } - - @Override - public void dehydrate() { - super.dehydrate(); - this.adapter = null; - } -} diff --git a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/PrimitiveAdapter.java b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/PrimitiveAdapter.java deleted file mode 100644 index 3c5862e..0000000 --- a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/PrimitiveAdapter.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.gitlab.jfronny.gson.compile.processor.adapter; - -public class PrimitiveAdapter extends Adapter { - @Override - public boolean applies() { - return unboxedType.getKind().isPrimitive(); - } - - @Override - public void generateWrite(Runnable writeGet) { - if (type.equals(unboxedType)) { - code.add("writer.value("); - writeGet.run(); - code.addStatement(");\n"); - } else { - code.add("$T $L = ", type, argName); - writeGet.run(); - code.add(";\n"); - code.beginControlFlow("if ($L == null)", argName) - .addStatement("writer.nullValue()") - .endControlFlow("else writer.value($L)", argName); - } - } - - @Override - public void generateRead() { - code.add(switch (unboxedType.getKind()) { - case BOOLEAN -> "reader.nextBoolean()"; - case BYTE -> "(byte) reader.nextInt()"; - case SHORT -> "(short) reader.nextInt()"; - case INT -> "reader.nextInt()"; - case LONG -> "reader.nextLong()"; - case CHAR -> "(char) reader.nextInt()"; - case FLOAT -> "(float) reader.nextDouble()"; - case DOUBLE -> "reader.nextDouble()"; - default -> throw new IllegalArgumentException("Unsupported primitive: " + unboxedType.getKind()); - }); - } -} diff --git a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/ReflectAdapter.java b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/ReflectAdapter.java deleted file mode 100644 index afb56fa..0000000 --- a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/ReflectAdapter.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.gitlab.jfronny.gson.compile.processor.adapter; - -import com.squareup.javapoet.*; -import io.gitlab.jfronny.gson.compile.processor.Const; - -import javax.lang.model.element.Modifier; -import javax.tools.Diagnostic; - -public class ReflectAdapter extends AdapterAdapter { - @Override - public boolean applies() { - return !options.containsKey("gsonCompileNoReflect"); - } - - @Override - protected String createAdapter(String typeAdapterName) { - message.printMessage(Diagnostic.Kind.WARNING, "Falling back to adapter detection for unsupported type " + type, property.getElement()); - TypeName typeAdapterType = ParameterizedTypeName.get(Const.TYPE_ADAPTER, typeName); - CodeBlock.Builder block = CodeBlock.builder(); - block.add("$T.HOLDER.getGson().getAdapter(", Const.CCORE); - appendFieldTypeToken(true); - block.add(")"); - klazz.addField( - FieldSpec.builder(typeAdapterType, typeAdapterName) - .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) - .initializer(block.build()) - .build() - ); - return typeAdapterName; - } -} diff --git a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/StringAdapter.java b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/StringAdapter.java deleted file mode 100644 index 1b8707e..0000000 --- a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/StringAdapter.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.gitlab.jfronny.gson.compile.processor.adapter; - -public class StringAdapter extends Adapter { - @Override - public boolean applies() { - return type.toString().equals(String.class.getCanonicalName()); - } - - @Override - public void generateWrite(Runnable writeGet) { - code.add("$T $L = ", type, argName); - writeGet.run(); - code.add(";\n"); - code.beginControlFlow("if ($L == null)", argName) - .addStatement("writer.nullValue()") - .endControlFlow("else writer.value($L)", argName); - } - - @Override - public void generateRead() { - code.add("reader.nextString()"); - } -} diff --git a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/impl/ArrayAdapter.java b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/impl/ArrayAdapter.java new file mode 100644 index 0000000..d2974ef --- /dev/null +++ b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/impl/ArrayAdapter.java @@ -0,0 +1,85 @@ +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.adapter.Adapter; + +import javax.lang.model.element.Modifier; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.TypeMirror; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class ArrayAdapter extends Adapter { + @Override + public Hydrated instantiate() { + return new Hydrated(); + } + + public class Hydrated extends Adapter.Hydrated { + private ArrayType type; + private TypeMirror componentType; + + @Override + public boolean applies() { + return type != null; + } + + @Override + protected void afterHydrate() { + type = TypeHelper.asArrayType(super.type); + componentType = type == null ? null : type.getComponentType(); + } + + @Override + public void generateWrite(Runnable writeGet) { + code.add("for ($T $N : ", componentType, argName); + writeGet.run(); + code.beginControlFlow(")") + .beginControlFlow("if ($N == null)", argName) + .addStatement("if (writer.getSerializeNulls()) writer.nullValue()") + .nextControlFlow("else"); + generateWrite(code, componentType, argName, componentType.getAnnotationMirrors(), () -> code.add(argName)); + code.endControlFlow().endControlFlow(); + } + + @Override + public void generateRead() { + CodeBlock.Builder kode = CodeBlock.builder(); + // Coerce + kode.beginControlFlow("if (reader.isLenient() && reader.peek() != $T.BEGIN_ARRAY)", Cl.GSON_TOKEN) + .add("return new $T[] { ", componentType); + generateRead(kode, componentType, argName, componentType.getAnnotationMirrors()); + kode.add(" };\n").endControlFlow(); + + kode.addStatement("$T<$T> list = new $T<>()", List.class, componentType, ArrayList.class) + .addStatement("reader.beginArray()") + .beginControlFlow("while (reader.hasNext())") + .beginControlFlow("if (reader.peek() == $T.NULL)", Cl.GSON_TOKEN) + .addStatement("reader.nextNull()") + .addStatement("list.add(null)") + .nextControlFlow("else") + .add("list.add("); + generateRead(kode, componentType, argName, componentType.getAnnotationMirrors()); + kode.add(");\n") + .endControlFlow() + .endControlFlow() + .addStatement("reader.endArray()") + .addStatement("return list.toArray($T[]::new)", componentType); + + String methodName = "read$" + name; + klazz.addMethod( + MethodSpec.methodBuilder(methodName) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .returns(typeName) + .addParameter(Cl.GSON_READER, "reader") + .addException(IOException.class) + .addCode(kode.build()) + .build() + ); + code.add("$N(reader)", methodName); + } + } +} diff --git a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/impl/DeclaredAdapter.java b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/impl/DeclaredAdapter.java new file mode 100644 index 0000000..a44f3ac --- /dev/null +++ b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/impl/DeclaredAdapter.java @@ -0,0 +1,72 @@ +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.adapter.AdapterAdapter; + +import javax.lang.model.element.*; +import javax.lang.model.type.DeclaredType; +import javax.tools.Diagnostic; +import java.util.List; +import java.util.Map; + +public class DeclaredAdapter extends AdapterAdapter { + @Override + public Hydrated instantiate() { + return new Hydrated(); + } + + public class Hydrated extends AdapterAdapter.Hydrated { + private DeclaredType typeAdapterClass; + + @Override + public boolean applies() { + return typeAdapterClass != null; + } + + @Override + protected void afterHydrate() { + this.typeAdapterClass = findTypeAdapterClass(annotations); + } + + @Override + protected String createAdapter(String typeAdapterName) { + if (TypeHelper.isInstance(typeAdapterClass, Cl.TYPE_ADAPTER.toString(), typeUtils)) { + klazz.addField( + FieldSpec.builder(TypeName.get(typeAdapterClass), typeAdapterName) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .initializer("new $T()", typeAdapterClass) + .build() + ); + } else if (TypeHelper.isInstance(typeAdapterClass, Cl.TYPE_ADAPTER_FACTORY.toString(), typeUtils)) { + TypeName typeAdapterType = ParameterizedTypeName.get(Cl.TYPE_ADAPTER, TypeName.get(type).box()); + CodeBlock.Builder block = CodeBlock.builder(); + block.add("new $T().create($T.HOLDER.getGson(), ", typeAdapterClass, Cl.CCORE); + appendFieldTypeToken(false); + block.add(")"); + klazz.addField( + FieldSpec.builder(typeAdapterType, typeAdapterName) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .initializer(block.build()) + .build() + ); + } else message.printMessage(Diagnostic.Kind.ERROR, "@JsonAdapter value must by TypeAdapter or TypeAdapterFactory reference.", sourceElement); + return typeAdapterName; + } + + private static DeclaredType findTypeAdapterClass(List annotations) { + for (AnnotationMirror annotation : annotations) { + String typeName = annotation.getAnnotationType().toString(); + if (typeName.equals(Cl.JSON_ADAPTER.toString())) { + Map elements = annotation.getElementValues(); + if (!elements.isEmpty()) { + AnnotationValue value = elements.values().iterator().next(); + return (DeclaredType) value.getValue(); + } + } + } + return null; + } + } +} diff --git a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/impl/OtherSerializableAdapter.java b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/impl/OtherSerializableAdapter.java new file mode 100644 index 0000000..3b96980 --- /dev/null +++ b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/impl/OtherSerializableAdapter.java @@ -0,0 +1,36 @@ +package io.gitlab.jfronny.gson.compile.processor.adapter.impl; + +import com.squareup.javapoet.TypeName; +import io.gitlab.jfronny.gson.compile.processor.SerializableClass; +import io.gitlab.jfronny.gson.compile.processor.adapter.AdapterAdapter; + +public class OtherSerializableAdapter extends AdapterAdapter { + @Override + public Hydrated instantiate() { + return new Hydrated(); + } + + public class Hydrated extends AdapterAdapter.Hydrated { + private String adapter; + + @Override + public boolean applies() { + return adapter != null; + } + + @Override + protected String createAdapter(String name) { + return adapter; + } + + @Override + protected void afterHydrate() { + for (SerializableClass adapter : other) { + if (TypeName.get(adapter.classElement().asType()).equals(typeName)) { + // Use self-made adapter + this.adapter = adapter.generatedClassName().toString(); + } + } + } + } +} diff --git a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/impl/PrimitiveAdapter.java b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/impl/PrimitiveAdapter.java new file mode 100644 index 0000000..1cb9960 --- /dev/null +++ b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/impl/PrimitiveAdapter.java @@ -0,0 +1,39 @@ +package io.gitlab.jfronny.gson.compile.processor.adapter.impl; + +import io.gitlab.jfronny.gson.compile.processor.adapter.Adapter; + +public class PrimitiveAdapter extends Adapter { + @Override + public Hydrated instantiate() { + return new Hydrated(); + } + + public class Hydrated extends Adapter.Hydrated { + @Override + public boolean applies() { + return unboxedType.getKind().isPrimitive(); + } + + @Override + public void generateWrite(Runnable writeGet) { + code.add("writer.value("); + writeGet.run(); + code.add(");\n"); + } + + @Override + public void generateRead() { + code.add(switch (unboxedType.getKind()) { + case BOOLEAN -> "reader.nextBoolean()"; + case BYTE -> "(byte) reader.nextInt()"; + case SHORT -> "(short) reader.nextInt()"; + case INT -> "reader.nextInt()"; + case LONG -> "reader.nextLong()"; + case CHAR -> "(char) reader.nextInt()"; + case FLOAT -> "(float) reader.nextDouble()"; + case DOUBLE -> "reader.nextDouble()"; + default -> throw new IllegalArgumentException("Unsupported primitive: " + unboxedType.getKind()); + }); + } + } +} diff --git a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/impl/ReflectAdapter.java b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/impl/ReflectAdapter.java new file mode 100644 index 0000000..eef02a1 --- /dev/null +++ b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/impl/ReflectAdapter.java @@ -0,0 +1,39 @@ +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.adapter.AdapterAdapter; + +import javax.lang.model.element.Modifier; +import javax.tools.Diagnostic; + +public class ReflectAdapter extends AdapterAdapter { + @Override + public Hydrated instantiate() { + return new Hydrated(); + } + + public class Hydrated extends AdapterAdapter.Hydrated { + @Override + public boolean applies() { + return !options.containsKey("gsonCompileNoReflect"); + } + + @Override + protected String createAdapter(String typeAdapterName) { + message.printMessage(Diagnostic.Kind.WARNING, "Falling back to adapter detection for unsupported type " + type, sourceElement); + TypeName typeAdapterType = ParameterizedTypeName.get(Cl.TYPE_ADAPTER, typeName); + CodeBlock.Builder block = CodeBlock.builder(); + block.add("$T.HOLDER.getGson().getAdapter(", Cl.CCORE); + appendFieldTypeToken(true); + block.add(")"); + klazz.addField( + FieldSpec.builder(typeAdapterType, typeAdapterName) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .initializer(block.build()) + .build() + ); + return typeAdapterName; + } + } +} diff --git a/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/impl/StringAdapter.java b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/impl/StringAdapter.java new file mode 100644 index 0000000..f74f4bf --- /dev/null +++ b/gson-compile-processor/src/main/java/io/gitlab/jfronny/gson/compile/processor/adapter/impl/StringAdapter.java @@ -0,0 +1,29 @@ +package io.gitlab.jfronny.gson.compile.processor.adapter.impl; + +import io.gitlab.jfronny.gson.compile.processor.adapter.Adapter; + +public class StringAdapter extends Adapter { + @Override + public Hydrated instantiate() { + return new Hydrated(); + } + + public class Hydrated extends Adapter.Hydrated { + @Override + public boolean applies() { + return type.toString().equals(String.class.getCanonicalName()); + } + + @Override + public void generateWrite(Runnable writeGet) { + code.add("writer.value("); + writeGet.run(); + code.add(");\n"); + } + + @Override + public void generateRead() { + code.add("reader.nextString()"); + } + } +}