Support for nested types within same sources

This commit is contained in:
Johannes Frohnmeyer 2022-11-01 10:29:09 +01:00
parent 1da2263bef
commit 37bc7f9782
Signed by: Johannes
GPG Key ID: E76429612C2929F4
4 changed files with 40 additions and 20 deletions

View File

@ -30,4 +30,4 @@ The goal of this AP is to
## Credit
The Gson-Compile processor is based on [gsonvalue](https://github.com/evant/gsonvalue) and [value-processor](https://github.com/evant/value-processor) by Eva Tatarka.
The API was inspired by [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) by Jetbrains
The serialization is based on [my fork](https://gitlab.com/JFronny/gson-comments) of [gson](https://github.com/google/gson) by Google
The serialization is powered by [my fork](https://gitlab.com/JFronny/gson-comments) of [gson](https://github.com/google/gson) by Google

View File

@ -5,5 +5,5 @@ import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface GComment {
String value() default "";
String value();
}

View File

@ -21,5 +21,13 @@ public class Main {
public String getBass() {
return "Yes";
}
public ExamplePojo2 nested;
}
@GSerializable
public static class ExamplePojo2 {
@GComment("Yes!")
public boolean primitive;
}
}

View File

@ -47,6 +47,7 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
Set<ToProcess> toProcesses = new LinkedHashSet<>();
// Gather all serializable types
for (TypeElement annotation : annotations) {
for (Element element : roundEnvironment.getElementsAnnotatedWith(annotation)) {
for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
@ -73,14 +74,21 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
if (bld.generateAdapter == null) throw new IllegalArgumentException("Missing annotation parameter: generateAdapter");
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!");
toProcesses.add(new ToProcess((TypeElement) element, bld.with, bld.generateAdapter));
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));
}
}
}
}
// Do not allow mutation past this point, especially not from individual process tasks
toProcesses = Set.copyOf(toProcesses);
// Generate adapters
for (ToProcess toProcess : toProcesses) {
try {
process(toProcess.element, toProcess.adapter, toProcess.generateAdapter);
process(toProcess.element, toProcess.generatedClassName, toProcess.adapter, toProcess.generateAdapter, toProcesses);
} catch (IOException | ElementException e) {
message.printMessage(Diagnostic.Kind.ERROR, "GsonCompile threw an exception: " + StringFormatter.toString(e), toProcess.element);
}
@ -88,13 +96,12 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
return false;
}
record ToProcess(TypeElement element, @Nullable TypeMirror adapter, boolean generateAdapter) {}
record ToProcess(TypeElement element, ClassName generatedClassName, @Nullable TypeMirror adapter, boolean generateAdapter) {}
private void process(TypeElement classElement, @Nullable TypeMirror adapter, boolean generateAdapter) throws IOException, ElementException {
private void process(TypeElement classElement, ClassName generatedClassName, @Nullable TypeMirror adapter, boolean generateAdapter, Set<ToProcess> other) throws IOException, ElementException {
ClassName className = ClassName.get(classElement);
if (!seen.add(className)) return; // Don't process the same class more than once
ClassName generatedClassName = ClassName.get(className.packageName(), Const.PREFIX + String.join("_", className.simpleNames()));
TypeName classType = TypeName.get(classElement.asType());
List<TypeVariableName> typeVariables = new ArrayList<>();
if (classType instanceof ParameterizedTypeName type) {
@ -114,13 +121,11 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
if (generateAdapter) {
generateDelegatingAdapter(spec, classType, generatedClassName);
}
generateSerialisation(spec, classType, classElement, typeVariables);
generateSerialisation(spec, classType, classElement, typeVariables, other);
}
generateAuxiliary(spec, classType);
//TODO register adapter as available and use in generated adapters
JavaFile javaFile = JavaFile.builder(className.packageName(), spec.build())
.skipJavaLangImports(true)
.indent(" ")
@ -264,7 +269,7 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
);
}
private void generateSerialisation(TypeSpec.Builder spec, TypeName classType, TypeElement classElement, List<TypeVariableName> typeVariables) throws ElementException {
private void generateSerialisation(TypeSpec.Builder spec, TypeName classType, TypeElement classElement, List<TypeVariableName> typeVariables, Set<ToProcess> otherAdapters) throws ElementException {
Value value = valueCreator.from(classElement);
ConstructionSource constructionSource = value.getConstructionSource();
Properties properties = value.getProperties();
@ -281,12 +286,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);
generateWrite(field, spec, code, "writer", "value.$N", 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);
generateWrite(getter, spec, code, "writer", "value.$N()", typeVariables, otherAdapters);
}
code.addStatement("writer.endObject()");
@ -319,7 +324,7 @@ 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);
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);
@ -381,7 +386,7 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
}
}
private void generateWrite(Property<?> prop, TypeSpec.Builder klazz, CodeBlock.Builder code, String writer, String get, List<TypeVariableName> typeVariables) {
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)) {
@ -397,10 +402,10 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
}
//TODO handle lists, sets, arrays, other common types (-> https://github.com/bluelinelabs/LoganSquare/blob/development/docs/TypeConverters.md)
//TODO support comment annotations
code.addStatement(getAdapter(prop, klazz, typeVariables) + ".write(" + writer + ", " + get + ")", prop.getCallableName());
code.addStatement(getAdapter(prop, klazz, typeVariables, otherAdapters) + ".write(" + writer + ", " + get + ")", prop.getCallableName());
}
private String generateRead(Property<?> prop, TypeSpec.Builder klazz, String reader, List<TypeVariableName> typeVariables) {
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()) {
@ -419,10 +424,10 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
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) + ".read(" + reader + ")";
return getAdapter(prop, klazz, typeVariables, otherAdapters) + ".read(" + reader + ")";
}
private String getAdapter(Property<?> prop, TypeSpec.Builder klazz, List<TypeVariableName> typeVariables) {
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;
@ -450,9 +455,16 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
);
} 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());
//TODO handle known custom type adapters and return proper static class
TypeName typeAdapterType = ParameterizedTypeName.get(Const.TYPE_ADAPTER, TypeName.get(prop.getType()).box());
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);