Implement GPrefer

This commit is contained in:
Johannes Frohnmeyer 2022-11-01 15:24:59 +01:00
parent f0f9a171fd
commit cf795cdedc
Signed by: Johannes
GPG Key ID: E76429612C2929F4
5 changed files with 34 additions and 26 deletions

View File

@ -16,14 +16,15 @@ The goal of this AP is to
- Nested serializable types
- Arrays
- Collections (Sets, Lists, Queues, Deques)
- GPrefer to resolve multiple factories/constructors
## TODO
- Setters/field access in read()
- Maps with string, primitive or enum keys
- Date via ISO8601Utils
- Enums
- Support for nested types from libraries
- Static classes (for configs)
- GPrefer to bypass builder/constructor recovery
## 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.

View File

@ -5,6 +5,13 @@ import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface GSerializable {
/**
* @return The class implementing this types serialization/deserialization. Must have static read/write methods
*/
Class<?> with() default void.class;
/**
* @return Whether to generate an adapter class to use with normal gson adapter resolution
*/
boolean generateAdapter() default false;
}

View File

@ -1,8 +1,7 @@
package io.gitlab.jfronny.gson;
import io.gitlab.jfronny.gson.annotations.SerializedName;
import io.gitlab.jfronny.gson.compile.annotations.GComment;
import io.gitlab.jfronny.gson.compile.annotations.GSerializable;
import io.gitlab.jfronny.gson.compile.annotations.*;
import java.util.*;
@ -49,5 +48,9 @@ public class Main {
@GSerializable
public record ExampleRecord(String hello, @GComment("Sheesh") ExamplePojo2 pojo) {
@GPrefer
public ExampleRecord(String yes) {
this(yes, null);
}
}
}

View File

@ -72,7 +72,6 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
if (bld.with == null) throw new IllegalArgumentException("Missing annotation parameter: with");
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!");
toGenerate.add(SerializableClass.of((TypeElement) element, bld.with, bld.generateAdapter));
}
@ -154,19 +153,13 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
}
private static void generateDelegateToAdapter(TypeSpec.Builder spec, TypeName classType, TypeMirror adapter) {
TypeName adapterType = TypeName.get(adapter);
spec.addField(
FieldSpec.builder(adapterType, "ADAPTER", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer("new $T()", adapterType)
.build()
);
spec.addMethod(
MethodSpec.methodBuilder("read")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addParameter(Cl.GSON_READER, "reader")
.addException(IOException.class)
.returns(classType)
.addCode("return ADAPTER.read(reader);")
.addCode("return $T.read(reader);", adapter)
.build()
);
spec.addMethod(
@ -176,7 +169,7 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
.addParameter(classType, "value")
.addException(IOException.class)
.returns(classType)
.addCode("ADAPTER.write(reader, value);")
.addCode("$T.write(reader, value);", adapter)
.build()
);
}
@ -264,7 +257,7 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
}
private void generateSerialisation(TypeSpec.Builder spec, TypeName classType, TypeElement classElement, List<TypeVariableName> typeVariables, Set<SerializableClass> otherAdapters) throws ElementException {
Value value = valueCreator.from(classElement);
Value value = valueCreator.from(classElement, false);
ConstructionSource constructionSource = value.getConstructionSource();
Properties properties = value.getProperties();

View File

@ -1,5 +1,7 @@
package io.gitlab.jfronny.gson.compile.processor.util.valueprocessor;
import io.gitlab.jfronny.gson.compile.annotations.GPrefer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.*;
import javax.lang.model.util.ElementFilter;
@ -12,10 +14,6 @@ public class ValueCreator {
this.env = env;
}
public Value from(Element element) throws ElementException {
return from(element, false);
}
/**
* Creates a [Value] from the given element. This element can be the [TypeElement] of the target class, or a
* specific constructor or factory method. If [isBuilder] is true, then the element represents the builder class,
@ -133,17 +131,23 @@ public class ValueCreator {
constructors.add(method);
}
}
if (constructors.isEmpty() && noArgConstructor != null) {
constructors.add(noArgConstructor);
if (constructors.isEmpty()) {
if (noArgConstructor != null) return noArgConstructor;
else throw new ElementException("Lacking constructor or factory method", klazz);
}
if (constructors.size() == 1) {
return constructors.get(0);
} else {
List<ElementException.Message> messages = new ArrayList<>();
messages.add(new ElementException.Message("More than one constructor or factory method found.", klazz));
constructors.stream().map(s -> new ElementException.Message(" " + s, s)).forEach(messages::add);
throw new ElementException(messages);
if (constructors.size() == 1) return constructors.get(0);
if (noArgConstructor != null) constructors.add(noArgConstructor);
List<ExecutableElement> preferred = new ArrayList<>();
for (ExecutableElement constructor : constructors) {
if (constructor.getAnnotationsByType(GPrefer.class).length != 0) {
preferred.add(constructor);
}
}
if (preferred.size() == 1) return preferred.get(0);
List<ElementException.Message> messages = new ArrayList<>();
messages.add(new ElementException.Message("More than one constructor or factory method found.", klazz));
constructors.stream().map(s -> new ElementException.Message(" " + s, s)).forEach(messages::add);
throw new ElementException(messages);
}
private static void checkKind(Element element, ElementKind kind) {