From f1124aad02569c983cb8979445245141bf029a88 Mon Sep 17 00:00:00 2001
From: Reinier Zwitserloot
Date: Thu, 3 Sep 2009 01:44:59 +0200
Subject: Addressed issue #32: The @EqualsAndHashCode and @ToString annotations
now support explicitly listing the fields to use, via the new 'of' parameter.
We've also added any fields that start with $ to the default excludes list. Lombok itself can generate these fields ($lock of @Synchronized, for example), and in general they probably should count as effectively not part of the class.
---
src/lombok/EqualsAndHashCode.java | 14 +++-
src/lombok/ToString.java | 14 +++-
.../eclipse/handlers/HandleEqualsAndHashCode.java | 91 +++++++++++++---------
src/lombok/eclipse/handlers/HandleToString.java | 77 ++++++++++--------
src/lombok/eclipse/handlers/PKG.java | 25 ++++++
.../javac/handlers/HandleEqualsAndHashCode.java | 83 +++++++++++---------
src/lombok/javac/handlers/HandleToString.java | 77 ++++++++++--------
src/lombok/javac/handlers/PKG.java | 26 +++++++
website/features/Data.html | 2 +-
website/features/EqualsAndHashCode.html | 5 ++
website/features/GetterSetter.html | 2 +-
website/features/ToString.html | 8 +-
12 files changed, 279 insertions(+), 145 deletions(-)
diff --git a/src/lombok/EqualsAndHashCode.java b/src/lombok/EqualsAndHashCode.java
index ac2b95a1..597a6933 100644
--- a/src/lombok/EqualsAndHashCode.java
+++ b/src/lombok/EqualsAndHashCode.java
@@ -35,8 +35,9 @@ import java.lang.annotation.Target;
* a method; any method named hashCode will make @EqualsAndHashCode not generate that method,
* for example.
*
- * All fields that are non-static and non-transient are used in the equality check and hashCode generation. You can exclude
- * more fields by specifying them in the exclude parameter.
+ * By default, all fields that are non-static and non-transient are used in the equality check and hashCode generation.
+ * You can exclude more fields by specifying them in the exclude parameter. You can also explicitly specify
+ * the fields that are to be used by specifying them in the of parameter.
*
* Normally, auto-generating hashCode and equals implementations in a subclass is a bad idea, as
* the superclass also defines fields, for which equality checks/hashcodes won't be auto-generated. Therefore, a warning
@@ -58,9 +59,18 @@ public @interface EqualsAndHashCode {
/**
* Any fields listed here will not be taken into account in the generated
* equals and hashCode implementations.
+ * Mutually exclusive with {@link #of()}.
*/
String[] exclude() default {};
+ /**
+ * If present, explicitly lists the fields that are to be printed.
+ * Normally, all non-static, non-transient fields are printed.
+ *
+ * Mutually exclusive with {@link #exclude()}.
+ */
+ String[] of() default {};
+
/**
* Call on the superclass's implementations of equals and hashCode before calculating
* for the fields in this class.
diff --git a/src/lombok/ToString.java b/src/lombok/ToString.java
index 10c61807..46d9dabe 100644
--- a/src/lombok/ToString.java
+++ b/src/lombok/ToString.java
@@ -33,8 +33,9 @@ import java.lang.annotation.Target;
* that it's doing nothing at all. The parameter list and return type are not relevant when deciding to skip generation of
* the method; any method named toString will make @ToString not generate anything.
*
- * All fields that are non-static are used in the toString generation. You can exclude fields by specifying them
- * in the exclude parameter.
+ * By default, all fields that are non-static are used in the toString generation. You can exclude fields by specifying them
+ * in the exclude parameter. You can also explicitly specify the fields that
+ * are to be used by specifying them in the of parameter.
*
* Array fields are handled by way of {@link java.util.Arrays#deepToString(Object[])} where necessary.
* The downside is that arrays with circular references (arrays that contain themselves,
@@ -60,9 +61,18 @@ public @interface ToString {
/**
* Any fields listed here will not be printed in the generated toString implementation.
+ * Mutually exclusive with {@link #of()}.
*/
String[] exclude() default {};
+ /**
+ * If present, explicitly lists the fields that are to be printed.
+ * Normally, all non-static fields are printed.
+ *
+ * Mutually exclusive with {@link #exclude()}.
+ */
+ String[] of() default {};
+
/**
* Include the result of the superclass's implementation of toString in the output.
* default: false
diff --git a/src/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/lombok/eclipse/handlers/HandleEqualsAndHashCode.java
index 5896dc2d..46792236 100644
--- a/src/lombok/eclipse/handlers/HandleEqualsAndHashCode.java
+++ b/src/lombok/eclipse/handlers/HandleEqualsAndHashCode.java
@@ -91,24 +91,17 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler BUILT_IN_TYPES = Collections.unmodifiableSet(new HashSet(Arrays.asList(
"byte", "short", "int", "long", "char", "boolean", "double", "float")));
- private void checkForBogusExcludes(Node type, AnnotationValues annotation) {
- List list = Arrays.asList(annotation.getInstance().exclude());
- boolean[] matched = new boolean[list.size()];
-
- for ( Node child : type.down() ) {
- if ( list.isEmpty() ) break;
- if ( child.getKind() != Kind.FIELD ) continue;
- if ( (((FieldDeclaration)child.get()).modifiers & ClassFileConstants.AccStatic) != 0 ) continue;
- if ( (((FieldDeclaration)child.get()).modifiers & ClassFileConstants.AccTransient) != 0 ) continue;
- int idx = list.indexOf(child.getName());
- if ( idx > -1 ) matched[idx] = true;
- }
-
- for ( int i = 0 ; i < list.size() ; i++ ) {
- if ( !matched[i] ) {
+ private void checkForBogusFieldNames(Node type, AnnotationValues annotation) {
+ if ( annotation.isExplicit("exclude") ) {
+ for ( int i : createListOfNonExistentFields(Arrays.asList(annotation.getInstance().exclude()), type, true, true) ) {
annotation.setWarning("exclude", "This field does not exist, or would have been excluded anyway.", i);
}
}
+ if ( annotation.isExplicit("of") ) {
+ for ( int i : createListOfNonExistentFields(Arrays.asList(annotation.getInstance().of()), type, false, false) ) {
+ annotation.setWarning("of", "This field does not exist.", i);
+ }
+ }
}
public void generateEqualsAndHashCodeForType(Node typeNode, Node errorNode) {
@@ -121,26 +114,34 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandleremptyList(), callSuper, true, false);
+ generateMethods(typeNode, errorNode, null, null, null, false);
}
@Override public boolean handle(AnnotationValues annotation, Annotation ast, Node annotationNode) {
EqualsAndHashCode ann = annotation.getInstance();
List excludes = Arrays.asList(ann.exclude());
+ List includes = Arrays.asList(ann.of());
Node typeNode = annotationNode.up();
- checkForBogusExcludes(typeNode, annotation);
+ checkForBogusFieldNames(typeNode, annotation);
+
+ Boolean callSuper = ann.callSuper();
+ if ( !annotation.isExplicit("callSuper") ) callSuper = null;
+ if ( !annotation.isExplicit("exclude") ) excludes = null;
+ if ( !annotation.isExplicit("of") ) includes = null;
+
+ if ( excludes != null && includes != null ) {
+ excludes = null;
+ annotation.setWarning("exclude", "exclude and of are mutually exclusive; the 'exclude' parameter will be ignored.");
+ }
- return generateMethods(typeNode, annotationNode, excludes,
- ann.callSuper(), annotation.getRawExpression("callSuper") == null, true);
+ return generateMethods(typeNode, annotationNode, excludes, includes, callSuper, true);
}
- public boolean generateMethods(Node typeNode, Node errorNode, List excludes,
- boolean callSuper, boolean implicit, boolean whineIfExists) {
+ public boolean generateMethods(Node typeNode, Node errorNode, List excludes, List includes,
+ Boolean callSuper, boolean whineIfExists) {
+ assert excludes == null || includes == null;
+
TypeDeclaration typeDecl = null;
if ( typeNode.get() instanceof TypeDeclaration ) typeDecl = (TypeDeclaration) typeNode.get();
@@ -153,6 +154,14 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler nodesForEquality = new ArrayList();
- for ( Node child : typeNode.down() ) {
- if ( child.getKind() != Kind.FIELD ) continue;
- FieldDeclaration fieldDecl = (FieldDeclaration) child.get();
- //Skip static fields.
- if ( (fieldDecl.modifiers & ClassFileConstants.AccStatic) != 0 ) continue;
- //Skip transient fields.
- if ( (fieldDecl.modifiers & ClassFileConstants.AccTransient) != 0 ) continue;
- //Skip excluded fields.
- if ( excludes.contains(new String(fieldDecl.name)) ) continue;
- nodesForEquality.add(child);
+ if ( includes != null ) {
+ for ( Node child : typeNode.down() ) {
+ if ( child.getKind() != Kind.FIELD ) continue;
+ FieldDeclaration fieldDecl = (FieldDeclaration) child.get();
+ if ( includes.contains(new String(fieldDecl.name)) ) nodesForEquality.add(child);
+ }
+ } else {
+ for ( Node child : typeNode.down() ) {
+ if ( child.getKind() != Kind.FIELD ) continue;
+ FieldDeclaration fieldDecl = (FieldDeclaration) child.get();
+ //Skip static fields.
+ if ( (fieldDecl.modifiers & ClassFileConstants.AccStatic) != 0 ) continue;
+ //Skip transient fields.
+ if ( (fieldDecl.modifiers & ClassFileConstants.AccTransient) != 0 ) continue;
+ //Skip excluded fields.
+ if ( excludes != null && excludes.contains(new String(fieldDecl.name)) ) continue;
+ //Skip fields that start with $.
+ if ( fieldDecl.name.length > 0 && fieldDecl.name[0] == '$' ) continue;
+ nodesForEquality.add(child);
+ }
}
switch ( methodExists("hashCode", typeNode) ) {
diff --git a/src/lombok/eclipse/handlers/HandleToString.java b/src/lombok/eclipse/handlers/HandleToString.java
index f2c4e9cf..d639e3fd 100644
--- a/src/lombok/eclipse/handlers/HandleToString.java
+++ b/src/lombok/eclipse/handlers/HandleToString.java
@@ -66,25 +66,20 @@ import org.mangosdk.spi.ProviderFor;
*/
@ProviderFor(EclipseAnnotationHandler.class)
public class HandleToString implements EclipseAnnotationHandler {
- private void checkForBogusExcludes(Node type, AnnotationValues annotation) {
- List list = Arrays.asList(annotation.getInstance().exclude());
- boolean[] matched = new boolean[list.size()];
-
- for ( Node child : type.down() ) {
- if ( list.isEmpty() ) break;
- if ( child.getKind() != Kind.FIELD ) continue;
- if ( (((FieldDeclaration)child.get()).modifiers & ClassFileConstants.AccStatic) != 0 ) continue;
- int idx = list.indexOf(child.getName());
- if ( idx > -1 ) matched[idx] = true;
- }
-
- for ( int i = 0 ; i < list.size() ; i++ ) {
- if ( !matched[i] ) {
+ private void checkForBogusFieldNames(Node type, AnnotationValues annotation) {
+ if ( annotation.isExplicit("exclude") ) {
+ for ( int i : createListOfNonExistentFields(Arrays.asList(annotation.getInstance().exclude()), type, true, false) ) {
annotation.setWarning("exclude", "This field does not exist, or would have been excluded anyway.", i);
}
}
+ if ( annotation.isExplicit("of") ) {
+ for ( int i : createListOfNonExistentFields(Arrays.asList(annotation.getInstance().of()), type, false, false) ) {
+ annotation.setWarning("of", "This field does not exist.", i);
+ }
+ }
}
+
public void generateToStringForType(Node typeNode, Node errorNode) {
for ( Node child : typeNode.down() ) {
if ( child.getKind() == Kind.ANNOTATION ) {
@@ -95,29 +90,31 @@ public class HandleToString implements EclipseAnnotationHandler {
}
}
- boolean includeFieldNames = false;
- boolean callSuper = false;
+ boolean includeFieldNames = true;
try {
includeFieldNames = ((Boolean)ToString.class.getMethod("includeFieldNames").getDefaultValue()).booleanValue();
} catch ( Exception ignore ) {}
- try {
- callSuper = ((Boolean)ToString.class.getMethod("callSuper").getDefaultValue()).booleanValue();
- } catch ( Exception ignore ) {}
- generateToString(typeNode, errorNode, Collections.emptyList(), includeFieldNames, callSuper, false);
+ generateToString(typeNode, errorNode, null, null, includeFieldNames, null, false);
}
public boolean handle(AnnotationValues annotation, Annotation ast, Node annotationNode) {
ToString ann = annotation.getInstance();
List excludes = Arrays.asList(ann.exclude());
+ List includes = Arrays.asList(ann.of());
Node typeNode = annotationNode.up();
+ Boolean callSuper = ann.callSuper();
- checkForBogusExcludes(typeNode, annotation);
+ if ( !annotation.isExplicit("callSuper") ) callSuper = null;
+ if ( !annotation.isExplicit("exclude") ) excludes = null;
+ if ( !annotation.isExplicit("of") ) includes = null;
- return generateToString(typeNode, annotationNode, excludes, ann.includeFieldNames(), ann.callSuper(), true);
+ checkForBogusFieldNames(typeNode, annotation);
+
+ return generateToString(typeNode, annotationNode, excludes, includes, ann.includeFieldNames(), callSuper, true);
}
- public boolean generateToString(Node typeNode, Node errorNode, List excludes,
- boolean includeFieldNames, boolean callSuper, boolean whineIfExists) {
+ public boolean generateToString(Node typeNode, Node errorNode, List excludes, List includes,
+ boolean includeFieldNames, Boolean callSuper, boolean whineIfExists) {
TypeDeclaration typeDecl = null;
if ( typeNode.get() instanceof TypeDeclaration ) typeDecl = (TypeDeclaration) typeNode.get();
@@ -130,15 +127,31 @@ public class HandleToString implements EclipseAnnotationHandler {
return false;
}
+ if ( callSuper == null ) {
+ try {
+ callSuper = ((Boolean)ToString.class.getMethod("callSuper").getDefaultValue()).booleanValue();
+ } catch ( Exception ignore ) {}
+ }
+
List nodesForToString = new ArrayList();
- for ( Node child : typeNode.down() ) {
- if ( child.getKind() != Kind.FIELD ) continue;
- FieldDeclaration fieldDecl = (FieldDeclaration) child.get();
- //Skip static fields.
- if ( (fieldDecl.modifiers & ClassFileConstants.AccStatic) != 0 ) continue;
- //Skip excluded fields.
- if ( excludes.contains(new String(fieldDecl.name)) ) continue;
- nodesForToString.add(child);
+ if ( includes != null ) {
+ for ( Node child : typeNode.down() ) {
+ if ( child.getKind() != Kind.FIELD ) continue;
+ FieldDeclaration fieldDecl = (FieldDeclaration) child.get();
+ if ( includes.contains(new String(fieldDecl.name)) ) nodesForToString.add(child);
+ }
+ } else {
+ for ( Node child : typeNode.down() ) {
+ if ( child.getKind() != Kind.FIELD ) continue;
+ FieldDeclaration fieldDecl = (FieldDeclaration) child.get();
+ //Skip static fields.
+ if ( (fieldDecl.modifiers & ClassFileConstants.AccStatic) != 0 ) continue;
+ //Skip excluded fields.
+ if ( excludes.contains(new String(fieldDecl.name)) ) continue;
+ //Skip fields that start with $
+ if ( fieldDecl.name.length > 0 && fieldDecl.name[0] == '$' ) continue;
+ nodesForToString.add(child);
+ }
}
switch ( methodExists("toString", typeNode) ) {
diff --git a/src/lombok/eclipse/handlers/PKG.java b/src/lombok/eclipse/handlers/PKG.java
index 98c60524..58fac4a1 100644
--- a/src/lombok/eclipse/handlers/PKG.java
+++ b/src/lombok/eclipse/handlers/PKG.java
@@ -33,6 +33,7 @@ import lombok.core.TransformationsUtil;
import lombok.core.AST.Kind;
import lombok.eclipse.Eclipse;
import lombok.eclipse.EclipseAST;
+import lombok.eclipse.EclipseAST.Node;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
@@ -54,6 +55,7 @@ import org.eclipse.jdt.internal.compiler.ast.StringLiteral;
import org.eclipse.jdt.internal.compiler.ast.ThrowStatement;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
class PKG {
private PKG() {}
@@ -273,4 +275,27 @@ class PKG {
ann.bits |= ASTNode.HasBeenGenerated;
return ann;
}
+
+ static List createListOfNonExistentFields(List list, Node type, boolean excludeStandard, boolean excludeTransient) {
+ boolean[] matched = new boolean[list.size()];
+
+ for ( Node child : type.down() ) {
+ if ( list.isEmpty() ) break;
+ if ( child.getKind() != Kind.FIELD ) continue;
+ if ( excludeStandard ) {
+ if ( (((FieldDeclaration)child.get()).modifiers & ClassFileConstants.AccStatic) != 0 ) continue;
+ if ( child.getName().startsWith("$") ) continue;
+ }
+ if ( excludeTransient && (((FieldDeclaration)child.get()).modifiers & ClassFileConstants.AccTransient) != 0 ) continue;
+ int idx = list.indexOf(child.getName());
+ if ( idx > -1 ) matched[idx] = true;
+ }
+
+ List problematic = new ArrayList();
+ for ( int i = 0 ; i < list.size() ; i++ ) {
+ if ( !matched[i] ) problematic.add(i);
+ }
+
+ return problematic;
+ }
}
diff --git a/src/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/lombok/javac/handlers/HandleEqualsAndHashCode.java
index 8c3124c9..16a04da7 100644
--- a/src/lombok/javac/handlers/HandleEqualsAndHashCode.java
+++ b/src/lombok/javac/handlers/HandleEqualsAndHashCode.java
@@ -22,7 +22,6 @@
package lombok.javac.handlers;
import static lombok.javac.handlers.PKG.*;
-
import lombok.EqualsAndHashCode;
import lombok.core.AnnotationValues;
import lombok.core.AST.Kind;
@@ -59,35 +58,33 @@ import com.sun.tools.javac.util.Name;
*/
@ProviderFor(JavacAnnotationHandler.class)
public class HandleEqualsAndHashCode implements JavacAnnotationHandler {
- private void checkForBogusExcludes(Node type, AnnotationValues annotation) {
- List list = List.from(annotation.getInstance().exclude());
- boolean[] matched = new boolean[list.size()];
-
- for ( Node child : type.down() ) {
- if ( list.isEmpty() ) break;
- if ( child.getKind() != Kind.FIELD ) continue;
- if ( (((JCVariableDecl)child.get()).mods.flags & Flags.STATIC) != 0 ) continue;
- if ( (((JCVariableDecl)child.get()).mods.flags & Flags.TRANSIENT) != 0 ) continue;
- int idx = list.indexOf(child.getName());
- if ( idx > -1 ) matched[idx] = true;
- }
-
- for ( int i = 0 ; i < list.size() ; i++ ) {
- if ( !matched[i] ) {
+ private void checkForBogusFieldNames(Node type, AnnotationValues annotation) {
+ if ( annotation.isExplicit("exclude") ) {
+ for ( int i : createListOfNonExistentFields(List.from(annotation.getInstance().exclude()), type, true, true) ) {
annotation.setWarning("exclude", "This field does not exist, or would have been excluded anyway.", i);
}
}
+ if ( annotation.isExplicit("of") ) {
+ for ( int i : createListOfNonExistentFields(List.from(annotation.getInstance().of()), type, false, false) ) {
+ annotation.setWarning("of", "This field does not exist.", i);
+ }
+ }
}
@Override public boolean handle(AnnotationValues annotation, JCAnnotation ast, Node annotationNode) {
EqualsAndHashCode ann = annotation.getInstance();
List excludes = List.from(ann.exclude());
+ List includes = List.from(ann.of());
Node typeNode = annotationNode.up();
- checkForBogusExcludes(typeNode, annotation);
+ checkForBogusFieldNames(typeNode, annotation);
- return generateMethods(typeNode, annotationNode, excludes,
- ann.callSuper(), annotation.getRawExpression("callSuper") == null, true);
+ Boolean callSuper = ann.callSuper();
+ if ( !annotation.isExplicit("callSuper") ) callSuper = null;
+ if ( !annotation.isExplicit("exclude") ) excludes = null;
+ if ( !annotation.isExplicit("of") ) includes = null;
+
+ return generateMethods(typeNode, annotationNode, excludes, includes, callSuper, true);
}
public void generateEqualsAndHashCodeForType(Node typeNode, Node errorNode) {
@@ -100,15 +97,11 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandlernil(), callSuper, true, false);
+ generateMethods(typeNode, errorNode, null, null, null, false);
}
- private boolean generateMethods(Node typeNode, Node errorNode, List excludes,
- boolean callSuper, boolean implicit, boolean whineIfExists) {
+ private boolean generateMethods(Node typeNode, Node errorNode, List excludes, List includes,
+ Boolean callSuper, boolean whineIfExists) {
boolean notAClass = true;
if ( typeNode.get() instanceof JCClassDecl ) {
long flags = ((JCClassDecl)typeNode.get()).mods.flags;
@@ -121,6 +114,12 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler nodesForEquality = List.nil();
- for ( Node child : typeNode.down() ) {
- if ( child.getKind() != Kind.FIELD ) continue;
- JCVariableDecl fieldDecl = (JCVariableDecl) child.get();
- //Skip static fields.
- if ( (fieldDecl.mods.flags & Flags.STATIC) != 0 ) continue;
- //Skip transient fields.
- if ( (fieldDecl.mods.flags & Flags.TRANSIENT) != 0 ) continue;
- //Skip excluded fields.
- if ( excludes.contains(fieldDecl.name.toString()) ) continue;
- nodesForEquality = nodesForEquality.append(child);
+ if ( includes != null ) {
+ for ( Node child : typeNode.down() ) {
+ if ( child.getKind() != Kind.FIELD ) continue;
+ JCVariableDecl fieldDecl = (JCVariableDecl) child.get();
+ if ( includes.contains(fieldDecl.name.toString()) ) nodesForEquality = nodesForEquality.append(child);
+ }
+ } else {
+ for ( Node child : typeNode.down() ) {
+ if ( child.getKind() != Kind.FIELD ) continue;
+ JCVariableDecl fieldDecl = (JCVariableDecl) child.get();
+ //Skip static fields.
+ if ( (fieldDecl.mods.flags & Flags.STATIC) != 0 ) continue;
+ //Skip transient fields.
+ if ( (fieldDecl.mods.flags & Flags.TRANSIENT) != 0 ) continue;
+ //Skip excluded fields.
+ if ( excludes != null && excludes.contains(fieldDecl.name.toString()) ) continue;
+ //Skip fields that start with $
+ if ( fieldDecl.name.toString().startsWith("$") ) continue;
+ nodesForEquality = nodesForEquality.append(child);
+ }
}
switch ( methodExists("hashCode", typeNode) ) {
diff --git a/src/lombok/javac/handlers/HandleToString.java b/src/lombok/javac/handlers/HandleToString.java
index 49f0d15c..7cdbb6e1 100644
--- a/src/lombok/javac/handlers/HandleToString.java
+++ b/src/lombok/javac/handlers/HandleToString.java
@@ -54,33 +54,34 @@ import com.sun.tools.javac.util.List;
*/
@ProviderFor(JavacAnnotationHandler.class)
public class HandleToString implements JavacAnnotationHandler {
- private void checkForBogusExcludes(Node type, AnnotationValues annotation) {
- List list = List.from(annotation.getInstance().exclude());
- boolean[] matched = new boolean[list.size()];
-
- for ( Node child : type.down() ) {
- if ( list.isEmpty() ) break;
- if ( child.getKind() != Kind.FIELD ) continue;
- if ( (((JCVariableDecl)child.get()).mods.flags & Flags.STATIC) != 0 ) continue;
- int idx = list.indexOf(child.getName());
- if ( idx > -1 ) matched[idx] = true;
- }
-
- for ( int i = 0 ; i < list.size() ; i++ ) {
- if ( !matched[i] ) {
+ private void checkForBogusFieldNames(Node type, AnnotationValues annotation) {
+ if ( annotation.isExplicit("exclude") ) {
+ for ( int i : createListOfNonExistentFields(List.from(annotation.getInstance().exclude()), type, true, false) ) {
annotation.setWarning("exclude", "This field does not exist, or would have been excluded anyway.", i);
}
}
+ if ( annotation.isExplicit("of") ) {
+ for ( int i : createListOfNonExistentFields(List.from(annotation.getInstance().of()), type, false, false) ) {
+ annotation.setWarning("of", "This field does not exist.", i);
+ }
+ }
}
@Override public boolean handle(AnnotationValues annotation, JCAnnotation ast, Node annotationNode) {
ToString ann = annotation.getInstance();
List excludes = List.from(ann.exclude());
+ List includes = List.from(ann.of());
Node typeNode = annotationNode.up();
- checkForBogusExcludes(typeNode, annotation);
+ checkForBogusFieldNames(typeNode, annotation);
- return generateToString(typeNode, annotationNode, excludes, ann.includeFieldNames(), ann.callSuper(), true);
+ Boolean callSuper = ann.callSuper();
+
+ if ( !annotation.isExplicit("callSuper") ) callSuper = null;
+ if ( !annotation.isExplicit("exclude") ) excludes = null;
+ if ( !annotation.isExplicit("of") ) includes = null;
+
+ return generateToString(typeNode, annotationNode, excludes, includes, ann.includeFieldNames(), callSuper, true);
}
public void generateToStringForType(Node typeNode, Node errorNode) {
@@ -93,39 +94,51 @@ public class HandleToString implements JavacAnnotationHandler {
}
}
- boolean includeFieldNames = false;
- boolean callSuper = false;
+ boolean includeFieldNames = true;
try {
includeFieldNames = ((Boolean)ToString.class.getMethod("includeFieldNames").getDefaultValue()).booleanValue();
} catch ( Exception ignore ) {}
- try {
- callSuper = ((Boolean)ToString.class.getMethod("callSuper").getDefaultValue()).booleanValue();
- } catch ( Exception ignore ) {}
- generateToString(typeNode, errorNode, List.nil(), includeFieldNames, callSuper, false);
+ generateToString(typeNode, errorNode, null, null, includeFieldNames, null, false);
}
- private boolean generateToString(Node typeNode, Node errorNode, List excludes,
- boolean includeFieldNames, boolean callSuper, boolean whineIfExists) {
+ private boolean generateToString(Node typeNode, Node errorNode, List excludes, List includes,
+ boolean includeFieldNames, Boolean callSuper, boolean whineIfExists) {
boolean notAClass = true;
if ( typeNode.get() instanceof JCClassDecl ) {
long flags = ((JCClassDecl)typeNode.get()).mods.flags;
notAClass = (flags & (Flags.INTERFACE | Flags.ANNOTATION | Flags.ENUM)) != 0;
}
+ if ( callSuper == null ) {
+ try {
+ callSuper = ((Boolean)ToString.class.getMethod("callSuper").getDefaultValue()).booleanValue();
+ } catch ( Exception ignore ) {}
+ }
+
if ( notAClass ) {
errorNode.addError("@ToString is only supported on a class.");
return false;
}
List nodesForToString = List.nil();
- for ( Node child : typeNode.down() ) {
- if ( child.getKind() != Kind.FIELD ) continue;
- JCVariableDecl fieldDecl = (JCVariableDecl) child.get();
- //Skip static fields.
- if ( (fieldDecl.mods.flags & Flags.STATIC) != 0 ) continue;
- //Skip excluded fields.
- if ( excludes.contains(fieldDecl.name.toString()) ) continue;
- nodesForToString = nodesForToString.append(child);
+ if ( includes != null ) {
+ for ( Node child : typeNode.down() ) {
+ if ( child.getKind() != Kind.FIELD ) continue;
+ JCVariableDecl fieldDecl = (JCVariableDecl) child.get();
+ if ( includes.contains(fieldDecl.name.toString()) ) nodesForToString = nodesForToString.append(child);
+ }
+ } else {
+ for ( Node child : typeNode.down() ) {
+ if ( child.getKind() != Kind.FIELD ) continue;
+ JCVariableDecl fieldDecl = (JCVariableDecl) child.get();
+ //Skip static fields.
+ if ( (fieldDecl.mods.flags & Flags.STATIC) != 0 ) continue;
+ //Skip excluded fields.
+ if ( excludes != null && excludes.contains(fieldDecl.name.toString()) ) continue;
+ //Skip fields that start with $.
+ if ( fieldDecl.name.toString().startsWith("$") ) continue;
+ nodesForToString = nodesForToString.append(child);
+ }
}
switch ( methodExists("toString", typeNode) ) {
diff --git a/src/lombok/javac/handlers/PKG.java b/src/lombok/javac/handlers/PKG.java
index 4b05b9ae..0563f33c 100644
--- a/src/lombok/javac/handlers/PKG.java
+++ b/src/lombok/javac/handlers/PKG.java
@@ -288,4 +288,30 @@ class PKG {
JCStatement throwStatement = treeMaker.Throw(exception);
return treeMaker.If(treeMaker.Binary(JCTree.EQ, treeMaker.Ident(fieldName), treeMaker.Literal(TypeTags.BOT, null)), throwStatement, null);
}
+
+ static List createListOfNonExistentFields(List list, Node type, boolean excludeStandard, boolean excludeTransient) {
+ boolean[] matched = new boolean[list.size()];
+
+ for ( Node child : type.down() ) {
+ if ( list.isEmpty() ) break;
+ if ( child.getKind() != Kind.FIELD ) continue;
+ JCVariableDecl field = (JCVariableDecl)child.get();
+ if ( excludeStandard ) {
+ if ( (field.mods.flags & Flags.STATIC) != 0 ) continue;
+ if ( field.name.toString().startsWith("$") ) continue;
+ }
+ if ( excludeTransient && (field.mods.flags & Flags.TRANSIENT) != 0 ) continue;
+
+ int idx = list.indexOf(child.getName());
+ if ( idx > -1 ) matched[idx] = true;
+ }
+
+ List problematic = List.nil();
+ for ( int i = 0 ; i < list.size() ; i++ ) {
+ if ( !matched[i] ) problematic = problematic.append(i);
+ }
+
+ return problematic;
+ }
+
}
diff --git a/website/features/Data.html b/website/features/Data.html
index 9216d46e..9babb9f9 100644
--- a/website/features/Data.html
+++ b/website/features/Data.html
@@ -64,7 +64,7 @@
See the small print of @ToString, @EqualsAndHashCode and
@Getter / @Setter.
- Any annotations named @NonNull or @NotNull (case insensitive) on a field are interpreted as: This field must not ever hold
+ Any annotations named @NonNull (case insensitive) on a field are interpreted as: This field must not ever hold
null. Therefore, these annotations result in an explicit null check in the generated constructor for the provided field. Also, these
annotations (as well as any annotation named @Nullable) are copied to the constructor parameter, in both the true constructor and
any static constructor. The same principle applies to generated getters and setters (see the documentation for @Getter / @Setter)
diff --git a/website/features/EqualsAndHashCode.html b/website/features/EqualsAndHashCode.html
index d42891e7..d2575244 100644
--- a/website/features/EqualsAndHashCode.html
+++ b/website/features/EqualsAndHashCode.html
@@ -18,6 +18,7 @@
Any class definition may be annotated with @EqualsAndHashCode to let lombok generate implementations of the
equals(Object other) and hashCode() methods. By default, it'll use all non-static, non-transient
fields, but you can exclude more fields by naming them in the optional exclude parameter to the annotation.
+ Alternatively, you can specify exactly which fields you wish to be used by naming them in the of parameter.
By setting callSuper to true, you can include the equals and hashCode methods of your superclass in the generated methods.
For hashCode, the result of super.hashCode() is included in the hash algorithm, and for equals, the generated method will return
@@ -65,6 +66,10 @@
Attempting to exclude fields that don't exist or would have been excluded anyway (because they are static or transient) results in warnings on the named fields.
You therefore don't have to worry about typos.
+
+ Having both exclude and of generates a warning; the exclude parameter will be ignored in that case.
+
+ By default, any variables that start with a $ symbol are excluded automatically. You can only include them by using the 'of' parameter.
diff --git a/website/features/GetterSetter.html b/website/features/GetterSetter.html
index 618a6b0a..27bafc70 100644
--- a/website/features/GetterSetter.html
+++ b/website/features/GetterSetter.html
@@ -52,7 +52,7 @@
Any variation on boolean will not result in using the is prefix instead of the get prefix; for example,
returning java.lang.Boolean results in a get prefix, not an is prefix.
- Any annotations named @NonNull or @NotNull (case insensitive) on the field are interpreted as: This field must not ever hold
+ Any annotations named @NonNull (case insensitive) on the field are interpreted as: This field must not ever hold
null. Therefore, these annotations result in an explicit null check in the generated setter. Also, these
annotations (as well as any annotation named @Nullable) are copied to setter parameter and getter method
diff --git a/website/features/ToString.html b/website/features/ToString.html
index 0ed142a1..2b1d7b33 100644
--- a/website/features/ToString.html
+++ b/website/features/ToString.html
@@ -21,8 +21,8 @@
By setting the includeFieldNames parameter to true you can add some clarity (but also quite some length) to
the output of the toString() method.
- All non-static fields will be printed. If you want to skip some fields, you can name them in the exclude parameter; each named
- field will not be printed at all.
+ By default, all non-static fields will be printed. If you want to skip some fields, you can name them in the exclude parameter; each named
+ field will not be printed at all. Alternatively, you can specify exactly which fields you wish to be used by naming them in the of parameter.
By setting callSuper to true, you can include the output of the superclass implementation of toString to the
output. Be aware that the default implementation of toString() in java.lang.Object is pretty much meaningless, so you
@@ -53,8 +53,12 @@
Attempting to exclude fields that don't exist or would have been excluded anyway (because they are static) results in warnings on the named fields.
You therefore don't have to worry about typos.
+ Having both exclude and of generates a warning; the exclude parameter will be ignored in that case.
+
We don't promise to keep the output of the generated toString() methods the same between lombok versions. You should never design your API so that
other code is forced to parse your toString() output anyway!
+
+ By default, any variables that start with a $ symbol are excluded automatically. You can only include them by using the 'of' parameter.
--
cgit