Refactor adapters into separate classes

This commit is contained in:
Johannes Frohnmeyer 2022-11-01 11:18:35 +01:00
parent f7850f9109
commit 096ead18f1
Signed by: Johannes
GPG Key ID: E76429612C2929F4
6 changed files with 386 additions and 242 deletions

View File

@ -3,20 +3,19 @@ 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.util.AbstractProcessor2;
import io.gitlab.jfronny.gson.compile.processor.util.SupportedAnnotationTypes2;
import io.gitlab.jfronny.gson.compile.processor.util.valueprocessor.*;
import io.gitlab.jfronny.gson.compile.processor.util.valueprocessor.Properties;
import org.jetbrains.annotations.Nullable;
import io.gitlab.jfronny.gson.compile.processor.util.valueprocessor.*;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.type.*;
import javax.lang.model.util.*;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import java.io.*;
import java.lang.reflect.ParameterizedType;
import java.util.*;
import java.util.stream.Collectors;
@ -24,29 +23,30 @@ import java.util.stream.Collectors;
@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<Adapter> ADAPTERS = List.of(new PrimitiveAdapter(), new StringAdapter(), new AdapterAdapter());
private Messager message;
private Types typeUtils;
private Filer filer;
private Set<ClassName> seen;
private ValueCreator valueCreator;
private Elements elements;
private Map<String, String> options;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
message = processingEnv.getMessager();
filer = processingEnv.getFiler();
typeUtils = processingEnv.getTypeUtils();
elements = processingEnv.getElementUtils();
options = processingEnv.getOptions();
seen = new LinkedHashSet<>();
valueCreator = new ValueCreator(processingEnv);
for (Adapter adapter : ADAPTERS) {
adapter.init(processingEnv);
}
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
Set<ToProcess> toProcesses = new LinkedHashSet<>();
Set<SerializableClass> toGenerate = new LinkedHashSet<>();
// Gather all serializable types
for (TypeElement annotation : annotations) {
for (Element element : roundEnvironment.getElementsAnnotatedWith(annotation)) {
@ -75,34 +75,29 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
if (bld.with.toString().equals("void")) bld.with = null;
if (bld.with != null && bld.generateAdapter) throw new IllegalArgumentException("Adapter for " + element + " already exists, not generating another!");
TypeElement typeElement = (TypeElement) element;
ClassName className = ClassName.get(typeElement);
ClassName generatedClassName = ClassName.get(className.packageName(), Const.PREFIX + String.join("_", className.simpleNames()));
toProcesses.add(new ToProcess(typeElement, generatedClassName, bld.with, bld.generateAdapter));
toGenerate.add(SerializableClass.of((TypeElement) element, bld.with, bld.generateAdapter));
}
}
}
}
// Do not allow mutation past this point, especially not from individual process tasks
toProcesses = Set.copyOf(toProcesses);
toGenerate = Set.copyOf(toGenerate);
// Generate adapters
for (ToProcess toProcess : toProcesses) {
for (SerializableClass toProcess : toGenerate) {
try {
process(toProcess.element, toProcess.generatedClassName, toProcess.adapter, toProcess.generateAdapter, toProcesses);
process(toProcess, toGenerate);
} catch (IOException | ElementException e) {
message.printMessage(Diagnostic.Kind.ERROR, "GsonCompile threw an exception: " + StringFormatter.toString(e), toProcess.element);
message.printMessage(Diagnostic.Kind.ERROR, "GsonCompile threw an exception: " + StringFormatter.toString(e), toProcess.classElement());
}
}
return false;
}
record ToProcess(TypeElement element, ClassName generatedClassName, @Nullable TypeMirror adapter, boolean generateAdapter) {}
private void process(TypeElement classElement, ClassName generatedClassName, @Nullable TypeMirror adapter, boolean generateAdapter, Set<ToProcess> other) throws IOException, ElementException {
ClassName className = ClassName.get(classElement);
private void process(SerializableClass toProcess, Set<SerializableClass> other) throws IOException, ElementException {
ClassName className = toProcess.getClassName();
if (!seen.add(className)) return; // Don't process the same class more than once
TypeName classType = TypeName.get(classElement.asType());
TypeName classType = toProcess.getTypeName();
List<TypeVariableName> typeVariables = new ArrayList<>();
if (classType instanceof ParameterizedTypeName type) {
for (TypeName argument : type.typeArguments) {
@ -110,18 +105,18 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
}
}
TypeSpec.Builder spec = TypeSpec.classBuilder(generatedClassName.simpleName())
.addOriginatingElement(classElement)
TypeSpec.Builder spec = TypeSpec.classBuilder(toProcess.generatedClassName().simpleName())
.addOriginatingElement(toProcess.classElement())
.addTypeVariables(typeVariables)
.addModifiers(Modifier.PUBLIC);
if (adapter != null) {
generateDelegateToAdapter(spec, classType, adapter);
if (toProcess.adapter() != null) {
generateDelegateToAdapter(spec, classType, toProcess.adapter());
} else {
if (generateAdapter) {
generateDelegatingAdapter(spec, classType, generatedClassName);
if (toProcess.generateAdapter()) {
generateDelegatingAdapter(spec, classType, toProcess.generatedClassName());
}
generateSerialisation(spec, classType, classElement, typeVariables, other);
generateSerialisation(spec, classType, toProcess.classElement(), typeVariables, other);
}
generateAuxiliary(spec, classType);
@ -269,7 +264,7 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
);
}
private void generateSerialisation(TypeSpec.Builder spec, TypeName classType, TypeElement classElement, List<TypeVariableName> typeVariables, Set<ToProcess> otherAdapters) throws ElementException {
private void generateSerialisation(TypeSpec.Builder spec, TypeName classType, TypeElement classElement, List<TypeVariableName> typeVariables, Set<SerializableClass> otherAdapters) throws ElementException {
Value value = valueCreator.from(classElement);
ConstructionSource constructionSource = value.getConstructionSource();
Properties properties = value.getProperties();
@ -286,12 +281,12 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
for (Property.Field field : properties.fields) {
generateComments(field, code);
code.addStatement("writer.name($S)", getSerializedName(field));
generateWrite(field, spec, code, "writer", "value.$N", typeVariables, otherAdapters);
generateWrite(field, spec, code, () -> code.add("value.$N", field.getCallableName()), typeVariables, otherAdapters);
}
for (Property.Getter getter : properties.getters) {
generateComments(getter, code);
code.addStatement("writer.name($S)", getSerializedName(getter));
generateWrite(getter, spec, code, "writer", "value.$N()", typeVariables, otherAdapters);
generateWrite(getter, spec, code, () -> code.add("value.$N()", getter.getCallableName()), typeVariables, otherAdapters);
}
code.addStatement("writer.endObject()");
@ -324,16 +319,18 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
.beginControlFlow("while (reader.hasNext())")
.beginControlFlow("switch (reader.nextName())");
for (Property<?> param : properties.names) {
String read = generateRead(param, spec, "reader", typeVariables, otherAdapters);
if (param.getType().getKind().isPrimitive()) {
code.add("case $S -> ", getSerializedName(param));
code.addStatement("$L = $L", Const.ARG_PREFIX + param.getName(), read);
code.add("case $S -> $L = ", getSerializedName(param), Const.ARG_PREFIX + param.getName());
generateRead(param, spec, code, typeVariables, otherAdapters);
code.add(";\n");
} else {
code.beginControlFlow("case $S ->", getSerializedName(param))
.beginControlFlow("if (reader.peek() == $T.NULL)", Const.GSON_TOKEN)
.addStatement("reader.nextNull()")
.addStatement("$L = null", Const.ARG_PREFIX + param.getName())
.endControlFlow("else $L = $L", Const.ARG_PREFIX + param.getName(), read)
.addStatement("$L = null", Const.ARG_PREFIX + param.getName());
code.unindent().add("} else $L = ", Const.ARG_PREFIX + param.getName());
generateRead(param, spec, code, typeVariables, otherAdapters);
code.add(";\n")
.endControlFlow();
}
}
@ -386,179 +383,36 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
}
}
private void generateWrite(Property<?> prop, TypeSpec.Builder klazz, CodeBlock.Builder code, String writer, String get, List<TypeVariableName> typeVariables, Set<ToProcess> otherAdapters) {
TypeMirror unboxed = unbox(prop.getType());
if (unbox(prop.getType()).getKind().isPrimitive() || prop.getType().toString().equals(String.class.getCanonicalName())) {
if (prop.getType().equals(unboxed)) {
code.addStatement("$L.value(" + get + ")", writer, prop.getCallableName());
} else {
String pName = Const.ARG_PREFIX + prop.getName();
code.addStatement("$T $L = " + get, prop.getType(), pName, prop.getCallableName())
.beginControlFlow("if ($L == null)", pName)
.addStatement("$L.nullValue()", writer)
.endControlFlow("else $L.value($L)", writer, pName);
private void generateWrite(Property<?> prop, TypeSpec.Builder klazz, CodeBlock.Builder code, Runnable writeGet, List<TypeVariableName> typeVariables, Set<SerializableClass> otherAdapters) {
for (Adapter adapter : ADAPTERS) {
adapter.hydrate(prop, klazz, code, typeVariables, otherAdapters);
if (adapter.applies()) {
adapter.generateWrite(writeGet);
}
adapter.dehydrate();
return;
}
//TODO handle lists, sets, arrays, other common types (-> https://github.com/bluelinelabs/LoganSquare/blob/development/docs/TypeConverters.md)
code.addStatement(getAdapter(prop, klazz, typeVariables, otherAdapters) + ".write(" + writer + ", " + get + ")", prop.getCallableName());
throw new IllegalArgumentException("Could not find adapter for property " + prop);
}
private String generateRead(Property<?> prop, TypeSpec.Builder klazz, String reader, List<TypeVariableName> typeVariables, Set<ToProcess> otherAdapters) {
TypeMirror unboxed = unbox(prop.getType());
if (unboxed.getKind().isPrimitive()) {
return switch (unboxed.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: " + unboxed.getKind());
};
private void generateRead(Property<?> prop, TypeSpec.Builder klazz, CodeBlock.Builder code, List<TypeVariableName> typeVariables, Set<SerializableClass> otherAdapters) {
for (Adapter adapter : ADAPTERS) {
adapter.hydrate(prop, klazz, code, typeVariables, otherAdapters);
if (adapter.applies()) {
adapter.generateRead();
adapter.dehydrate();
return;
} else adapter.dehydrate();
}
if (prop.getType().toString().equals(String.class.getCanonicalName())) {
return reader + ".nextString()";
}
//TODO handle lists, sets, arrays, other common types (-> https://github.com/bluelinelabs/LoganSquare/blob/development/docs/TypeConverters.md)
return getAdapter(prop, klazz, typeVariables, otherAdapters) + ".read(" + reader + ")";
}
private String getAdapter(Property<?> prop, TypeSpec.Builder klazz, List<TypeVariableName> typeVariables, Set<ToProcess> otherAdapters) {
String typeAdapterName = Const.ADAPTER_PREFIX + prop.getName();
for (FieldSpec spec : klazz.fieldSpecs) {
if (spec.name.equals(typeAdapterName)) return typeAdapterName;
}
DeclaredType typeAdapterClass = findTypeAdapterClass(prop.getAnnotations());
if (typeAdapterClass != null) {
if (isInstance(typeAdapterClass, Const.TYPE_ADAPTER.toString())) {
klazz.addField(
FieldSpec.builder(TypeName.get(typeAdapterClass), typeAdapterName)
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.initializer("new $T()", typeAdapterClass)
.build()
);
} else if (isInstance(typeAdapterClass, Const.TYPE_ADAPTER_FACTORY.toString())) {
TypeName typeAdapterType = ParameterizedTypeName.get(Const.TYPE_ADAPTER, TypeName.get(prop.getType()).box());
CodeBlock.Builder block = CodeBlock.builder();
block.add("new $T().create($T.HOLDER.getGson(), ", typeAdapterClass, Const.CCORE);
appendFieldTypeToken(block, prop, typeVariables, 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.", prop.getElement());
} else {
TypeName targetType = TypeName.get(prop.getType()).box();
for (ToProcess adapter : otherAdapters) {
if (TypeName.get(adapter.element.asType()).equals(targetType)) {
// Use self-made adapter
return adapter.generatedClassName.toString();
}
}
message.printMessage(options.containsKey("gsonCompileNoReflect") ? Diagnostic.Kind.ERROR : Diagnostic.Kind.WARNING, "Falling back to adapter detection for unsupported type " + prop.getType(), prop.getElement());
TypeName typeAdapterType = ParameterizedTypeName.get(Const.TYPE_ADAPTER, targetType);
CodeBlock.Builder block = CodeBlock.builder();
block.add("$T.HOLDER.getGson().getAdapter(", Const.CCORE);
appendFieldTypeToken(block, prop, typeVariables, true);
block.add(")");
klazz.addField(
FieldSpec.builder(typeAdapterType, typeAdapterName)
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.initializer(block.build())
.build()
);
}
return typeAdapterName;
}
private void appendFieldTypeToken(CodeBlock.Builder block, Property<?> property, List<TypeVariableName> typeVariables, boolean allowClassType) {
TypeMirror type = property.getType();
TypeName typeName = TypeName.get(type);
if (isComplexType(type)) {
TypeName typeTokenType = ParameterizedTypeName.get(Const.TYPE_TOKEN, typeName);
List<? extends TypeMirror> typeParams = getGenericTypes(type);
if (typeParams.isEmpty()) {
block.add("new $T() {}", typeTokenType);
} else {
block.add("($T) $T.getParameterized($T.class, ", typeTokenType, Const.TYPE_TOKEN, typeUtils.erasure(type));
for (Iterator<? extends TypeMirror> iterator = typeParams.iterator(); iterator.hasNext(); ) {
TypeMirror typeParam = iterator.next();
int typeIndex = typeVariables.indexOf(TypeVariableName.get(typeParam.toString()));
block.add("(($T)typeToken.getType()).getActualTypeArguments()[$L]", ParameterizedType.class, typeIndex);
if (iterator.hasNext()) {
block.add(", ");
}
}
block.add(")");
}
} else if (isGenericType(type)) {
TypeName typeTokenType = ParameterizedTypeName.get(Const.TYPE_TOKEN, typeName);
int typeIndex = typeVariables.indexOf(TypeVariableName.get(property.getType().toString()));
block.add("($T) $T.get((($T)typeToken.getType()).getActualTypeArguments()[$L])",
typeTokenType, Const.TYPE_TOKEN, ParameterizedType.class, typeIndex);
} else {
if (allowClassType) {
block.add("$T.class", typeName);
} else {
block.add("TypeToken.get($T.class)", typeName);
}
}
}
private boolean isComplexType(TypeMirror type) {
Element element = typeUtils.asElement(type);
if (!(element instanceof TypeElement typeElement)) return false;
return !typeElement.getTypeParameters().isEmpty();
}
private boolean isGenericType(TypeMirror type) {
return type.getKind() == TypeKind.TYPEVAR;
}
private List<? extends TypeMirror> getGenericTypes(TypeMirror type) {
DeclaredType declaredType = asDeclaredType(type);
if (declaredType == null) {
return Collections.emptyList();
}
ArrayList<TypeMirror> result = new ArrayList<>();
for (TypeMirror argType : declaredType.getTypeArguments()) {
if (argType.getKind() == TypeKind.TYPEVAR) {
result.add(argType);
}
}
return result;
throw new IllegalArgumentException("Could not find adapter for property " + prop);
}
private static String getDefaultValue(TypeMirror type) {
switch (type.getKind()) {
case BYTE:
case SHORT:
case INT:
case LONG:
case FLOAT:
case CHAR:
case DOUBLE:
return "0";
case BOOLEAN:
return "false";
default:
return "null";
}
}
private TypeMirror unbox(TypeMirror type) {
try {
return typeUtils.unboxedType(type);
} catch (IllegalArgumentException e) {
return 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) {
@ -569,44 +423,4 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
}
return property.getName();
}
private DeclaredType findTypeAdapterClass(List<? extends AnnotationMirror> annotations) {
for (AnnotationMirror annotation : annotations) {
String typeName = annotation.getAnnotationType().toString();
if (typeName.equals(Const.JSON_ADAPTER.toString())) {
Map<? extends ExecutableElement, ? extends AnnotationValue> elements = annotation.getElementValues();
if (!elements.isEmpty()) {
AnnotationValue value = elements.values().iterator().next();
return (DeclaredType) value.getValue();
}
}
}
return null;
}
private boolean isInstance(DeclaredType type, String parentClassName) {
if (type == null) return false;
TypeElement element = (TypeElement) type.asElement();
for (TypeMirror interfaceType : element.getInterfaces()) {
if (typeUtils.erasure(interfaceType).toString().equals(parentClassName)) return true;
}
TypeMirror superclassType = element.getSuperclass();
if (superclassType != null) {
if (typeUtils.erasure(superclassType).toString().equals(parentClassName)) {
return true;
} else {
return isInstance(asDeclaredType(superclassType), parentClassName);
}
}
return false;
}
private static DeclaredType asDeclaredType(TypeMirror type) {
return type.accept(new SimpleTypeVisitor14<>() {
@Override
public DeclaredType visitDeclared(DeclaredType t, Object o) {
return t;
}
}, null);
}
}

View File

@ -0,0 +1,24 @@
package io.gitlab.jfronny.gson.compile.processor;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.TypeName;
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, 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()));
return new SerializableClass(element, generatedClassName, with, generateAdapter);
}
public ClassName getClassName() {
return ClassName.get(classElement);
}
public TypeName getTypeName() {
return TypeName.get(classElement.asType());
}
}

View File

@ -0,0 +1,62 @@
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.type.TypeMirror;
import javax.lang.model.util.Types;
import java.util.*;
public abstract class Adapter {
protected Property<?> property;
protected TypeSpec.Builder klazz;
protected List<TypeVariableName> typeVariables;
protected Set<SerializableClass> other;
protected TypeMirror type;
protected TypeMirror unboxedType;
protected CodeBlock.Builder code;
protected String argName;
protected Messager message;
protected Types typeUtils;
protected Map<String, String> options;
public abstract boolean applies();
public abstract void generateWrite(Runnable writeGet);
public abstract void generateRead();
public void init(ProcessingEnvironment env) {
this.message = env.getMessager();
this.typeUtils = env.getTypeUtils();
this.options = env.getOptions();
}
public void hydrate(Property<?> property, TypeSpec.Builder klazz, CodeBlock.Builder code, List<TypeVariableName> typeVariables, Set<SerializableClass> other) {
this.property = property;
this.klazz = klazz;
this.typeVariables = typeVariables;
this.other = other;
this.type = property.getType();
this.code = code;
try {
this.unboxedType = typeUtils.unboxedType(type);
} catch (IllegalArgumentException e) {
this.unboxedType = type;
}
this.argName = Const.ARG_PREFIX + this.property.getName();
}
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;
}
}

View File

@ -0,0 +1,182 @@
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.lang.model.element.*;
import javax.lang.model.type.*;
import javax.lang.model.util.SimpleTypeVisitor14;
import javax.tools.Diagnostic;
import java.lang.reflect.ParameterizedType;
import java.util.*;
public class AdapterAdapter extends Adapter {
@Override
public boolean applies() {
return true;
}
@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;
}
DeclaredType typeAdapterClass = findTypeAdapterClass(property.getAnnotations());
if (typeAdapterClass != null) {
if (isInstance(typeAdapterClass, Const.TYPE_ADAPTER.toString())) {
klazz.addField(
FieldSpec.builder(TypeName.get(typeAdapterClass), typeAdapterName)
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.initializer("new $T()", typeAdapterClass)
.build()
);
} else if (isInstance(typeAdapterClass, Const.TYPE_ADAPTER_FACTORY.toString())) {
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(block, property, typeVariables, 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());
} else {
TypeName targetType = TypeName.get(type).box();
for (SerializableClass adapter : other) {
if (TypeName.get(adapter.classElement().asType()).equals(targetType)) {
// Use self-made adapter
return adapter.generatedClassName().toString();
}
}
message.printMessage(options.containsKey("gsonCompileNoReflect") ? Diagnostic.Kind.ERROR : Diagnostic.Kind.WARNING, "Falling back to adapter detection for unsupported type " + type, property.getElement());
TypeName typeAdapterType = ParameterizedTypeName.get(Const.TYPE_ADAPTER, targetType);
CodeBlock.Builder block = CodeBlock.builder();
block.add("$T.HOLDER.getGson().getAdapter(", Const.CCORE);
appendFieldTypeToken(block, property, typeVariables, true);
block.add(")");
klazz.addField(
FieldSpec.builder(typeAdapterType, typeAdapterName)
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.initializer(block.build())
.build()
);
}
return typeAdapterName;
}
private void appendFieldTypeToken(CodeBlock.Builder block, Property<?> property, List<TypeVariableName> typeVariables, boolean allowClassType) {
TypeMirror type = property.getType();
TypeName typeName = TypeName.get(type);
if (isComplexType(type)) {
TypeName typeTokenType = ParameterizedTypeName.get(Const.TYPE_TOKEN, typeName);
List<? extends TypeMirror> typeParams = getGenericTypes(type);
if (typeParams.isEmpty()) {
block.add("new $T() {}", typeTokenType);
} else {
block.add("($T) $T.getParameterized($T.class, ", typeTokenType, Const.TYPE_TOKEN, typeUtils.erasure(type));
for (Iterator<? extends TypeMirror> iterator = typeParams.iterator(); iterator.hasNext(); ) {
TypeMirror typeParam = iterator.next();
int typeIndex = typeVariables.indexOf(TypeVariableName.get(typeParam.toString()));
block.add("(($T)typeToken.getType()).getActualTypeArguments()[$L]", ParameterizedType.class, typeIndex);
if (iterator.hasNext()) {
block.add(", ");
}
}
block.add(")");
}
} else if (isGenericType(type)) {
TypeName typeTokenType = ParameterizedTypeName.get(Const.TYPE_TOKEN, typeName);
int typeIndex = typeVariables.indexOf(TypeVariableName.get(property.getType().toString()));
block.add("($T) $T.get((($T)typeToken.getType()).getActualTypeArguments()[$L])",
typeTokenType, Const.TYPE_TOKEN, ParameterizedType.class, typeIndex);
} else {
if (allowClassType) {
block.add("$T.class", typeName);
} else {
block.add("TypeToken.get($T.class)", typeName);
}
}
}
private boolean isComplexType(TypeMirror type) {
Element element = typeUtils.asElement(type);
if (!(element instanceof TypeElement typeElement)) return false;
return !typeElement.getTypeParameters().isEmpty();
}
private boolean isGenericType(TypeMirror type) {
return type.getKind() == TypeKind.TYPEVAR;
}
private List<? extends TypeMirror> getGenericTypes(TypeMirror type) {
DeclaredType declaredType = asDeclaredType(type);
if (declaredType == null) {
return Collections.emptyList();
}
ArrayList<TypeMirror> result = new ArrayList<>();
for (TypeMirror argType : declaredType.getTypeArguments()) {
if (argType.getKind() == TypeKind.TYPEVAR) {
result.add(argType);
}
}
return result;
}
private DeclaredType findTypeAdapterClass(List<? extends AnnotationMirror> annotations) {
for (AnnotationMirror annotation : annotations) {
String typeName = annotation.getAnnotationType().toString();
if (typeName.equals(Const.JSON_ADAPTER.toString())) {
Map<? extends ExecutableElement, ? extends AnnotationValue> elements = annotation.getElementValues();
if (!elements.isEmpty()) {
AnnotationValue value = elements.values().iterator().next();
return (DeclaredType) value.getValue();
}
}
}
return null;
}
private boolean isInstance(DeclaredType type, String parentClassName) {
if (type == null) return false;
TypeElement element = (TypeElement) type.asElement();
for (TypeMirror interfaceType : element.getInterfaces()) {
if (typeUtils.erasure(interfaceType).toString().equals(parentClassName)) return true;
}
TypeMirror superclassType = element.getSuperclass();
if (superclassType != null) {
if (typeUtils.erasure(superclassType).toString().equals(parentClassName)) {
return true;
} else {
return isInstance(asDeclaredType(superclassType), parentClassName);
}
}
return false;
}
private static DeclaredType asDeclaredType(TypeMirror type) {
return type.accept(new SimpleTypeVisitor14<>() {
@Override
public DeclaredType visitDeclared(DeclaredType t, Object o) {
return t;
}
}, null);
}
}

View File

@ -0,0 +1,39 @@
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());
});
}
}

View File

@ -0,0 +1,23 @@
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 = ", typeVariables, 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()");
}
}