From d0a14329b648d381f018fd8f6deb2e33acdd1db2 Mon Sep 17 00:00:00 2001 From: JFronny Date: Wed, 24 Apr 2024 14:14:46 +0200 Subject: [PATCH] feat(serialize-generator): try to support SerializerFor --- .../serialize/generator/AdapterRef.java | 7 +++ .../SerializeGeneratorProcessor.java | 41 +++++++++++--- .../serialize/generator/adapter/Adapter.java | 9 ++-- .../serialize/generator/adapter/Adapters.java | 26 ++++----- .../adapter/impl/InferredAdapter.java | 53 +++++++++++++++++++ .../generator/gprocessor/GProcessor.java | 3 +- .../gprocessor/InstanceProcessor.java | 3 +- .../generator/gprocessor/StaticProcessor.java | 3 +- 8 files changed, 119 insertions(+), 26 deletions(-) create mode 100644 commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/AdapterRef.java create mode 100644 commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/adapter/impl/InferredAdapter.java diff --git a/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/AdapterRef.java b/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/AdapterRef.java new file mode 100644 index 0000000..e213db1 --- /dev/null +++ b/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/AdapterRef.java @@ -0,0 +1,7 @@ +package io.gitlab.jfronny.commons.serialize.generator; + +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +public record AdapterRef(TypeElement in, TypeMirror[] targets, boolean nullSafe) { +} diff --git a/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/SerializeGeneratorProcessor.java b/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/SerializeGeneratorProcessor.java index 0016e12..8c06eec 100644 --- a/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/SerializeGeneratorProcessor.java +++ b/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/SerializeGeneratorProcessor.java @@ -3,6 +3,7 @@ package io.gitlab.jfronny.commons.serialize.generator; import com.squareup.javapoet.*; import io.gitlab.jfronny.commons.StringFormatter; import io.gitlab.jfronny.commons.serialize.databind.api.SerializeWithAdapter; +import io.gitlab.jfronny.commons.serialize.databind.api.SerializerFor; import io.gitlab.jfronny.commons.serialize.generator.adapter.Adapter; import io.gitlab.jfronny.commons.serialize.generator.adapter.Adapters; import io.gitlab.jfronny.commons.serialize.generator.annotations.GPrefer; @@ -31,9 +32,10 @@ import javax.tools.StandardLocation; import java.io.IOException; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; @SupportedSourceVersion(SourceVersion.RELEASE_17) -@SupportedAnnotationTypes2({GSerializable.class, SerializeWithAdapter.class}) +@SupportedAnnotationTypes2({GSerializable.class, SerializeWithAdapter.class, SerializerFor.class}) @SupportedOptions({"serializeProcessorNoReflect", "serializeProcessorDisableSafe"}) public class SerializeGeneratorProcessor extends AbstractProcessor2 { private Map seen; @@ -58,6 +60,7 @@ public class SerializeGeneratorProcessor extends AbstractProcessor2 { @Override public boolean process(Set annotations, RoundEnvironment roundEnvironment) { Set toGenerate = new LinkedHashSet<>(); + Set adapterRefs = new LinkedHashSet<>(); // Gather all serializable types for (TypeElement annotation : annotations) { for (Element element : roundEnvironment.getElementsAnnotatedWith(annotation)) { @@ -127,6 +130,28 @@ public class SerializeGeneratorProcessor extends AbstractProcessor2 { if (bld.nullSafe == null) throw new IllegalArgumentException("Missing annotation parameter: nullSafe"); toGenerate.add(SerializableClass.of((TypeElement) element, bld.adapter, false, null, null, false, false, hasManifold)); + } else if (mirror.getAnnotationType().toString().equals(SerializerFor.class.getCanonicalName())) { + var bld = new Object() { + TypeMirror[] targets = null; + Boolean nullSafe = null; + }; + elements.getElementValuesWithDefaults(mirror).forEach((executableElement, value) -> { + String name = executableElement.getSimpleName().toString(); + switch (name) { + case "targets" -> { + if (bld.targets != null) throw new IllegalArgumentException("Duplicate annotation parameter: targets"); + bld.targets = ((List) value.getValue()).stream().map(o -> (TypeMirror) o).toArray(TypeMirror[]::new); + } + case "nullSafe" -> { + if (bld.nullSafe != null) throw new IllegalArgumentException("Duplicate annotation parameter: nullSafe"); + bld.nullSafe = (Boolean) value.getValue(); + } + default -> throw new IllegalArgumentException("Unexpected annotation parameter: " + name); + } + }); + if (bld.targets == null) throw new IllegalArgumentException("Missing annotation parameter: targets"); + if (bld.nullSafe == null) throw new IllegalArgumentException("Missing annotation parameter: nullSafe"); + adapterRefs.add(new AdapterRef((TypeElement) element, bld.targets, bld.nullSafe)); } else { throw new IllegalArgumentException("Unexpected annotation: " + mirror.getAnnotationType()); } @@ -138,10 +163,11 @@ public class SerializeGeneratorProcessor extends AbstractProcessor2 { } // Do not allow mutation past this point, especially not from individual process tasks toGenerate = Set.copyOf(toGenerate); + adapterRefs = Set.copyOf(adapterRefs); // Generate adapters for (SerializableClass toProcess : toGenerate) { try { - process(toProcess, toGenerate); + process(toProcess, toGenerate, adapterRefs); } catch (ElementException e) { e.printMessage(message); } @@ -197,13 +223,12 @@ public class SerializeGeneratorProcessor extends AbstractProcessor2 { } } if (!generatedAdapters.isEmpty()) { - for (ClassName name : generatedAdapters) { - - } try { filer.createResource(StandardLocation.SOURCE_OUTPUT, "", "META-INF/services/io.gitlab.jfronny.commons.serialize.databind.api.TypeAdapter") .openWriter() - .append(generatedAdapters.stream().map(ClassName::canonicalName).collect(Collectors.joining("\n"))) + .append(Stream.concat(generatedAdapters.stream(), adapterRefs.stream().map(AdapterRef::in).map(ClassName::get)) + .map(ClassName::canonicalName) + .collect(Collectors.joining("\n"))) .close(); } catch (IOException e) { message.printMessage(Diagnostic.Kind.ERROR, "Could not write service loader file: " + StringFormatter.toString(e)); @@ -219,7 +244,7 @@ public class SerializeGeneratorProcessor extends AbstractProcessor2 { return null; } - private void process(SerializableClass toProcess, Set other) throws ElementException { + private void process(SerializableClass toProcess, Set other, Set refs) throws ElementException { if (seen.containsKey(toProcess.generatedClassName())) return; // Don't process the same class more than once TypeName classType = toProcess.getTypeName(); @@ -245,7 +270,7 @@ public class SerializeGeneratorProcessor extends AbstractProcessor2 { if (toProcess.generateAdapter()) { generatedAdapters.add(processor.generateDelegatingAdapter(spec, classType, toProcess.generatedClassName())); } - processor.generateSerialisation(spec, toProcess, typeVariables, other); //TODO fix type annotations + processor.generateSerialisation(spec, toProcess, typeVariables, other, refs); //TODO fix type annotations } processor.generateAuxiliary(spec, classType, toProcess.configure()); diff --git a/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/adapter/Adapter.java b/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/adapter/Adapter.java index 86c8ce0..c8bbaeb 100644 --- a/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/adapter/Adapter.java +++ b/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/adapter/Adapter.java @@ -4,6 +4,7 @@ import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; +import io.gitlab.jfronny.commons.serialize.generator.AdapterRef; import io.gitlab.jfronny.commons.serialize.generator.SerializableClass; import javax.annotation.processing.Messager; @@ -29,11 +30,12 @@ public abstract class Adapter.Hydrated> { this.options = env.getOptions(); } - public final T hydrate(TypeSpec.Builder klazz, CodeBlock.Builder code, List typeVariables, Set other, TypeMirror type, String propName, List annotations, Element sourceElement) { + public final T hydrate(TypeSpec.Builder klazz, CodeBlock.Builder code, List typeVariables, Set other, Set refs, TypeMirror type, String propName, List annotations, Element sourceElement) { T instance = instantiate(); instance.klazz = klazz; instance.typeVariables = typeVariables; instance.other = other; + instance.refs = refs; instance.type = type; instance.code = code; instance.unboxedType = instance.unbox(type); @@ -51,6 +53,7 @@ public abstract class Adapter.Hydrated> { protected TypeSpec.Builder klazz; protected List typeVariables; protected Set other; + protected Set refs; protected TypeMirror type; protected TypeMirror unboxedType; protected CodeBlock.Builder code; @@ -67,11 +70,11 @@ public abstract class Adapter.Hydrated> { 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); + Adapters.generateRead(klazz, code, typeVariables, other, refs, 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); + Adapters.generateWrite(klazz, code, typeVariables, other, refs, type, name, annotations, sourceElement, message, writeGet); } protected TypeMirror unbox(TypeMirror type) { diff --git a/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/adapter/Adapters.java b/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/adapter/Adapters.java index 7daa6b4..c2fb836 100644 --- a/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/adapter/Adapters.java +++ b/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/adapter/Adapters.java @@ -3,6 +3,7 @@ package io.gitlab.jfronny.commons.serialize.generator.adapter; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; +import io.gitlab.jfronny.commons.serialize.generator.AdapterRef; import io.gitlab.jfronny.commons.serialize.generator.SerializableClass; import io.gitlab.jfronny.commons.serialize.generator.adapter.impl.*; import io.gitlab.jfronny.commons.serialize.generator.core.value.Property; @@ -19,6 +20,7 @@ import java.util.function.Consumer; public class Adapters { public static final List> ADAPTERS = List.of( new DeclaredAdapter(), + new InferredAdapter(), new PrimitiveAdapter(), new StringAdapter(), new DateAdapter(), @@ -30,29 +32,29 @@ public class Adapters { 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 generateRead(Property prop, TypeSpec.Builder klazz, CodeBlock.Builder code, List typeVariables, Set otherAdapters, Set refs, Messager message) { + withAdapter(prop, klazz, code, typeVariables, otherAdapters, refs, 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)); + public static void generateWrite(Property prop, TypeSpec.Builder klazz, CodeBlock.Builder code, List typeVariables, Set otherAdapters, Set refs, Messager message, Runnable writeGet) { + withAdapter(prop, klazz, code, typeVariables, otherAdapters, refs, 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); + private static void withAdapter(Property prop, TypeSpec.Builder klazz, CodeBlock.Builder code, List typeVariables, Set otherAdapters, Set refs, Messager message, Consumer.Hydrated> action) { + withAdapter(klazz, code, typeVariables, otherAdapters, refs, 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 generateRead(TypeSpec.Builder klazz, CodeBlock.Builder code, List typeVariables, Set other, Set refs, TypeMirror type, String propName, List annotations, Element sourceElement, Messager message) { + withAdapter(klazz, code, typeVariables, other, refs, 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)); + public static void generateWrite(TypeSpec.Builder klazz, CodeBlock.Builder code, List typeVariables, Set other, Set refs, TypeMirror type, String propName, List annotations, Element sourceElement, Messager message, Runnable writeGet) { + withAdapter(klazz, code, typeVariables, other, refs, 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) { + private static void withAdapter(TypeSpec.Builder klazz, CodeBlock.Builder code, List typeVariables, Set other, Set refs, 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); + Adapter.Hydrated hydrated = adapter.hydrate(klazz, code, typeVariables, other, refs, type, propName, annotations, sourceElement); if (hydrated.applies()) { action.accept(hydrated); return; diff --git a/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/adapter/impl/InferredAdapter.java b/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/adapter/impl/InferredAdapter.java new file mode 100644 index 0000000..be9951e --- /dev/null +++ b/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/adapter/impl/InferredAdapter.java @@ -0,0 +1,53 @@ +package io.gitlab.jfronny.commons.serialize.generator.adapter.impl; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeName; +import io.gitlab.jfronny.commons.serialize.generator.AdapterRef; +import io.gitlab.jfronny.commons.serialize.generator.adapter.AdapterAdapter; +import io.gitlab.jfronny.commons.tuple.Tuple; + +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; +import java.util.Set; + +public class InferredAdapter extends AdapterAdapter { + @Override + public Hydrated instantiate() { + return new Hydrated(); + } + + public class Hydrated extends AdapterAdapter.Hydrated { + private TypeElement typeAdapterClass; + + @Override + public boolean applies() { + return typeAdapterClass != null; + } + + @Override + protected void afterHydrate() { + var tmp = findTypeAdapterClass(type, refs); + // Ignore nullSafe for now + if (tmp != null) { + this.typeAdapterClass = tmp.left(); + } + } + + @Override + protected TypeName createAdapter(String typeAdapterName) { + return ClassName.get(typeAdapterClass); + } + + private static Tuple findTypeAdapterClass(TypeMirror type, Set refs) { + TypeName tn = TypeName.get(type); + for (AdapterRef ref : refs) { + for (TypeMirror target : ref.targets()) { + if (tn.equals(TypeName.get(target))) { + return Tuple.of(ref.in(), ref.nullSafe()); + } + } + } + return null; + } + } +} diff --git a/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/gprocessor/GProcessor.java b/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/gprocessor/GProcessor.java index 2a2160c..2ea88f9 100644 --- a/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/gprocessor/GProcessor.java +++ b/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/gprocessor/GProcessor.java @@ -1,6 +1,7 @@ package io.gitlab.jfronny.commons.serialize.generator.gprocessor; import com.squareup.javapoet.*; +import io.gitlab.jfronny.commons.serialize.generator.AdapterRef; import io.gitlab.jfronny.commons.serialize.generator.Cl; import io.gitlab.jfronny.commons.serialize.generator.SerializableClass; import io.gitlab.jfronny.commons.serialize.generator.core.value.ElementException; @@ -47,7 +48,7 @@ public abstract class GProcessor { } public abstract ClassName generateDelegatingAdapter(TypeSpec.Builder spec, TypeName classType, ClassName generatedClassName); - public abstract void generateSerialisation(TypeSpec.Builder spec, SerializableClass self, List typeVariables, Set otherAdapters) throws ElementException; + public abstract void generateSerialisation(TypeSpec.Builder spec, SerializableClass self, List typeVariables, Set otherAdapters, Set refs) throws ElementException; protected boolean isIgnored(Property property) { for (AnnotationMirror annotationMirror : property.getAnnotations()) { diff --git a/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/gprocessor/InstanceProcessor.java b/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/gprocessor/InstanceProcessor.java index 717a0bf..ef9642c 100644 --- a/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/gprocessor/InstanceProcessor.java +++ b/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/gprocessor/InstanceProcessor.java @@ -1,6 +1,7 @@ package io.gitlab.jfronny.commons.serialize.generator.gprocessor; import com.squareup.javapoet.*; +import io.gitlab.jfronny.commons.serialize.generator.AdapterRef; import io.gitlab.jfronny.commons.serialize.generator.Cl; import io.gitlab.jfronny.commons.serialize.generator.SerializableClass; import io.gitlab.jfronny.commons.serialize.generator.adapter.Adapters; @@ -59,7 +60,7 @@ public class InstanceProcessor extends GProcessor { // (Or, alternatively, create one common solution for these) // !!!WARNING!!! @Override - public void generateSerialisation(TypeSpec.Builder spec, SerializableClass self, List typeVariables, Set otherAdapters) throws ElementException { + public void generateSerialisation(TypeSpec.Builder spec, SerializableClass self, List typeVariables, Set otherAdapters, Set refs) throws ElementException { Value value = self.builder() == null ? valueCreator.from(self.classElement(), false) : valueCreator.from(TypeHelper.asDeclaredType(self.builder()).asElement(), true); ConstructionSource constructionSource = value.getConstructionSource(); Properties properties = value.getProperties(); diff --git a/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/gprocessor/StaticProcessor.java b/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/gprocessor/StaticProcessor.java index c28fc8e..6173e71 100644 --- a/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/gprocessor/StaticProcessor.java +++ b/commons-serialize-generator/src/main/java/io/gitlab/jfronny/commons/serialize/generator/gprocessor/StaticProcessor.java @@ -1,6 +1,7 @@ package io.gitlab.jfronny.commons.serialize.generator.gprocessor; import com.squareup.javapoet.*; +import io.gitlab.jfronny.commons.serialize.generator.AdapterRef; import io.gitlab.jfronny.commons.serialize.generator.Cl; import io.gitlab.jfronny.commons.serialize.generator.SerializableClass; import io.gitlab.jfronny.commons.serialize.generator.adapter.Adapters; @@ -30,7 +31,7 @@ public class StaticProcessor extends GProcessor { // (Or, alternatively, create one common solution for these) // !!!WARNING!!! @Override - public void generateSerialisation(TypeSpec.Builder spec, SerializableClass self, List typeVariables, Set otherAdapters) throws ElementException { + public void generateSerialisation(TypeSpec.Builder spec, SerializableClass self, List typeVariables, Set otherAdapters, Set refs) throws ElementException { Value value = valueCreator.fromStatic(self.classElement()); Properties properties = value.getProperties();