Fix missing field/setters in read()
This commit is contained in:
parent
47c77a116b
commit
ca86f98ae4
24
README.md
24
README.md
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue