279 lines
11 KiB
Java
279 lines
11 KiB
Java
package io.gitlab.jfronny.gson.compile.processor.core.value;
|
|
|
|
import io.gitlab.jfronny.commons.data.delegate.DelegateList;
|
|
|
|
import javax.lang.model.element.*;
|
|
import javax.lang.model.type.TypeKind;
|
|
import javax.lang.model.type.TypeMirror;
|
|
import javax.lang.model.util.ElementFilter;
|
|
import javax.lang.model.util.Types;
|
|
import java.util.*;
|
|
|
|
public class Properties extends DelegateList.Simple<Property<?>> {
|
|
public final List<Property<?>> names;
|
|
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.Setter> builderParams;
|
|
|
|
public static Properties build(Types types, ConstructionSource constructionSource, boolean isStatic) throws ElementException {
|
|
Builder builder = new Builder(types, isStatic);
|
|
// constructor params
|
|
if (constructionSource.getConstructionElement() != null) {
|
|
for (VariableElement param : constructionSource.getConstructionElement().getParameters()) {
|
|
builder.addConstructorParam(param);
|
|
}
|
|
}
|
|
|
|
if (constructionSource instanceof ConstructionSource.Builder csb) {
|
|
var builderClass = csb.getBuilderClass();
|
|
for (ExecutableElement method : ElementFilter.methodsIn(builderClass.getEnclosedElements())) {
|
|
builder.addBuilderParam(method);
|
|
}
|
|
}
|
|
|
|
var targetClass = constructionSource.getTargetClass();
|
|
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.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);
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) return true;
|
|
if (!(obj instanceof Properties other)) return false;
|
|
return names.equals(other.names);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return names.hashCode();
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return names.toString();
|
|
}
|
|
|
|
private static class Builder {
|
|
private static final Set<String> METHODS_TO_SKIP = Set.of("hashCode", "toString", "clone");
|
|
|
|
private final boolean isStatic;
|
|
private final Types types;
|
|
public final List<Property<?>> names = new ArrayList<>();
|
|
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.Setter> builderParams = new ArrayList<>();
|
|
|
|
public Builder(Types types, boolean isStatic) {
|
|
this.types = types;
|
|
this.isStatic = isStatic;
|
|
}
|
|
|
|
public void addFieldsAndAccessors(TypeElement targetClass) {
|
|
// accessors
|
|
for (ExecutableElement method : ElementFilter.methodsIn(targetClass.getEnclosedElements())) {
|
|
addGetter(targetClass, method);
|
|
addSetter(targetClass, method);
|
|
}
|
|
|
|
// fields
|
|
for (VariableElement field : ElementFilter.fieldsIn(targetClass.getEnclosedElements())) {
|
|
addField(field);
|
|
}
|
|
|
|
for (TypeMirror superInterface : targetClass.getInterfaces()) {
|
|
addFieldsAndAccessors((TypeElement) types.asElement(superInterface));
|
|
}
|
|
|
|
TypeMirror superclass = targetClass.getSuperclass();
|
|
if (superclass.getKind() != TypeKind.NONE && !superclass.toString().equals("java.lang.Object")) {
|
|
addFieldsAndAccessors((TypeElement) types.asElement(superclass));
|
|
}
|
|
}
|
|
|
|
public void addGetter(TypeElement classElement, ExecutableElement method) {
|
|
Set<Modifier> modifiers = method.getModifiers();
|
|
if (modifiers.contains(Modifier.PRIVATE)
|
|
|| (isStatic != modifiers.contains(Modifier.STATIC))
|
|
|| method.getReturnType().getKind() == TypeKind.VOID
|
|
|| !method.getParameters().isEmpty()
|
|
|| isMethodToSkip(classElement, method)) {
|
|
return;
|
|
}
|
|
getters.add(new Property.Getter(method));
|
|
}
|
|
|
|
public void addSetter(TypeElement classElement, ExecutableElement method) {
|
|
Set<Modifier> modifiers = method.getModifiers();
|
|
if (modifiers.contains(Modifier.PRIVATE)
|
|
|| (isStatic != modifiers.contains(Modifier.STATIC))
|
|
|| method.getReturnType().getKind() != TypeKind.VOID
|
|
|| method.getParameters().size() != 1
|
|
|| isMethodToSkip(classElement, method)
|
|
|| !method.getSimpleName().toString().startsWith("set")) {
|
|
return;
|
|
}
|
|
setters.add(new Property.Setter(method));
|
|
}
|
|
|
|
public void addField(VariableElement field) {
|
|
Set<Modifier> modifiers = field.getModifiers();
|
|
if (isStatic != modifiers.contains(Modifier.STATIC)) return;
|
|
fields.add(new Property.Field(field));
|
|
}
|
|
|
|
public void addConstructorParam(VariableElement param) {
|
|
Property.ConstructorParam prop = new Property.ConstructorParam(param);
|
|
constructorParams.add(prop);
|
|
params.add(prop);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
public Properties build() throws ElementException {
|
|
stripBeans(getters);
|
|
removeExtraBuilders();
|
|
removeExtraSetters();
|
|
removeGettersForTransientFields();
|
|
removeSettersForTransientFields();
|
|
mergeSerializeNames(params, fields, getters, setters);
|
|
removeExtraFields();
|
|
names.addAll(params);
|
|
getters.stream().filter(f -> !containsName(names, f)).forEach(names::add);
|
|
setters.stream().filter(f -> !containsName(names, f)).forEach(names::add);
|
|
fields.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) {
|
|
if (getters.stream().allMatch(Property.Getter::isBean)) {
|
|
for (Property.Getter getter : getters) {
|
|
getter.stripBean();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void removeExtraBuilders() {
|
|
for (int i = builderParams.size() - 1; i >= 0; i--) {
|
|
Property.Setter builderParam = builderParams.get(i);
|
|
if (containsName(constructorParams, builderParam)) {
|
|
builderParams.remove(i);
|
|
params.remove(builderParam);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void removeExtraSetters() {
|
|
setters.removeIf(s -> containsName(constructorParams, s));
|
|
}
|
|
|
|
private void removeExtraFields() {
|
|
fields.removeIf(field -> {
|
|
Set<Modifier> modifiers = field.element.getModifiers();
|
|
return modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.TRANSIENT);
|
|
});
|
|
}
|
|
|
|
private void removeGettersForTransientFields() {
|
|
getters.removeIf(getter -> {
|
|
Property<?> field = findName(fields, getter);
|
|
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) {
|
|
String name = method.getSimpleName().toString();
|
|
if (METHODS_TO_SKIP.contains(name)) {
|
|
return true;
|
|
}
|
|
return isKotlinClass(classElement) && name.matches("component[0-9]+");
|
|
}
|
|
}
|
|
|
|
private static void merge(Property<?>[] properties) throws ElementException {
|
|
if (properties.length == 0) return;
|
|
List<AnnotationMirror> annotations = null;
|
|
for (Property<?> name : properties) {
|
|
if (name == null) continue;
|
|
if (!name.getAnnotations().isEmpty()) {
|
|
if (annotations == null) annotations = new ArrayList<>(name.getAnnotations());
|
|
else {
|
|
for (AnnotationMirror annotation : name.getAnnotations()) {
|
|
if (annotations.contains(annotation)) {
|
|
throw new ElementException("Duplicate annotation " + annotation + " found on " + name, name.element);
|
|
} else annotations.add(annotation);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (annotations != null) {
|
|
for (Property<?> name : properties) {
|
|
if (name == null) continue;
|
|
name.setAnnotations(annotations);
|
|
}
|
|
}
|
|
}
|
|
|
|
@SafeVarargs
|
|
private static void mergeSerializeNames(List<? extends Property<?>>... propertyLists) throws ElementException {
|
|
if (propertyLists.length == 0) return;
|
|
for (Property<?> name : propertyLists[0]) {
|
|
var names = new Property<?>[propertyLists.length];
|
|
names[0] = name;
|
|
for (int i = 1; i < propertyLists.length; i++) {
|
|
names[i] = findName(propertyLists[i], name);
|
|
}
|
|
merge(names);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
public static boolean containsName(List<? extends Property<?>> properties, Property<?> property) {
|
|
return findName(properties, property) != null;
|
|
}
|
|
|
|
private static boolean isKotlinClass(TypeElement element) {
|
|
return element.getAnnotationMirrors().stream()
|
|
.anyMatch(m -> m.getAnnotationType().toString().equals("kotlin.Metadata"));
|
|
}
|
|
}
|