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.
This commit is contained in:
Reinier Zwitserloot committed 2009-09-03 01:44:59 +02:00
1 parent 6b7919166e
commit f1124aad02
12 files changed
+279 -145

No files matched your search

+12 -2
View File
@@ -35,8 +35,9 @@ import java.lang.annotation.Target;
* a method; any method named <code>hashCode</code> will make <code>&#64;EqualsAndHashCode</code> not generate that method,
* for example.
* <p>
* 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 <code>exclude</code> 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 <code>exclude</code> parameter. You can also explicitly specify
* the fields that are to be used by specifying them in the <code>of</code> parameter.
* <p>
* Normally, auto-generating <code>hashCode</code> and <code>equals</code> 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
* <code>equals</code> and <code>hashCode</code> 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.
* <p>
* Mutually exclusive with {@link #exclude()}.
*/
String[] of() default {};
/**
* Call on the superclass's implementations of <code>equals</code> and <code>hashCode</code> before calculating
* for the fields in this class.
+12 -2
View File
@@ -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 <code>toString</code> will make <code>&#64;ToString</code> not generate anything.
* <p>
* All fields that are non-static are used in the toString generation. You can exclude fields by specifying them
* in the <code>exclude</code> parameter.
* By default, all fields that are non-static are used in the toString generation. You can exclude fields by specifying them
* in the <code>exclude</code> parameter. You can also explicitly specify the fields that
* are to be used by specifying them in the <code>of</code> parameter.
* <p>
* 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 <code>toString</code> 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.
* <p>
* Mutually exclusive with {@link #exclude()}.
*/
String[] of() default {};
/**
* Include the result of the superclass's implementation of <code>toString</code> in the output.
* <strong>default: false</strong>
@@ -91,24 +91,17 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsA
private static final Set<String> BUILT_IN_TYPES = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
"byte", "short", "int", "long", "char", "boolean", "double", "float")));
private void checkForBogusExcludes(Node type, AnnotationValues<EqualsAndHashCode> annotation) {
List<String> 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<EqualsAndHashCode> 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 EclipseAnnotationHandler<EqualsA
}
}
boolean callSuper = false;
try {
callSuper = ((Boolean)EqualsAndHashCode.class.getMethod("callSuper").getDefaultValue()).booleanValue();
} catch ( Exception ignore ) {}
generateMethods(typeNode, errorNode, Collections.<String>emptyList(), callSuper, true, false);
generateMethods(typeNode, errorNode, null, null, null, false);
}
@Override public boolean handle(AnnotationValues<EqualsAndHashCode> annotation, Annotation ast, Node annotationNode) {
EqualsAndHashCode ann = annotation.getInstance();
List<String> excludes = Arrays.asList(ann.exclude());
List<String> includes = Arrays.asList(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;
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, includes, callSuper, true);
}
public boolean generateMethods(Node typeNode, Node errorNode, List<String> excludes,
boolean callSuper, boolean implicit, boolean whineIfExists) {
public boolean generateMethods(Node typeNode, Node errorNode, List<String> excludes, List<String> 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<EqualsA
return false;
}
boolean implicitCallSuper = callSuper == null;
if ( callSuper == null ) {
try {
callSuper = ((Boolean)EqualsAndHashCode.class.getMethod("callSuper").getDefaultValue()).booleanValue();
} catch ( Exception ignore ) {}
}
boolean isDirectDescendantOfObject = true;
if ( typeDecl.superclass != null ) {
@@ -165,21 +174,31 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsA
return true;
}
if ( !isDirectDescendantOfObject && !callSuper && implicit ) {
if ( !isDirectDescendantOfObject && !callSuper && implicitCallSuper ) {
errorNode.addWarning("Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type.");
}
List<Node> nodesForEquality = new ArrayList<Node>();
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) ) {
+45 -32
View File
@@ -66,25 +66,20 @@ import org.mangosdk.spi.ProviderFor;
*/
@ProviderFor(EclipseAnnotationHandler.class)
public class HandleToString implements EclipseAnnotationHandler<ToString> {
private void checkForBogusExcludes(Node type, AnnotationValues<ToString> annotation) {
List<String> 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<ToString> 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<ToString> {
}
}
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.<String>emptyList(), includeFieldNames, callSuper, false);
generateToString(typeNode, errorNode, null, null, includeFieldNames, null, false);
}
public boolean handle(AnnotationValues<ToString> annotation, Annotation ast, Node annotationNode) {
ToString ann = annotation.getInstance();
List<String> excludes = Arrays.asList(ann.exclude());
List<String> 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<String> excludes,
boolean includeFieldNames, boolean callSuper, boolean whineIfExists) {
public boolean generateToString(Node typeNode, Node errorNode, List<String> excludes, List<String> 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<ToString> {
return false;
}
if ( callSuper == null ) {
try {
callSuper = ((Boolean)ToString.class.getMethod("callSuper").getDefaultValue()).booleanValue();
} catch ( Exception ignore ) {}
}
List<Node> nodesForToString = new ArrayList<Node>();
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) ) {
+25
View File
@@ -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<Integer> createListOfNonExistentFields(List<String> 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<Integer> problematic = new ArrayList<Integer>();
for ( int i = 0 ; i < list.size() ; i++ ) {
if ( !matched[i] ) problematic.add(i);
}
return problematic;
}
}
@@ -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<EqualsAndHashCode> {
private void checkForBogusExcludes(Node type, AnnotationValues<EqualsAndHashCode> annotation) {
List<String> 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<EqualsAndHashCode> 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<EqualsAndHashCode> annotation, JCAnnotation ast, Node annotationNode) {
EqualsAndHashCode ann = annotation.getInstance();
List<String> excludes = List.from(ann.exclude());
List<String> 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 JavacAnnotationHandler<EqualsAnd
}
}
boolean callSuper = false;
try {
callSuper = ((Boolean)EqualsAndHashCode.class.getMethod("callSuper").getDefaultValue()).booleanValue();
} catch ( Exception ignore ) {}
generateMethods(typeNode, errorNode, List.<String>nil(), callSuper, true, false);
generateMethods(typeNode, errorNode, null, null, null, false);
}
private boolean generateMethods(Node typeNode, Node errorNode, List<String> excludes,
boolean callSuper, boolean implicit, boolean whineIfExists) {
private boolean generateMethods(Node typeNode, Node errorNode, List<String> excludes, List<String> 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<EqualsAnd
}
boolean isDirectDescendantOfObject = true;
boolean implicitCallSuper = callSuper == null;
if ( callSuper == null ) {
try {
callSuper = ((Boolean)EqualsAndHashCode.class.getMethod("callSuper").getDefaultValue()).booleanValue();
} catch ( Exception ignore ) {}
}
JCTree extending = ((JCClassDecl)typeNode.get()).extending;
if ( extending != null ) {
@@ -133,21 +132,31 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd
return true;
}
if ( !isDirectDescendantOfObject && !callSuper && implicit ) {
if ( !isDirectDescendantOfObject && !callSuper && implicitCallSuper ) {
errorNode.addWarning("Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type.");
}
List<Node> 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) ) {
+45 -32
View File
@@ -54,33 +54,34 @@ import com.sun.tools.javac.util.List;
*/
@ProviderFor(JavacAnnotationHandler.class)
public class HandleToString implements JavacAnnotationHandler<ToString> {
private void checkForBogusExcludes(Node type, AnnotationValues<ToString> annotation) {
List<String> 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<ToString> 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<ToString> annotation, JCAnnotation ast, Node annotationNode) {
ToString ann = annotation.getInstance();
List<String> excludes = List.from(ann.exclude());
List<String> 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<ToString> {
}
}
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.<String>nil(), includeFieldNames, callSuper, false);
generateToString(typeNode, errorNode, null, null, includeFieldNames, null, false);
}
private boolean generateToString(Node typeNode, Node errorNode, List<String> excludes,
boolean includeFieldNames, boolean callSuper, boolean whineIfExists) {
private boolean generateToString(Node typeNode, Node errorNode, List<String> excludes, List<String> 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<Node> 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) ) {
+26
View File
@@ -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<Integer> createListOfNonExistentFields(List<String> 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<Integer> problematic = List.nil();
for ( int i = 0 ; i < list.size() ; i++ ) {
if ( !matched[i] ) problematic = problematic.append(i);
}
return problematic;
}
}
+1 -1
View File
@@ -64,7 +64,7 @@
<p>See the small print of <a href="ToString.html"><code>@ToString</code></a>, <a href="EqualsAndHashCode.html"><code>@EqualsAndHashCode</code></a> and
<a href="GetterSetter.html"><code>@Getter / @Setter</code></a>.
</p><p>
Any annotations named <code>@NonNull</code> or <code>@NotNull</code> (case insensitive) on a field are interpreted as: This field must not ever hold
Any annotations named <code>@NonNull</code> (case insensitive) on a field are interpreted as: This field must not ever hold
<em>null</em>. 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 <code>@Nullable</code>) 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 <a href="GetterSetter.html">@Getter / @Setter</a>)
+5
View File
@@ -18,6 +18,7 @@
Any class definition may be annotated with <code>@EqualsAndHashCode</code> to let lombok generate implementations of the
<code>equals(Object other)</code> and <code>hashCode()</code> methods. By default, it'll use all non-static, non-transient
fields, but you can exclude more fields by naming them in the optional <code>exclude</code> parameter to the annotation.
Alternatively, you can specify exactly which fields you wish to be used by naming them in the <code>of</code> parameter.
</p><p>
By setting <code>callSuper</code> to <em>true</em>, you can include the <code>equals</code> and <code>hashCode</code> methods of your superclass in the generated methods.
For <code>hashCode</code>, the result of <code>super.hashCode()</code> is included in the hash algorithm, and for <code>equals</code>, the generated method will return
@@ -65,6 +66,10 @@
</p><p>
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.
</p><p>
Having both <code>exclude</code> and <code>of</code> generates a warning; the <code>exclude</code> parameter will be ignored in that case.
</p><p>
By default, any variables that start with a $ symbol are excluded automatically. You can only include them by using the 'of' parameter.
</p>
</div>
</div>
+1 -1
View File
@@ -52,7 +52,7 @@
Any variation on <code>boolean</code> will <em>not</em> result in using the <code>is</code> prefix instead of the <code>get</code> prefix; for example,
returning <code>java.lang.Boolean</code> results in a <code>get</code> prefix, not an <code>is</code> prefix.
</p><p>
Any annotations named <code>@NonNull</code> or <code>@NotNull</code> (case insensitive) on the field are interpreted as: This field must not ever hold
Any annotations named <code>@NonNull</code> (case insensitive) on the field are interpreted as: This field must not ever hold
<em>null</em>. Therefore, these annotations result in an explicit null check in the generated setter. Also, these
annotations (as well as any annotation named <code>@Nullable</code>) are copied to setter parameter and getter method
</p>
+6 -2
View File
@@ -21,8 +21,8 @@
By setting the <code>includeFieldNames</code> parameter to <em>true</em> you can add some clarity (but also quite some length) to
the output of the <code>toString()</code> method.
</p><p>
All non-static fields will be printed. If you want to skip some fields, you can name them in the <code>exclude</code> 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 <code>exclude</code> 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 <code>of</code> parameter.
</p><p>
By setting <code>callSuper</code> to <em>true</em>, you can include the output of the superclass implementation of <code>toString</code> to the
output. Be aware that the default implementation of <code>toString()</code> in <code>java.lang.Object</code> 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.
</p><p>
Having both <code>exclude</code> and <code>of</code> generates a warning; the <code>exclude</code> parameter will be ignored in that case.
</p><p>
We don't promise to keep the output of the generated <code>toString()</code> methods the same between lombok versions. You should never design your API so that
other code is forced to parse your <code>toString()</code> output anyway!
</p><p>
By default, any variables that start with a $ symbol are excluded automatically. You can only include them by using the 'of' parameter.
</p>
</div>
</div>