diff options
-rw-r--r-- | src/lombok/EqualsAndHashCode.java | 14 | ||||
-rw-r--r-- | src/lombok/ToString.java | 14 | ||||
-rw-r--r-- | src/lombok/eclipse/handlers/HandleEqualsAndHashCode.java | 91 | ||||
-rw-r--r-- | src/lombok/eclipse/handlers/HandleToString.java | 77 | ||||
-rw-r--r-- | src/lombok/eclipse/handlers/PKG.java | 25 | ||||
-rw-r--r-- | src/lombok/javac/handlers/HandleEqualsAndHashCode.java | 83 | ||||
-rw-r--r-- | src/lombok/javac/handlers/HandleToString.java | 77 | ||||
-rw-r--r-- | src/lombok/javac/handlers/PKG.java | 26 | ||||
-rw-r--r-- | website/features/Data.html | 2 | ||||
-rw-r--r-- | website/features/EqualsAndHashCode.html | 5 | ||||
-rw-r--r-- | website/features/GetterSetter.html | 2 | ||||
-rw-r--r-- | 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 <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,10 +59,19 @@ 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. * <strong>default: false</strong> 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 <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,10 +61,19 @@ 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> */ 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<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); + + 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<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) ) { 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<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) ) { 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<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; + } } 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<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) ) { 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<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) ) { 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<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; + } + } 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 @@ <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>) 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 <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> 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 <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> 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 <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> |