mirror of
https://github.com/tiennm99/lombok.git
synced 2026-10-15 06:16:14 +00:00
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:
1 parent
6b7919166e
commit
f1124aad02
12 files changed
+279
-145
No files matched your search
@@ -35,8 +35,9 @@ import java.lang.annotation.Target;
|
||||
* a method; any method named <code>hashCode</code> will make <code>@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.
|
||||
|
||||
@@ -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>@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) ) {
|
||||
|
||||
@@ -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) ) {
|
||||
|
||||
@@ -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) ) {
|
||||
|
||||
@@ -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) ) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user