Compare commits
5 Commits
f0f9a171fd
...
ad238ac2b3
Author | SHA1 | Date |
---|---|---|
Johannes Frohnmeyer | ad238ac2b3 | |
Johannes Frohnmeyer | fe2b1fbe24 | |
Johannes Frohnmeyer | ca86f98ae4 | |
Johannes Frohnmeyer | 47c77a116b | |
Johannes Frohnmeyer | cf795cdedc |
29
README.md
29
README.md
|
@ -6,26 +6,35 @@ The goal of this AP is to
|
|||
- Support json5 through gson-comments
|
||||
- Be compile-time where possible (ideally compatible with proguard)
|
||||
|
||||
## Currently supported
|
||||
- Primitive types
|
||||
- Adapter generation
|
||||
- Utility methods
|
||||
- Strict no-reflection enforcement via `-AgsonCompileNoReflect`
|
||||
- Comments via `@GComment`
|
||||
## Supported types
|
||||
- Primitives (and boxes)
|
||||
- Records
|
||||
- Nested serializable types
|
||||
- Arrays
|
||||
- Collections (Sets, Lists, Queues, Deques)
|
||||
- java.util.Date as iso8601
|
||||
- Enums
|
||||
|
||||
## Used properties
|
||||
Use `@GPrefer` to choose one construction method if multiple are available
|
||||
- Builders (referenced in `@GSerializable`)
|
||||
- Factory functions
|
||||
- Constructors
|
||||
- Getters/Setters
|
||||
- Direct fields (public non-static only)
|
||||
|
||||
## Additional features
|
||||
- Support for generating Gson adapters to hook into the gson system
|
||||
- Optional, strict no-reflection enforcement via `-AgsonCompileNoReflect`
|
||||
- Comments via `@GComment`
|
||||
- Several utility methods in the generated class for reading from/writing to various sources
|
||||
|
||||
## TODO
|
||||
- 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.
|
||||
The API was inspired by [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) by Jetbrains
|
||||
The serialization is powered by [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
|
||||
|
|
|
@ -5,6 +5,18 @@ 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 The builder class to use for creating this type. Incompatible with custom serialization
|
||||
*/
|
||||
Class<?> builder() default void.class;
|
||||
|
||||
/**
|
||||
* @return Whether to generate an adapter class to use with normal gson adapter resolution
|
||||
*/
|
||||
boolean generateAdapter() default false;
|
||||
}
|
||||
|
|
|
@ -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.*;
|
||||
|
||||
|
@ -32,6 +31,10 @@ public class Main {
|
|||
|
||||
public Queue<String> queue;
|
||||
|
||||
public Date date;
|
||||
|
||||
public EeE eEe;
|
||||
|
||||
public void setJoe(String joe) {
|
||||
}
|
||||
|
||||
|
@ -49,5 +52,39 @@ public class Main {
|
|||
|
||||
@GSerializable
|
||||
public record ExampleRecord(String hello, @GComment("Sheesh") ExamplePojo2 pojo) {
|
||||
@GPrefer
|
||||
public ExampleRecord(String yes) {
|
||||
this(yes, null);
|
||||
}
|
||||
}
|
||||
|
||||
@GSerializable(builder = Example4.Builder.class)
|
||||
public static class Example4 {
|
||||
public String someField;
|
||||
public boolean shesh;
|
||||
|
||||
public static class Builder {
|
||||
private String someField;
|
||||
private boolean shesh;
|
||||
|
||||
public Builder(String someField) {
|
||||
this.someField = someField;
|
||||
}
|
||||
|
||||
public Builder setShesh(boolean shesh) {
|
||||
this.shesh = shesh;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Example4 build() {
|
||||
Example4 e = new Example4();
|
||||
e.someField = someField;
|
||||
return e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum EeE {
|
||||
Yes, Yay, Aaee;
|
||||
}
|
||||
}
|
|
@ -16,5 +16,6 @@ public class Cl {
|
|||
public static final ClassName SERIALIZED_NAME = ClassName.get("io.gitlab.jfronny.gson.annotations", "SerializedName");
|
||||
|
||||
public static final ClassName GCOMMENT = ClassName.get("io.gitlab.jfronny.gson.compile.annotations", "GComment");
|
||||
public static final ClassName GISO8601UTILS = ClassName.get("io.gitlab.jfronny.gson.internal.bind.util", "ISO8601Utils");
|
||||
public static final ClassName CCORE = ClassName.get("io.gitlab.jfronny.gson.compile.core", "CCore");
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ import javax.lang.model.util.Elements;
|
|||
import javax.tools.Diagnostic;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_17)
|
||||
@SupportedAnnotationTypes2({GSerializable.class})
|
||||
|
@ -50,31 +49,39 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
|
|||
for (TypeElement annotation : annotations) {
|
||||
for (Element element : roundEnvironment.getElementsAnnotatedWith(annotation)) {
|
||||
for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
|
||||
if (mirror.getAnnotationType().toString().equals(GSerializable.class.getCanonicalName())) {
|
||||
var bld = new Object() {
|
||||
TypeMirror with = null;
|
||||
Boolean generateAdapter = null;
|
||||
};
|
||||
elements.getElementValuesWithDefaults(mirror).forEach((executableElement, value) -> {
|
||||
String name = executableElement.getSimpleName().toString();
|
||||
switch (name) {
|
||||
case "with" -> {
|
||||
if (bld.with != null) throw new IllegalArgumentException("Duplicate annotation parameter: with");
|
||||
bld.with = (TypeMirror) value.getValue();
|
||||
try {
|
||||
if (mirror.getAnnotationType().toString().equals(GSerializable.class.getCanonicalName())) {
|
||||
var bld = new Object() {
|
||||
TypeMirror with = null;
|
||||
TypeMirror builder = null;
|
||||
Boolean generateAdapter = null;
|
||||
};
|
||||
elements.getElementValuesWithDefaults(mirror).forEach((executableElement, value) -> {
|
||||
String name = executableElement.getSimpleName().toString();
|
||||
switch (name) {
|
||||
case "with" -> {
|
||||
if (bld.with != null) throw new IllegalArgumentException("Duplicate annotation parameter: with");
|
||||
bld.with = (TypeMirror) value.getValue();
|
||||
}
|
||||
case "builder" -> {
|
||||
if (bld.builder != null) throw new IllegalArgumentException("Duplicate annotation parameter: builder");
|
||||
bld.builder = (TypeMirror) value.getValue();
|
||||
}
|
||||
case "generateAdapter" -> {
|
||||
if (bld.generateAdapter != null) throw new IllegalArgumentException("Duplicate annotation parameter: generateAdapter");
|
||||
bld.generateAdapter = (Boolean) value.getValue();
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Unexpected annotation parameter: " + name);
|
||||
}
|
||||
case "generateAdapter" -> {
|
||||
if (bld.generateAdapter != null) throw new IllegalArgumentException("Duplicate annotation parameter: generateAdapter");
|
||||
bld.generateAdapter = (Boolean) value.getValue();
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Unexpected annotation parameter: " + name);
|
||||
}
|
||||
});
|
||||
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!");
|
||||
});
|
||||
if (bld.with == null) throw new IllegalArgumentException("Missing annotation parameter: with");
|
||||
if (bld.builder == null) throw new IllegalArgumentException("Missing annotation parameter: with");
|
||||
if (bld.generateAdapter == null) throw new IllegalArgumentException("Missing annotation parameter: generateAdapter");
|
||||
|
||||
toGenerate.add(SerializableClass.of((TypeElement) element, bld.with, bld.generateAdapter));
|
||||
toGenerate.add(SerializableClass.of((TypeElement) element, bld.with, bld.builder, bld.generateAdapter));
|
||||
}
|
||||
} catch (ElementException e) {
|
||||
e.printMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -85,8 +92,10 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
|
|||
for (SerializableClass toProcess : toGenerate) {
|
||||
try {
|
||||
process(toProcess, toGenerate);
|
||||
} catch (IOException | ElementException e) {
|
||||
} catch (IOException e) {
|
||||
message.printMessage(Diagnostic.Kind.ERROR, "GsonCompile threw an exception: " + StringFormatter.toString(e), toProcess.classElement());
|
||||
} catch (ElementException e) {
|
||||
e.printMessage(message);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -115,7 +124,7 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
|
|||
if (toProcess.generateAdapter()) {
|
||||
generateDelegatingAdapter(spec, classType, toProcess.generatedClassName());
|
||||
}
|
||||
generateSerialisation(spec, classType, toProcess.classElement(), typeVariables, other);
|
||||
generateSerialisation(spec, toProcess, typeVariables, other);
|
||||
}
|
||||
|
||||
generateAuxiliary(spec, classType);
|
||||
|
@ -154,19 +163,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 +179,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()
|
||||
);
|
||||
}
|
||||
|
@ -263,8 +266,8 @@ 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);
|
||||
private void generateSerialisation(TypeSpec.Builder spec, SerializableClass self, List<TypeVariableName> typeVariables, Set<SerializableClass> otherAdapters) 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();
|
||||
|
||||
|
@ -278,6 +281,7 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
|
|||
|
||||
code.addStatement("writer.beginObject()");
|
||||
for (Property.Field param : properties.fields) {
|
||||
if (Properties.containsName(properties.getters, param)) continue;
|
||||
generateComments(param, code);
|
||||
code.addStatement("writer.name($S)", getSerializedName(param));
|
||||
Runnable writeGet = () -> code.add("value.$N", param.getCallableName());
|
||||
|
@ -310,7 +314,7 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
|
|||
spec.addMethod(MethodSpec.methodBuilder("write")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(Cl.GSON_WRITER, "writer")
|
||||
.addParameter(classType, "value")
|
||||
.addParameter(self.getTypeName(), "value")
|
||||
.addException(IOException.class)
|
||||
.addCode(code.build())
|
||||
.build());
|
||||
|
@ -359,32 +363,48 @@ public class GsonCompileProcessor extends AbstractProcessor2 {
|
|||
.addStatement("reader.endObject()");
|
||||
}
|
||||
|
||||
code.addStatement("$T result;", self.getTypeName());
|
||||
ClassName creatorName = ClassName.get((TypeElement) constructionSource.getConstructionElement().getEnclosingElement());
|
||||
if (constructionSource instanceof ConstructionSource.Builder builder) {
|
||||
String args = properties.constructorParams.stream().map(s -> "_" + s.getName()).collect(Collectors.joining(", "));
|
||||
StringBuilder args = new StringBuilder();
|
||||
for (Property.ConstructorParam param : properties.constructorParams) {
|
||||
args.append(", _").append(param.getName());
|
||||
}
|
||||
code.add("$T builder = ", builder.getBuilderClass());
|
||||
if (constructionSource.isConstructor()) {
|
||||
code.add("return new $T($L)", builder.getBuilderClass(), args);
|
||||
code.add("new $T($L)", builder.getBuilderClass(), args.length() > 0 ? args.substring(2) : "");
|
||||
} else {
|
||||
code.add("return $T.$N($L)", creatorName, classElement.getSimpleName(), args);
|
||||
code.add("$T.$N($L)", creatorName, self.classElement().getSimpleName(), args.length() > 0 ? args.substring(2) : "");
|
||||
}
|
||||
code.add("\n").indent();
|
||||
for (Property.BuilderParam param : properties.builderParams) {
|
||||
code.add(".$N(_$L)\n", param.getCallableName(), param.getName());
|
||||
code.add(";\n");
|
||||
for (Property.Setter param : properties.builderParams) {
|
||||
code.addStatement("builder.$N(_$N)", param.getCallableName(), param.getName());
|
||||
}
|
||||
code.add(".$N();\n", builder.getBuildMethod().getSimpleName()).unindent();
|
||||
code.addStatement("result = builder.$N()", builder.getBuildMethod().getSimpleName());
|
||||
} else {
|
||||
String args = properties.params.stream().map(s -> "_" + s.getName()).collect(Collectors.joining(", "));
|
||||
StringBuilder args = new StringBuilder();
|
||||
for (Property.Param param : properties.params) {
|
||||
args.append(", _").append(param.getName());
|
||||
}
|
||||
if (constructionSource.isConstructor()) {
|
||||
code.addStatement("return new $T($L)", classType, args);
|
||||
code.addStatement("result = new $T($L)", self.getTypeName(), args.length() > 0 ? args.substring(2) : "");
|
||||
} else {
|
||||
code.addStatement("return $T.$N($L)", creatorName, constructionSource.getConstructionElement().getSimpleName(), args);
|
||||
code.addStatement("result = $T.$N($L)", creatorName, constructionSource.getConstructionElement().getSimpleName(), args.length() > 0 ? args.substring(2) : "");
|
||||
}
|
||||
}
|
||||
//TODO manually set fields and setters if not in constructor
|
||||
for (Property.Setter setter : properties.setters) {
|
||||
code.addStatement("result.$N(_$N)", setter.getCallableName(), setter.getName());
|
||||
}
|
||||
for (Property.Field field : properties.fields) {
|
||||
if (Properties.containsName(properties.setters, field)) continue;
|
||||
if (Properties.containsName(properties.params, field)) continue;
|
||||
code.addStatement("result.$N = _$N", field.getName(), field.getCallableName());
|
||||
}
|
||||
code.addStatement("return result");
|
||||
|
||||
spec.addMethod(MethodSpec.methodBuilder("read")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.returns(classType)
|
||||
.returns(self.getTypeName())
|
||||
.addParameter(Cl.GSON_READER, "reader")
|
||||
.addException(IOException.class)
|
||||
.addCode(code.build())
|
||||
|
|
|
@ -2,16 +2,17 @@ package io.gitlab.jfronny.gson.compile.processor;
|
|||
|
||||
import com.squareup.javapoet.ClassName;
|
||||
import com.squareup.javapoet.TypeName;
|
||||
import io.gitlab.jfronny.gson.compile.processor.util.valueprocessor.ElementException;
|
||||
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) {
|
||||
public record SerializableClass(TypeElement classElement, ClassName generatedClassName, @Nullable TypeMirror adapter, @Nullable TypeMirror builder, boolean generateAdapter) {
|
||||
public static SerializableClass of(TypeElement element, @Nullable TypeMirror with, @Nullable TypeMirror builder, boolean generateAdapter) throws ElementException {
|
||||
ClassName className = ClassName.get(element);
|
||||
ClassName generatedClassName = ClassName.get(className.packageName(), "GC_" + String.join("_", className.simpleNames()));
|
||||
return new SerializableClass(element, generatedClassName, with, generateAdapter);
|
||||
return new SerializableClass(element, generatedClassName, voidToNull(with), voidToNull(builder), generateAdapter).validate();
|
||||
}
|
||||
|
||||
public ClassName getClassName() {
|
||||
|
@ -21,4 +22,13 @@ public record SerializableClass(TypeElement classElement, ClassName generatedCla
|
|||
public TypeName getTypeName() {
|
||||
return TypeName.get(classElement.asType());
|
||||
}
|
||||
|
||||
private SerializableClass validate() throws ElementException {
|
||||
if (adapter != null && builder != null) throw new ElementException("@Serializable with both an adapter and a builder. This is unsupported!", classElement);
|
||||
return this;
|
||||
}
|
||||
|
||||
private static TypeMirror voidToNull(TypeMirror type) {
|
||||
return type == null || type.toString().equals("void") ? null : type;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import javax.lang.model.element.Element;
|
|||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.Types;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public abstract class Adapter<T extends Adapter<T>.Hydrated> {
|
||||
protected Messager message;
|
||||
|
|
|
@ -19,6 +19,8 @@ public class Adapters {
|
|||
new DeclaredAdapter(),
|
||||
new PrimitiveAdapter(),
|
||||
new StringAdapter(),
|
||||
new DateAdapter(),
|
||||
new EnumAdapter(),
|
||||
new ArrayAdapter(),
|
||||
new CollectionAdapter(),
|
||||
new OtherSerializableAdapter(),
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor.adapter.impl;
|
||||
|
||||
import com.squareup.javapoet.CodeBlock;
|
||||
import com.squareup.javapoet.MethodSpec;
|
||||
import io.gitlab.jfronny.gson.compile.processor.Cl;
|
||||
import io.gitlab.jfronny.gson.compile.processor.adapter.Adapter;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import java.text.ParseException;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.Date;
|
||||
|
||||
public class DateAdapter extends Adapter<DateAdapter.Hydrated> {
|
||||
@Override
|
||||
public Hydrated instantiate() {
|
||||
return new Hydrated();
|
||||
}
|
||||
|
||||
public class Hydrated extends Adapter<Hydrated>.Hydrated {
|
||||
@Override
|
||||
public boolean applies() {
|
||||
return type.toString().equals(Date.class.getCanonicalName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateWrite(Runnable writeGet) {
|
||||
code.add("writer.value($T.format(", Cl.GISO8601UTILS);
|
||||
writeGet.run();
|
||||
code.add("));\n");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateRead() {
|
||||
boolean found = false;
|
||||
for (MethodSpec spec : klazz.methodSpecs) {
|
||||
if (spec.name.equals("parseDate")) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
CodeBlock.Builder kode = CodeBlock.builder();
|
||||
kode.beginControlFlow("try")
|
||||
.addStatement("return $T.parse(date, new $T(0))", Cl.GISO8601UTILS, ParsePosition.class)
|
||||
.nextControlFlow("catch ($T e)", ParseException.class)
|
||||
.addStatement("throw new JsonSyntaxException(\"Failed Parsing '\" + date + \"' as Date\", e)")
|
||||
.endControlFlow();
|
||||
klazz.addMethod(
|
||||
MethodSpec.methodBuilder("parseDate")
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
|
||||
.returns(Date.class)
|
||||
.addParameter(String.class, "date")
|
||||
.addCode(kode.build())
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
code.add("parseDate(reader.nextString())");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor.adapter.impl;
|
||||
|
||||
import com.squareup.javapoet.*;
|
||||
import io.gitlab.jfronny.gson.compile.processor.TypeHelper;
|
||||
import io.gitlab.jfronny.gson.compile.processor.adapter.Adapter;
|
||||
|
||||
import javax.lang.model.element.ElementKind;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
|
||||
public class EnumAdapter extends Adapter<EnumAdapter.Hydrated> {
|
||||
@Override
|
||||
public Hydrated instantiate() {
|
||||
return new Hydrated();
|
||||
}
|
||||
|
||||
public class Hydrated extends Adapter<Hydrated>.Hydrated {
|
||||
private DeclaredType tel;
|
||||
|
||||
@Override
|
||||
protected void afterHydrate() {
|
||||
super.afterHydrate();
|
||||
tel = null;
|
||||
DeclaredType declared = TypeHelper.asDeclaredType(type);
|
||||
if (declared == null) return;
|
||||
if (declared.asElement().getKind() != ElementKind.ENUM) return;
|
||||
tel = declared;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies() {
|
||||
return tel != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateWrite(Runnable writeGet) {
|
||||
code.add("writer.value(");
|
||||
writeGet.run();
|
||||
code.add(".name());\n");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateRead() {
|
||||
CodeBlock.Builder kode = CodeBlock.builder();
|
||||
kode.beginControlFlow("for ($1T t : $1T.values())", tel)
|
||||
.addStatement("if (t.name().equals(value)) return t")
|
||||
.endControlFlow()
|
||||
.addStatement("return null");
|
||||
|
||||
String methodName = "read$" + name;
|
||||
klazz.addMethod(
|
||||
MethodSpec.methodBuilder(methodName)
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
|
||||
.returns(TypeName.get(tel))
|
||||
.addParameter(String.class, "value")
|
||||
.addCode(kode.build())
|
||||
.build()
|
||||
);
|
||||
code.add("$N(reader.nextString())", methodName);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,8 +14,9 @@ public class Properties extends DelegateList<Property<?>> {
|
|||
public final List<Property.Param> params;
|
||||
public final List<Property.Field> fields;
|
||||
public final List<Property.Getter> getters;
|
||||
public final List<Property.Setter> setters;
|
||||
public final List<Property.ConstructorParam> constructorParams;
|
||||
public final List<Property.BuilderParam> builderParams;
|
||||
public final List<Property.Setter> builderParams;
|
||||
|
||||
public static Properties build(Types types, ConstructionSource constructionSource) throws ElementException {
|
||||
Builder builder = new Builder(types);
|
||||
|
@ -27,27 +28,29 @@ public class Properties extends DelegateList<Property<?>> {
|
|||
if (constructionSource instanceof ConstructionSource.Builder csb) {
|
||||
var builderClass = csb.getBuilderClass();
|
||||
for (ExecutableElement method : ElementFilter.methodsIn(builderClass.getEnclosedElements())) {
|
||||
builder.addBuilderParam(builderClass.asType(), method);
|
||||
builder.addBuilderParam(method);
|
||||
}
|
||||
}
|
||||
|
||||
var targetClass = constructionSource.getTargetClass();
|
||||
builder.addFieldsAndGetters(targetClass);
|
||||
builder.addFieldsAndAccessors(targetClass);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private Properties(List<Property<?>> names,
|
||||
List<Property.Param> params,
|
||||
List<Property.Field> fields,
|
||||
List<Property.Getter> getters,
|
||||
List<Property.ConstructorParam> constructorParams,
|
||||
List<Property.BuilderParam> builderParams) {
|
||||
List<Property.Param> params,
|
||||
List<Property.Field> fields,
|
||||
List<Property.Getter> getters,
|
||||
List<Property.Setter> setters,
|
||||
List<Property.ConstructorParam> constructorParams,
|
||||
List<Property.Setter> builderParams) {
|
||||
super(names);
|
||||
this.names = Objects.requireNonNull(names);
|
||||
this.params = Objects.requireNonNull(params);
|
||||
this.fields = Objects.requireNonNull(fields);
|
||||
this.getters = Objects.requireNonNull(getters);
|
||||
this.setters = Objects.requireNonNull(setters);
|
||||
this.constructorParams = Objects.requireNonNull(constructorParams);
|
||||
this.builderParams = Objects.requireNonNull(builderParams);
|
||||
}
|
||||
|
@ -77,17 +80,19 @@ public class Properties extends DelegateList<Property<?>> {
|
|||
public final List<Property.Param> params = new ArrayList<>();
|
||||
public final List<Property.Field> fields = new ArrayList<>();
|
||||
public final List<Property.Getter> getters = new ArrayList<>();
|
||||
public final List<Property.Setter> setters = new ArrayList<>();
|
||||
public final List<Property.ConstructorParam> constructorParams = new ArrayList<>();
|
||||
public final List<Property.BuilderParam> builderParams = new ArrayList<>();
|
||||
public final List<Property.Setter> builderParams = new ArrayList<>();
|
||||
|
||||
public Builder(Types types) {
|
||||
this.types = types;
|
||||
}
|
||||
|
||||
public void addFieldsAndGetters(TypeElement targetClass) {
|
||||
// getters
|
||||
public void addFieldsAndAccessors(TypeElement targetClass) {
|
||||
// accessors
|
||||
for (ExecutableElement method : ElementFilter.methodsIn(targetClass.getEnclosedElements())) {
|
||||
addGetter(targetClass, method);
|
||||
addSetter(targetClass, method);
|
||||
}
|
||||
|
||||
// fields
|
||||
|
@ -96,12 +101,12 @@ public class Properties extends DelegateList<Property<?>> {
|
|||
}
|
||||
|
||||
for (TypeMirror superInterface : targetClass.getInterfaces()) {
|
||||
addFieldsAndGetters((TypeElement) types.asElement(superInterface));
|
||||
addFieldsAndAccessors((TypeElement) types.asElement(superInterface));
|
||||
}
|
||||
|
||||
TypeMirror superclass = targetClass.getSuperclass();
|
||||
if (superclass.getKind() != TypeKind.NONE && !superclass.toString().equals("java.lang.Object")) {
|
||||
addFieldsAndGetters((TypeElement) types.asElement(superclass));
|
||||
addFieldsAndAccessors((TypeElement) types.asElement(superclass));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,6 +122,18 @@ public class Properties extends DelegateList<Property<?>> {
|
|||
getters.add(new Property.Getter(method));
|
||||
}
|
||||
|
||||
public void addSetter(TypeElement classElement, ExecutableElement method) {
|
||||
Set<Modifier> modifiers = method.getModifiers();
|
||||
if (modifiers.contains(Modifier.PRIVATE)
|
||||
|| modifiers.contains(Modifier.STATIC)
|
||||
|| method.getReturnType().getKind() != TypeKind.VOID
|
||||
|| method.getParameters().size() != 1
|
||||
|| isMethodToSkip(classElement, method)) {
|
||||
return;
|
||||
}
|
||||
setters.add(new Property.Setter(method));
|
||||
}
|
||||
|
||||
public void addField(VariableElement field) {
|
||||
Set<Modifier> modifiers = field.getModifiers();
|
||||
if (modifiers.contains(Modifier.STATIC)) return;
|
||||
|
@ -129,9 +146,9 @@ public class Properties extends DelegateList<Property<?>> {
|
|||
params.add(prop);
|
||||
}
|
||||
|
||||
public void addBuilderParam(TypeMirror builderType, ExecutableElement method) {
|
||||
if (method.getReturnType().equals(builderType) && method.getParameters().size() == 1) {
|
||||
Property.BuilderParam prop = new Property.BuilderParam(method);
|
||||
public void addBuilderParam(ExecutableElement method) {
|
||||
if (method.getParameters().size() == 1 && method.getSimpleName().toString().startsWith("set")) {
|
||||
Property.Setter prop = new Property.Setter(method);
|
||||
builderParams.add(prop);
|
||||
params.add(prop);
|
||||
}
|
||||
|
@ -140,13 +157,16 @@ public class Properties extends DelegateList<Property<?>> {
|
|||
public Properties build() throws ElementException {
|
||||
stripBeans(getters);
|
||||
removeExtraBuilders();
|
||||
removeExtraSetters();
|
||||
removeGettersForTransientFields();
|
||||
mergeSerializeNames(params, fields, getters);
|
||||
removeSettersForTransientFields();
|
||||
mergeSerializeNames(params, fields, getters, setters);
|
||||
removeExtraFields();
|
||||
names.addAll(params);
|
||||
fields.stream().filter(f -> !containsName(names, f)).forEach(names::add);
|
||||
getters.stream().filter(f -> !containsName(names, f)).forEach(names::add);
|
||||
return new Properties(names, params, fields, getters, constructorParams, builderParams);
|
||||
setters.stream().filter(f -> !containsName(names, f)).forEach(names::add);
|
||||
return new Properties(names, params, fields, getters, setters, constructorParams, builderParams);
|
||||
}
|
||||
|
||||
private void stripBeans(List<Property.Getter> getters) {
|
||||
|
@ -159,7 +179,7 @@ public class Properties extends DelegateList<Property<?>> {
|
|||
|
||||
private void removeExtraBuilders() {
|
||||
for (int i = builderParams.size() - 1; i >= 0; i--) {
|
||||
Property.BuilderParam builderParam = builderParams.get(i);
|
||||
Property.Setter builderParam = builderParams.get(i);
|
||||
if (containsName(constructorParams, builderParam)) {
|
||||
builderParams.remove(i);
|
||||
params.remove(builderParam);
|
||||
|
@ -167,26 +187,29 @@ public class Properties extends DelegateList<Property<?>> {
|
|||
}
|
||||
}
|
||||
|
||||
private void removeExtraSetters() {
|
||||
setters.removeIf(s -> containsName(constructorParams, s));
|
||||
}
|
||||
|
||||
private void removeExtraFields() {
|
||||
for (int i = fields.size() - 1; i >= 0; i--) {
|
||||
Property.Field field = fields.get(i);
|
||||
fields.removeIf(field -> {
|
||||
Set<Modifier> modifiers = field.element.getModifiers();
|
||||
if (modifiers.contains(Modifier.PRIVATE)
|
||||
|| modifiers.contains(Modifier.TRANSIENT)
|
||||
|| containsName(getters, field)) {
|
||||
fields.remove(i);
|
||||
}
|
||||
}
|
||||
return modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.TRANSIENT);
|
||||
});
|
||||
}
|
||||
|
||||
private void removeGettersForTransientFields() {
|
||||
for (int i = getters.size() - 1; i >= 0; i--) {
|
||||
Property.Getter getter = getters.get(i);
|
||||
getters.removeIf(getter -> {
|
||||
Property<?> field = findName(fields, getter);
|
||||
if (field != null && field.element.getModifiers().contains(Modifier.TRANSIENT)) {
|
||||
getters.remove(i);
|
||||
}
|
||||
}
|
||||
return field != null && field.element.getModifiers().contains(Modifier.TRANSIENT);
|
||||
});
|
||||
}
|
||||
|
||||
private void removeSettersForTransientFields() {
|
||||
getters.removeIf(getter -> {
|
||||
Property<?> field = findName(fields, getter);
|
||||
return field != null && field.element.getModifiers().contains(Modifier.TRANSIENT);
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isMethodToSkip(TypeElement classElement, ExecutableElement method) {
|
||||
|
@ -235,11 +258,11 @@ public class Properties extends DelegateList<Property<?>> {
|
|||
}
|
||||
}
|
||||
|
||||
private static <N extends Property<?>> N findName(List<N> names, Property<?> property) {
|
||||
public static <N extends Property<?>> N findName(List<N> names, Property<?> property) {
|
||||
return names.stream().filter(n -> n.getName().equals(property.getName())).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
private static boolean containsName(List<? extends Property<?>> properties, Property<?> property) {
|
||||
public static boolean containsName(List<? extends Property<?>> properties, Property<?> property) {
|
||||
return findName(properties, property) != null;
|
||||
}
|
||||
|
||||
|
|
|
@ -144,12 +144,19 @@ public abstract sealed class Property<T extends Element> {
|
|||
}
|
||||
}
|
||||
|
||||
public static final class BuilderParam extends Param {
|
||||
public static final class Setter extends Param {
|
||||
private final ExecutableElement method;
|
||||
private final String name;
|
||||
|
||||
public BuilderParam(ExecutableElement method) {
|
||||
public Setter(ExecutableElement method) {
|
||||
super(method.getParameters().get(0));
|
||||
this.method = method;
|
||||
name = Character.toLowerCase(method.getSimpleName().toString().charAt(3)) + method.getSimpleName().toString().substring(4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue