diff options
-rw-r--r-- | doc/changelog.markdown | 1 | ||||
-rw-r--r-- | src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java | 82 | ||||
-rw-r--r-- | src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java | 2 | ||||
-rw-r--r-- | src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java | 2 | ||||
-rw-r--r-- | src/core/lombok/javac/handlers/JavacHandlerUtil.java | 57 | ||||
-rw-r--r-- | usage_examples/DataExample_post.jpage | 40 | ||||
-rw-r--r-- | usage_examples/EqualsAndHashCodeExample_post.jpage | 24 | ||||
-rw-r--r-- | usage_examples/EqualsAndHashCodeExample_pre.jpage | 4 | ||||
-rw-r--r-- | usage_examples/ToStringExample_post.jpage | 8 | ||||
-rw-r--r-- | usage_examples/ToStringExample_pre.jpage | 4 | ||||
-rw-r--r-- | website/features/EqualsAndHashCode.html | 3 | ||||
-rw-r--r-- | website/features/ToString.html | 3 |
12 files changed, 172 insertions, 58 deletions
diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 9816cfde..e2425717 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -17,6 +17,7 @@ Lombok Changelog * BUGFIX: delombok now no longer forgets to remove `import lombok.AccessLevel;`. In netbeans, that import will no longer be flagged erroneously as being unused. [Issue #100](http://code.google.com/p/projectlombok/issues/detail?id=100) and [Issue #103](http://code.google.com/p/projectlombok/issues/detail?id=103) * BUGFIX: While its discouraged, `import lombok.*;` is supposed to work in the vast majority of cases. In eclipse, however, it didn't. Now it does. [Issue #102](http://code.google.com/p/projectlombok/issues/detail?id=102) * BUGFIX: When `@Getter` or `@Setter` is applied to a multiple field declaration, such as `@Getter int x, y;`, the annotation now applies to all fields, not just the first. [Issue #54](http://code.google.com/p/projectlombok/issues/detail?id=54) +* ENHANCEMENT: generated `toString`, `equals` and `hashCode` methods will now use `this.getX()` and `other.getX()` instead of `this.x` and `other.x` if a suitable getter is available. This behaviour is useful for proxied classes, such as the POJOs that hibernate makes. Usage of the getters can be suppressed with `@ToString/@EqualsAndHashCode(doNotUseGetters = true)`. [Issue #110](http://code.google.com/p/projectlombok/issues/detail?id=110) ### v0.9.2 "Hailbunny" (December 15th, 2009) * preliminary support for lombok on NetBeans! - thanks go to Jan Lahoda from NetBeans. [Issue #20](http://code.google.com/p/projectlombok/issues/detail?id=20) diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 337ae3a8..29e44781 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -46,6 +46,7 @@ import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.FieldReference; import org.eclipse.jdt.internal.compiler.ast.IfStatement; import org.eclipse.jdt.internal.compiler.ast.MarkerAnnotation; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.NameReference; import org.eclipse.jdt.internal.compiler.ast.NullLiteral; @@ -98,34 +99,87 @@ public class EclipseHandlerUtil { } } + private static AbstractMethodDeclaration findGetter(EclipseNode field) { + TypeReference fieldType = ((FieldDeclaration)field.get()).type; + boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0; + EclipseNode typeNode = field.up(); + for (String potentialGetterName : TransformationsUtil.toAllGetterNames(field.getName(), isBoolean)) { + switch (methodExists(potentialGetterName, typeNode, false)) { + case EXISTS_BY_LOMBOK: + case EXISTS_BY_USER: + for (EclipseNode potentialGetter : typeNode.down()) { + if (potentialGetter.getKind() != Kind.METHOD) continue; + AbstractMethodDeclaration method = (AbstractMethodDeclaration) potentialGetter.get(); + /** static getX() methods don't count. */ + if ((method.modifiers & ClassFileConstants.AccStatic) != 0) continue; + /** Nor do getters with a non-empty parameter list. */ + if (method.arguments != null && method.arguments.length > 0) continue; + return method; + } + } + } + + return null; + } + static TypeReference getFieldType(EclipseNode field, boolean useFieldsDirectly) { - return ((FieldDeclaration)field.get()).type; + AbstractMethodDeclaration getter = useFieldsDirectly ? null : findGetter(field); + if (!(getter instanceof MethodDeclaration)) { + return ((FieldDeclaration)field.get()).type; + } + + return ((MethodDeclaration)getter).returnType; } static Expression createFieldAccessor(EclipseNode field, boolean useFieldsDirectly, ASTNode source) { int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; - FieldReference thisX = new FieldReference(field.getName().toCharArray(), p); - Eclipse.setGeneratedBy(thisX, source); - thisX.receiver = new ThisReference(pS, pE); - Eclipse.setGeneratedBy(thisX.receiver, source); - return thisX; + + AbstractMethodDeclaration getter = useFieldsDirectly ? null : findGetter(field); + + if (getter == null) { + FieldReference thisX = new FieldReference(field.getName().toCharArray(), p); + Eclipse.setGeneratedBy(thisX, source); + thisX.receiver = new ThisReference(pS, pE); + Eclipse.setGeneratedBy(thisX.receiver, source); + return thisX; + } + + MessageSend call = new MessageSend(); + Eclipse.setGeneratedBy(call, source); + call.sourceStart = pS; call.sourceEnd = pE; + call.receiver = new ThisReference(pS, pE); + Eclipse.setGeneratedBy(call.receiver, source); + call.selector = getter.selector; + return call; } static Expression createFieldAccessor(EclipseNode field, boolean useFieldsDirectly, ASTNode source, char[] receiver) { int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; - NameReference ref; + AbstractMethodDeclaration getter = useFieldsDirectly ? null : findGetter(field); - char[][] tokens = new char[2][]; - tokens[0] = receiver; - tokens[1] = field.getName().toCharArray(); - long[] poss = {p, p}; + if (getter == null) { + NameReference ref; + + char[][] tokens = new char[2][]; + tokens[0] = receiver; + tokens[1] = field.getName().toCharArray(); + long[] poss = {p, p}; + + ref = new QualifiedNameReference(tokens, poss, pS, pE); + Eclipse.setGeneratedBy(ref, source); + return ref; + } - ref = new QualifiedNameReference(tokens, poss, pS, pE); - Eclipse.setGeneratedBy(ref, source); - return ref; + MessageSend call = new MessageSend(); + Eclipse.setGeneratedBy(call, source); + call.sourceStart = pS; call.sourceEnd = pE; + call.receiver = new SingleNameReference(receiver, p); + Eclipse.setGeneratedBy(call.receiver, source); + call.selector = getter.selector; + return call; } /** diff --git a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java index b7c6cda6..2142618f 100644 --- a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java @@ -353,7 +353,7 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsA MessageSend hashCodeCall = new MessageSend(); hashCodeCall.sourceStart = pS; hashCodeCall.sourceEnd = pE; Eclipse.setGeneratedBy(hashCodeCall, source); - hashCodeCall.receiver = fieldAccessor; + hashCodeCall.receiver = createFieldAccessor(field, useFieldsDirectly, source); hashCodeCall.selector = "hashCode".toCharArray(); NullLiteral nullLiteral = new NullLiteral(pS, pE); Eclipse.setGeneratedBy(nullLiteral, source); diff --git a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java index 66d2308a..20ede725 100644 --- a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java @@ -281,7 +281,7 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd maker.Apply(List.<JCExpression>nil(), hcMethod, List.of(fieldAccessor))); } else /* objects */ { /* this.fieldName == null ? 0 : this.fieldName.hashCode() */ - JCExpression hcCall = maker.Apply(List.<JCExpression>nil(), maker.Select(fieldAccessor, typeNode.toName("hashCode")), + JCExpression hcCall = maker.Apply(List.<JCExpression>nil(), maker.Select(createFieldAccessor(maker, fieldNode, useFieldsDirectly), typeNode.toName("hashCode")), List.<JCExpression>nil()); JCExpression thisEqualsNull = maker.Binary(JCTree.EQ, fieldAccessor, maker.Literal(TypeTags.BOT, null)); intoResult = intoResult.append( diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 84388c30..cb2697f1 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -41,6 +41,7 @@ import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCImport; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.util.List; @@ -269,15 +270,26 @@ public class JavacHandlerUtil { } } - /** - * Creates an expression that reads the field. Will either be {@code this.field} or {@code this.getField()} depending on whether or not there's a getter. - */ - static JCExpression createFieldAccessor(TreeMaker maker, JavacNode field, boolean useFieldsDirectly) { - return createFieldAccessor(maker, field, useFieldsDirectly, maker.Ident(field.toName("this"))); - } - - static JCExpression createFieldAccessor(TreeMaker maker, JavacNode field, boolean useFieldsDirectly, JCExpression receiver) { - return maker.Select(receiver, ((JCVariableDecl)field.get()).name); + private static JCMethodDecl findGetter(JavacNode field) { + JCVariableDecl decl = (JCVariableDecl)field.get(); + JavacNode typeNode = field.up(); + for (String potentialGetterName : toAllGetterNames(decl)) { + switch (methodExists(potentialGetterName, typeNode, false)) { + case EXISTS_BY_LOMBOK: + case EXISTS_BY_USER: + for (JavacNode potentialGetter : typeNode.down()) { + if (potentialGetter.getKind() != Kind.METHOD) continue; + JCMethodDecl method = (JCMethodDecl) potentialGetter.get(); + /** static getX() methods don't count. */ + if ((method.mods.flags & Flags.STATIC) != 0) continue; + /** Nor do getters with a non-empty parameter list. */ + if (method.params != null && method.params.size() > 0) continue; + return method; + } + } + } + + return null; } /** @@ -286,7 +298,32 @@ public class JavacHandlerUtil { * @see #createFieldAccessor(TreeMaker, JavacNode) */ static JCExpression getFieldType(JavacNode field, boolean useFieldsDirectly) { - return ((JCVariableDecl)field.get()).vartype; + JCMethodDecl getter = useFieldsDirectly ? null : findGetter(field); + + if (getter == null) { + return ((JCVariableDecl)field.get()).vartype; + } + + return getter.restype; + } + + /** + * Creates an expression that reads the field. Will either be {@code this.field} or {@code this.getField()} depending on whether or not there's a getter. + */ + static JCExpression createFieldAccessor(TreeMaker maker, JavacNode field, boolean useFieldsDirectly) { + return createFieldAccessor(maker, field, useFieldsDirectly, maker.Ident(field.toName("this"))); + } + + static JCExpression createFieldAccessor(TreeMaker maker, JavacNode field, boolean useFieldsDirectly, JCExpression receiver) { + JCMethodDecl getter = useFieldsDirectly ? null : findGetter(field); + + if (getter == null) { + return maker.Select(receiver, ((JCVariableDecl)field.get()).name); + } + + JCMethodInvocation call = maker.Apply(List.<JCExpression>nil(), + maker.Select(receiver, getter.name), List.<JCExpression>nil()); + return call; } /** diff --git a/usage_examples/DataExample_post.jpage b/usage_examples/DataExample_post.jpage index 838c9cf7..a1fa038e 100644 --- a/usage_examples/DataExample_post.jpage +++ b/usage_examples/DataExample_post.jpage @@ -11,7 +11,7 @@ public class DataExample { } public String getName() { - return name; + return this.name; } void setAge(int age) { @@ -19,7 +19,7 @@ public class DataExample { } public int getAge() { - return age; + return this.age; } public void setScore(double score) { @@ -27,11 +27,11 @@ public class DataExample { } public double getScore() { - return score; + return this.score; } public String[] getTags() { - return tags; + return this.tags; } public void setTags(String[] tags) { @@ -39,7 +39,7 @@ public class DataExample { } @Override public String toString() { - return "DataExample(" + name + ", " + age + ", " + score + ", " + Arrays.deepToString(tags) + ")"; + return "DataExample(" + this.getName() + ", " + this.getAge() + ", " + this.getScore() + ", " + Arrays.deepToString(this.getTags()) + ")"; } @Override public boolean equals(Object o) { @@ -47,21 +47,21 @@ public class DataExample { if (o == null) return false; if (o.getClass() != this.getClass()) return false; DataExample other = (DataExample) o; - if (name == null ? other.name != null : !name.equals(other.name)) return false; - if (age != other.age) return false; - if (Double.compare(score, other.score) != 0) return false; - if (!Arrays.deepEquals(tags, other.tags)) return false; + if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false; + if (this.getAge() != other.getAge()) return false; + if (Double.compare(this.getScore(), other.getScore()) != 0) return false; + if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false; return true; } @Override public int hashCode() { final int PRIME = 31; int result = 1; - final long temp1 = Double.doubleToLongBits(score); - result = (result*PRIME) + (name == null ? 0 : name.hashCode()); - result = (result*PRIME) + age; + final long temp1 = Double.doubleToLongBits(this.getScore()); + result = (result*PRIME) + (this.getName() == null ? 0 : this.getName().hashCode()); + result = (result*PRIME) + this.getAge(); result = (result*PRIME) + (int)(temp1 ^ (temp1 >>> 32)); - result = (result*PRIME) + Arrays.deepHashCode(tags); + result = (result*PRIME) + Arrays.deepHashCode(this.getTags()); return result; } @@ -79,15 +79,15 @@ public class DataExample { } public String getName() { - return name; + return this.name; } public T getValue() { - return value; + return this.value; } @Override public String toString() { - return "Exercise(name=" + name + ", value=" + value + ")"; + return "Exercise(name=" + this.getName() + ", value=" + this.getValue() + ")"; } @Override public boolean equals(Object o) { @@ -95,16 +95,16 @@ public class DataExample { if (o == null) return false; if (o.getClass() != this.getClass()) return false; Exercise<?> other = (Exercise<?>) o; - if (name == null ? other.name != null : !name.equals(other.name)) return false; - if (value == null ? other.value != null : !value.equals(other.value)) return false; + if (this.getName() == null ? other.getValue() != null : !this.getName().equals(other.getName())) return false; + if (this.getValue() == null ? other.getValue() != null : !this.getValue().equals(other.getValue())) return false; return true; } @Override public int hashCode() { final int PRIME = 31; int result = 1; - result = (result*PRIME) + (name == null ? 0 : name.hashCode()); - result = (result*PRIME) + (value == null ? 0 : value.hashCode()); + result = (result*PRIME) + (this.getName() == null ? 0 : this.getName().hashCode()); + result = (result*PRIME) + (this.getValue() == null ? 0 : this.getValue().hashCode()); return result; } } diff --git a/usage_examples/EqualsAndHashCodeExample_post.jpage b/usage_examples/EqualsAndHashCodeExample_post.jpage index b3f6e035..312bb92f 100644 --- a/usage_examples/EqualsAndHashCodeExample_post.jpage +++ b/usage_examples/EqualsAndHashCodeExample_post.jpage @@ -8,24 +8,28 @@ public class EqualsAndHashCodeExample { private String[] tags; private int id; + public String getName() { + return this.name; + } + @Override public boolean equals(Object o) { if (o == this) return true; if (o == null) return false; if (o.getClass() != this.getClass()) return false; EqualsAndHashCodeExample other = (EqualsAndHashCodeExample) o; - if (name == null ? other.name != null : !name.equals(other.name)) return false; - if (Double.compare(score, other.score) != 0) return false; - if (!Arrays.deepEquals(tags, other.tags)) return false; + if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false; + if (Double.compare(this.score, other.score) != 0) return false; + if (!Arrays.deepEquals(this.tags, other.tags)) return false; return true; } @Override public int hashCode() { final int PRIME = 31; int result = 1; - final long temp1 = Double.doubleToLongBits(score); - result = (result*PRIME) + (name == null ? 0 : name.hashCode()); + final long temp1 = Double.doubleToLongBits(this.score); + result = (result*PRIME) + (this.name == null ? 0 : this.name.hashCode()); result = (result*PRIME) + (int)(temp1 ^ (temp1 >>> 32)); - result = (result*PRIME) + Arrays.deepHashCode(tags); + result = (result*PRIME) + Arrays.deepHashCode(this.tags); return result; } @@ -43,8 +47,8 @@ public class EqualsAndHashCodeExample { if (o.getClass() != this.getClass()) return false; if (!super.equals(o)) return false; Square other = (Square) o; - if (width != o.width) return false; - if (height != o.height) return false; + if (this.width != other.width) return false; + if (this.height != other.height) return false; return true; } @@ -52,8 +56,8 @@ public class EqualsAndHashCodeExample { final int PRIME = 31; int result = 1; result = (result*PRIME) + super.hashCode(); - result = (result*PRIME) + width; - result = (result*PRIME) + height; + result = (result*PRIME) + this.width; + result = (result*PRIME) + this.height; return result; } } diff --git a/usage_examples/EqualsAndHashCodeExample_pre.jpage b/usage_examples/EqualsAndHashCodeExample_pre.jpage index b146f29a..64faf59f 100644 --- a/usage_examples/EqualsAndHashCodeExample_pre.jpage +++ b/usage_examples/EqualsAndHashCodeExample_pre.jpage @@ -9,6 +9,10 @@ public class EqualsAndHashCodeExample { private String[] tags; private int id; + public String getName() { + return this.name; + } + @EqualsAndHashCode(callSuper=true) public static class Square extends Shape { private final int width, height; diff --git a/usage_examples/ToStringExample_post.jpage b/usage_examples/ToStringExample_post.jpage index f348e0dc..67e78f20 100644 --- a/usage_examples/ToStringExample_post.jpage +++ b/usage_examples/ToStringExample_post.jpage @@ -7,6 +7,10 @@ public class ToStringExample { private String[] tags; private int id; + public String getName() { + return this.getName(); + } + public static class Square extends Shape { private final int width, height; @@ -16,11 +20,11 @@ public class ToStringExample { } @Override public String toString() { - return "Square(super=" + super.toString() + ", width=" + width + ", height=" + height + ")"; + return "Square(super=" + super.toString() + ", width=" + this.width + ", height=" + this.height + ")"; } } @Override public String toString() { - return "ToStringExample(" + name + ", " + shape + ", " + Arrays.deepToString(tags) + ")"; + return "ToStringExample(" + this.getName() + ", " + this.shape + ", " + Arrays.deepToString(this.tags) + ")"; } } diff --git a/usage_examples/ToStringExample_pre.jpage b/usage_examples/ToStringExample_pre.jpage index 26b0cfe8..71b23b9d 100644 --- a/usage_examples/ToStringExample_pre.jpage +++ b/usage_examples/ToStringExample_pre.jpage @@ -8,6 +8,10 @@ public class ToStringExample { private String[] tags; private int id; + public String getName() { + return this.getName(); + } + @ToString(callSuper=true, includeFieldNames=true) public static class Square extends Shape { private final int width, height; diff --git a/website/features/EqualsAndHashCode.html b/website/features/EqualsAndHashCode.html index 15d15050..c5128d10 100644 --- a/website/features/EqualsAndHashCode.html +++ b/website/features/EqualsAndHashCode.html @@ -70,6 +70,9 @@ 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><p> + If a getter exists for a field to be included, it is called instead of using a direct field reference. This behaviour can be suppressed:<br /> + <code>@EqualsAndHashCode(doNotUseGetters = true)</code> </p> </div> </div> diff --git a/website/features/ToString.html b/website/features/ToString.html index 2b1d7b33..23041f48 100644 --- a/website/features/ToString.html +++ b/website/features/ToString.html @@ -59,6 +59,9 @@ 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><p> + If a getter exists for a field to be included, it is called instead of using a direct field reference. This behaviour can be suppressed:<br /> + <code>@ToString(doNotUseGetters = true)</code> </p> </div> </div> |