feat(serialize-generator): try to support SerializerFor
ci/woodpecker/push/woodpecker Pipeline failed Details

This commit is contained in:
Johannes Frohnmeyer 2024-04-24 14:14:46 +02:00
parent 1a436343da
commit d0a14329b6
Signed by: Johannes
GPG Key ID: E76429612C2929F4
8 changed files with 119 additions and 26 deletions

View File

@ -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) {
}

View File

@ -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<ClassName, TypeSpec.Builder> seen;
@ -58,6 +60,7 @@ public class SerializeGeneratorProcessor extends AbstractProcessor2 {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
Set<SerializableClass> toGenerate = new LinkedHashSet<>();
Set<AdapterRef> 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<SerializableClass> other) throws ElementException {
private void process(SerializableClass toProcess, Set<SerializableClass> other, Set<AdapterRef> 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());

View File

@ -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<T extends Adapter<T>.Hydrated> {
this.options = env.getOptions();
}
public final T hydrate(TypeSpec.Builder klazz, CodeBlock.Builder code, List<TypeVariableName> typeVariables, Set<SerializableClass> other, TypeMirror type, String propName, List<? extends AnnotationMirror> annotations, Element sourceElement) {
public final T hydrate(TypeSpec.Builder klazz, CodeBlock.Builder code, List<TypeVariableName> typeVariables, Set<SerializableClass> other, Set<AdapterRef> refs, TypeMirror type, String propName, List<? extends AnnotationMirror> 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<T extends Adapter<T>.Hydrated> {
protected TypeSpec.Builder klazz;
protected List<TypeVariableName> typeVariables;
protected Set<SerializableClass> other;
protected Set<AdapterRef> refs;
protected TypeMirror type;
protected TypeMirror unboxedType;
protected CodeBlock.Builder code;
@ -67,11 +70,11 @@ public abstract class Adapter<T extends Adapter<T>.Hydrated> {
protected void afterHydrate() {}
protected void generateRead(CodeBlock.Builder code, TypeMirror type, String name, List<? extends AnnotationMirror> 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<? extends AnnotationMirror> 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) {

View File

@ -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<Adapter<?>> 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<TypeVariableName> typeVariables, Set<SerializableClass> 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<TypeVariableName> typeVariables, Set<SerializableClass> otherAdapters, Set<AdapterRef> 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<TypeVariableName> typeVariables, Set<SerializableClass> 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<TypeVariableName> typeVariables, Set<SerializableClass> otherAdapters, Set<AdapterRef> 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<TypeVariableName> typeVariables, Set<SerializableClass> otherAdapters, Messager message, Consumer<Adapter<?>.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<TypeVariableName> typeVariables, Set<SerializableClass> otherAdapters, Set<AdapterRef> refs, Messager message, Consumer<Adapter<?>.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<TypeVariableName> typeVariables, Set<SerializableClass> other, TypeMirror type, String propName, List<? extends AnnotationMirror> 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<TypeVariableName> typeVariables, Set<SerializableClass> other, Set<AdapterRef> refs, TypeMirror type, String propName, List<? extends AnnotationMirror> 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<TypeVariableName> typeVariables, Set<SerializableClass> other, TypeMirror type, String propName, List<? extends AnnotationMirror> 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<TypeVariableName> typeVariables, Set<SerializableClass> other, Set<AdapterRef> refs, TypeMirror type, String propName, List<? extends AnnotationMirror> 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<TypeVariableName> typeVariables, Set<SerializableClass> other, TypeMirror type, String propName, List<? extends AnnotationMirror> annotations, Element sourceElement, Messager message, Consumer<Adapter<?>.Hydrated> action) {
private static void withAdapter(TypeSpec.Builder klazz, CodeBlock.Builder code, List<TypeVariableName> typeVariables, Set<SerializableClass> other, Set<AdapterRef> refs, TypeMirror type, String propName, List<? extends AnnotationMirror> annotations, Element sourceElement, Messager message, Consumer<Adapter<?>.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;

View File

@ -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<InferredAdapter.Hydrated> {
@Override
public Hydrated instantiate() {
return new Hydrated();
}
public class Hydrated extends AdapterAdapter<Hydrated>.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<TypeElement, Boolean> findTypeAdapterClass(TypeMirror type, Set<AdapterRef> 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;
}
}
}

View File

@ -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<TypeVariableName> typeVariables, Set<SerializableClass> otherAdapters) throws ElementException;
public abstract void generateSerialisation(TypeSpec.Builder spec, SerializableClass self, List<TypeVariableName> typeVariables, Set<SerializableClass> otherAdapters, Set<AdapterRef> refs) throws ElementException;
protected boolean isIgnored(Property<?> property) {
for (AnnotationMirror annotationMirror : property.getAnnotations()) {

View File

@ -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<TypeVariableName> typeVariables, Set<SerializableClass> otherAdapters) throws ElementException {
public void generateSerialisation(TypeSpec.Builder spec, SerializableClass self, List<TypeVariableName> typeVariables, Set<SerializableClass> otherAdapters, Set<AdapterRef> 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();

View File

@ -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<TypeVariableName> typeVariables, Set<SerializableClass> otherAdapters) throws ElementException {
public void generateSerialisation(TypeSpec.Builder spec, SerializableClass self, List<TypeVariableName> typeVariables, Set<SerializableClass> otherAdapters, Set<AdapterRef> refs) throws ElementException {
Value value = valueCreator.fromStatic(self.classElement());
Properties properties = value.getProperties();