Refactor adapters into separate classes
This commit is contained in:
parent
f7850f9109
commit
096ead18f1
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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()");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue