[wip] reworking how builder works with initialized fields.

This commit is contained in:
Reinier Zwitserloot committed 2017-03-07 01:13:00 +01:00
1 parent a2c10c70fa
commit c63c152884
4 files changed
+47 -6

No files matched your search

+7
View File
@@ -107,6 +107,13 @@ import java.lang.annotation.Target;
@Target({TYPE, METHOD, CONSTRUCTOR})
@Retention(SOURCE)
public @interface Builder {
/**
* Marks a given field as being a 'constant' (initialized by the initializing expression during construction and not eligible to be modified as part of {@code @Builder}.
*/
@Target(FIELD)
@Retention(SOURCE)
public @interface Constant {}
/** Name of the method that creates a new builder instance. Default: {@code builder}. */
String builderMethodName() default "builder";
@@ -139,12 +139,21 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
ListBuffer<JavacNode> allFields = new ListBuffer<JavacNode>();
@SuppressWarnings("deprecation")
boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation(lombok.experimental.Value.class, parent));
for (JavacNode fieldNode : HandleConstructor.findAllFields(tdParent)) {
for (JavacNode fieldNode : HandleConstructor.findAllFields(tdParent, true)) {
JCVariableDecl fd = (JCVariableDecl) fieldNode.get();
// final fields with an initializer cannot be written to, so they can't be 'builderized'. Unfortunately presence of @Value makes
// non-final fields final, but @Value's handler hasn't done this yet, so we have to do this math ourselves.
// Value will only skip making a field final if it has an explicit @NonFinal annotation, so we check for that.
if (fd.init != null && valuePresent && !hasAnnotation(NonFinal.class, fieldNode)) continue;
boolean isFinal = (fd.mods.flags & Flags.FINAL) != 0 || (valuePresent && !hasAnnotation(NonFinal.class, fieldNode));
JavacNode isConstant = findAnnotation(Builder.Constant.class, fieldNode, true);
if (fd.init != null && isFinal) {
if (isConstant != null) continue;
}
if (isConstant != null) {
if (!isFinal && fd.init == null) isConstant.addWarning("@Builder.Constant doesn't do anything unless the field has an initializing expression (' = something;') and is final.");
else if (!isFinal) isConstant.addWarning("@Builder.Constant doesn't do anything unless the field is final.");
else if (fd.init == null) isConstant.addWarning("@Builder.Constant doesn't do anything unless the field has an initializing expression (' = something;').");
}
BuilderFieldData bfd = new BuilderFieldData();
bfd.rawName = fd.name;
bfd.name = removePrefixFromField(fieldNode);
@@ -151,6 +151,10 @@ public class HandleConstructor {
}
public static List<JavacNode> findAllFields(JavacNode typeNode) {
return findAllFields(typeNode, false);
}
public static List<JavacNode> findAllFields(JavacNode typeNode, boolean evenFinalInitialized) {
ListBuffer<JavacNode> fields = new ListBuffer<JavacNode>();
for (JavacNode child : typeNode.down()) {
if (child.getKind() != Kind.FIELD) continue;
@@ -162,7 +166,7 @@ public class HandleConstructor {
if ((fieldFlags & Flags.STATIC) != 0) continue;
//Skip initialized final fields
boolean isFinal = (fieldFlags & Flags.FINAL) != 0;
if (!isFinal || fieldDecl.init == null) fields.append(child);
if (evenFinalInitialized || !isFinal || fieldDecl.init == null) fields.append(child);
}
return fields.toList();
}
@@ -191,6 +191,27 @@ public class JavacHandlerUtil {
}
}
static JavacNode findAnnotation(Class<? extends Annotation> type, JavacNode node, boolean delete) {
if (node == null) return null;
if (type == null) return null;
switch (node.getKind()) {
case ARGUMENT:
case FIELD:
case LOCAL:
case TYPE:
case METHOD:
for (JavacNode child : node.down()) {
if (annotationTypeMatches(type, child)) {
if (delete) deleteAnnotationIfNeccessary(child, type);
return child;
}
}
// intentional fallthrough
default:
return null;
}
}
/**
* Checks if the Annotation AST Node provided is likely to be an instance of the provided annotation type.
*