Fix missing field/setters in read()

This commit is contained in:
Johannes Frohnmeyer 2022-11-01 16:47:00 +01:00
parent 47c77a116b
commit ca86f98ae4
Signed by: Johannes
GPG Key ID: E76429612C2929F4
4 changed files with 111 additions and 57 deletions

View File

@ -6,20 +6,28 @@ 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)
- GPrefer to resolve multiple factories/constructors
## 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
- Setters/field access in read()
- Maps with string, primitive or enum keys
- Date via ISO8601Utils
- Enums

View File

@ -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})
@ -282,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());
@ -363,28 +363,44 @@ 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, self.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)", self.getTypeName(), 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)

View File

@ -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;
}

View File

@ -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