diff options
9 files changed, 287 insertions, 44 deletions
diff --git a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java index ee049f1f..b2d64f05 100644 --- a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java @@ -167,9 +167,13 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd } } + boolean needsCanEqual = false; switch (methodExists("equals", typeNode)) { case NOT_EXISTS: - JCMethodDecl method = createEquals(typeNode, nodesForEquality, callSuper, useFieldsDirectly); + boolean isFinal = (((JCClassDecl)typeNode.get()).mods.flags & Flags.FINAL) != 0; + needsCanEqual = !isFinal || !isDirectDescendantOfObject; + + JCMethodDecl method = createEquals(typeNode, nodesForEquality, callSuper, useFieldsDirectly, needsCanEqual); injectMethod(typeNode, method); break; case EXISTS_BY_LOMBOK: @@ -182,6 +186,18 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd break; } + if (needsCanEqual) { + switch (methodExists("canEqual", typeNode)) { + case NOT_EXISTS: + JCMethodDecl method = createCanEqual(typeNode); + injectMethod(typeNode, method); + break; + case EXISTS_BY_LOMBOK: + case EXISTS_BY_USER: + default: + break; + } + } switch (methodExists("hashCode", typeNode)) { case NOT_EXISTS: JCMethodDecl method = createHashCode(typeNode, nodesForEquality, callSuper, useFieldsDirectly); @@ -196,7 +212,6 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd } break; } - return true; } @@ -314,7 +329,7 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd return maker.TypeCast(maker.TypeIdent(TypeTags.INT), xorBits); } - private JCMethodDecl createEquals(JavacNode typeNode, List<JavacNode> fields, boolean callSuper, boolean useFieldsDirectly) { + private JCMethodDecl createEquals(JavacNode typeNode, List<JavacNode> fields, boolean callSuper, boolean useFieldsDirectly, boolean needsCanEqual) { TreeMaker maker = typeNode.getTreeMaker(); JCClassDecl type = (JCClassDecl) typeNode.get(); @@ -335,27 +350,9 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd maker.Ident(thisName)), returnBool(maker, true), null)); } - /* if (o == null) return false; */ { - statements = statements.append(maker.If(maker.Binary(JCTree.EQ, maker.Ident(oName), - maker.Literal(TypeTags.BOT, null)), returnBool(maker, false), null)); - } - - /* if (o.getClass() != this.getClass()) return false; */ { - Name getClass = typeNode.toName("getClass"); - List<JCExpression> exprNil = List.nil(); - JCExpression oGetClass = maker.Apply(exprNil, maker.Select(maker.Ident(oName), getClass), exprNil); - JCExpression thisGetClass = maker.Apply(exprNil, maker.Select(maker.Ident(thisName), getClass), exprNil); - statements = statements.append( - maker.If(maker.Binary(JCTree.NE, oGetClass, thisGetClass), returnBool(maker, false), null)); - } - - /* if (!super.equals(o)) return false; */ - if (callSuper) { - JCMethodInvocation callToSuper = maker.Apply(List.<JCExpression>nil(), - maker.Select(maker.Ident(typeNode.toName("super")), typeNode.toName("equals")), - List.<JCExpression>of(maker.Ident(oName))); - JCUnary superNotEqual = maker.Unary(JCTree.NOT, callToSuper); - statements = statements.append(maker.If(superNotEqual, returnBool(maker, false), null)); + /* if (!(o instanceof MyType) return false; */ { + JCUnary notInstanceOf = maker.Unary(JCTree.NOT, maker.TypeTest(maker.Ident(oName), maker.Ident(type.name))); + statements = statements.append(maker.If(notInstanceOf, returnBool(maker, false), null)); } /* MyType<?> other = (MyType<?>) o; */ { @@ -379,6 +376,25 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd maker.VarDef(maker.Modifiers(Flags.FINAL), otherName, selfType1, maker.TypeCast(selfType2, maker.Ident(oName)))); } + /* if (!other.canEqual(this)) return false; */ { + if (needsCanEqual) { + List<JCExpression> exprNil = List.nil(); + JCExpression equalityCheck = maker.Apply(exprNil, + maker.Select(maker.Ident(otherName), typeNode.toName("canEqual")), + List.<JCExpression>of(maker.Ident(thisName))); + statements = statements.append(maker.If(maker.Unary(JCTree.NOT, equalityCheck), returnBool(maker, false), null)); + } + } + + /* if (!super.equals(o)) return false; */ + if (callSuper) { + JCMethodInvocation callToSuper = maker.Apply(List.<JCExpression>nil(), + maker.Select(maker.Ident(typeNode.toName("super")), typeNode.toName("equals")), + List.<JCExpression>of(maker.Ident(oName))); + JCUnary superNotEqual = maker.Unary(JCTree.NOT, callToSuper); + statements = statements.append(maker.If(superNotEqual, returnBool(maker, false), null)); + } + for (JavacNode fieldNode : fields) { JCExpression fType = getFieldType(fieldNode, useFieldsDirectly); JCExpression thisFieldAccessor = createFieldAccessor(maker, fieldNode, useFieldsDirectly); @@ -428,6 +444,27 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd JCBlock body = maker.Block(0, statements); return maker.MethodDef(mods, typeNode.toName("equals"), returnType, List.<JCTypeParameter>nil(), params, List.<JCExpression>nil(), body, null); } + + private JCMethodDecl createCanEqual(JavacNode typeNode) { + /* public boolean canEquals(final java.lang.Object other) { + * return other instanceof MyType; + * } + */ + TreeMaker maker = typeNode.getTreeMaker(); + JCClassDecl type = (JCClassDecl) typeNode.get(); + + JCModifiers mods = maker.Modifiers(Flags.PUBLIC, List.<JCAnnotation>nil()); + JCExpression returnType = maker.TypeIdent(TypeTags.BOOLEAN); + Name canEqualName = typeNode.toName("canEqual"); + JCExpression objectType = chainDots(maker, typeNode, "java", "lang", "Object"); + Name otherName = typeNode.toName("other"); + List<JCVariableDecl> params = List.of(maker.VarDef(maker.Modifiers(Flags.FINAL), otherName, objectType, null)); + + JCBlock body = maker.Block(0, List.<JCStatement>of( + maker.Return(maker.TypeTest(maker.Ident(otherName), maker.Ident(type.name))))); + + return maker.MethodDef(mods, canEqualName, returnType, List.<JCTypeParameter>nil(), params, List.<JCExpression>nil(), body, null); + } private JCStatement generateCompareFloatOrDouble(JCExpression thisDotField, JCExpression otherDotField, TreeMaker maker, JavacNode node, boolean isDouble) { diff --git a/test/transform/resource/after-delombok/DataExtended.java b/test/transform/resource/after-delombok/DataExtended.java index 6f55bc2c..be1c3b74 100644 --- a/test/transform/resource/after-delombok/DataExtended.java +++ b/test/transform/resource/after-delombok/DataExtended.java @@ -15,12 +15,16 @@ class DataExtended { @java.lang.SuppressWarnings("all") public boolean equals(final java.lang.Object o) { if (o == this) return true; - if (o == null) return false; - if (o.getClass() != this.getClass()) return false; + if (!(o instanceof DataExtended)) return false; final DataExtended other = (DataExtended)o; + if (!other.canEqual(this)) return false; if (this.getX() != other.getX()) return false; return true; } + @java.lang.SuppressWarnings("all") + public boolean canEqual(final java.lang.Object other) { + return other instanceof DataExtended; + } @java.lang.Override @java.lang.SuppressWarnings("all") public int hashCode() { diff --git a/test/transform/resource/after-delombok/DataIgnore.java b/test/transform/resource/after-delombok/DataIgnore.java index 7e81432d..9f2a7d79 100644 --- a/test/transform/resource/after-delombok/DataIgnore.java +++ b/test/transform/resource/after-delombok/DataIgnore.java @@ -14,12 +14,16 @@ class DataIgnore { @java.lang.SuppressWarnings("all") public boolean equals(final java.lang.Object o) { if (o == this) return true; - if (o == null) return false; - if (o.getClass() != this.getClass()) return false; + if (!(o instanceof DataIgnore)) return false; final DataIgnore other = (DataIgnore)o; + if (!other.canEqual(this)) return false; if (this.getX() != other.getX()) return false; return true; } + @java.lang.SuppressWarnings("all") + public boolean canEqual(final java.lang.Object other) { + return other instanceof DataIgnore; + } @java.lang.Override @java.lang.SuppressWarnings("all") public int hashCode() { @@ -33,4 +37,4 @@ class DataIgnore { public java.lang.String toString() { return "DataIgnore(x=" + this.getX() + ")"; } -} +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/DataOnLocalClass.java b/test/transform/resource/after-delombok/DataOnLocalClass.java index bb3f564d..02101a81 100644 --- a/test/transform/resource/after-delombok/DataOnLocalClass.java +++ b/test/transform/resource/after-delombok/DataOnLocalClass.java @@ -23,13 +23,17 @@ class DataOnLocalClass1 { @java.lang.SuppressWarnings("all") public boolean equals(final java.lang.Object o) { if (o == this) return true; - if (o == null) return false; - if (o.getClass() != this.getClass()) return false; + if (!(o instanceof Local)) return false; final Local other = (Local)o; + if (!other.canEqual(this)) return false; if (this.getX() != other.getX()) return false; if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false; return true; } + @java.lang.SuppressWarnings("all") + public boolean canEqual(final java.lang.Object other) { + return other instanceof Local; + } @java.lang.Override @java.lang.SuppressWarnings("all") public int hashCode() { @@ -73,12 +77,16 @@ class DataOnLocalClass2 { @java.lang.SuppressWarnings("all") public boolean equals(final java.lang.Object o) { if (o == this) return true; - if (o == null) return false; - if (o.getClass() != this.getClass()) return false; + if (!(o instanceof InnerLocal)) return false; final InnerLocal other = (InnerLocal)o; + if (!other.canEqual(this)) return false; if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false; return true; } + @java.lang.SuppressWarnings("all") + public boolean canEqual(final java.lang.Object other) { + return other instanceof InnerLocal; + } @java.lang.Override @java.lang.SuppressWarnings("all") public int hashCode() { @@ -105,12 +113,16 @@ class DataOnLocalClass2 { @java.lang.SuppressWarnings("all") public boolean equals(final java.lang.Object o) { if (o == this) return true; - if (o == null) return false; - if (o.getClass() != this.getClass()) return false; + if (!(o instanceof Local)) return false; final Local other = (Local)o; + if (!other.canEqual(this)) return false; if (this.getX() != other.getX()) return false; return true; } + @java.lang.SuppressWarnings("all") + public boolean canEqual(final java.lang.Object other) { + return other instanceof Local; + } @java.lang.Override @java.lang.SuppressWarnings("all") public int hashCode() { diff --git a/test/transform/resource/after-delombok/DataPlain.java b/test/transform/resource/after-delombok/DataPlain.java index 1e11a33d..ef86f9d7 100644 --- a/test/transform/resource/after-delombok/DataPlain.java +++ b/test/transform/resource/after-delombok/DataPlain.java @@ -22,13 +22,17 @@ class Data1 { @java.lang.SuppressWarnings("all") public boolean equals(final java.lang.Object o) { if (o == this) return true; - if (o == null) return false; - if (o.getClass() != this.getClass()) return false; + if (!(o instanceof Data1)) return false; final Data1 other = (Data1)o; + if (!other.canEqual(this)) return false; if (this.getX() != other.getX()) return false; if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false; return true; } + @java.lang.SuppressWarnings("all") + public boolean canEqual(final java.lang.Object other) { + return other instanceof Data1; + } @java.lang.Override @java.lang.SuppressWarnings("all") public int hashCode() { @@ -68,13 +72,17 @@ class Data2 { @java.lang.SuppressWarnings("all") public boolean equals(final java.lang.Object o) { if (o == this) return true; - if (o == null) return false; - if (o.getClass() != this.getClass()) return false; + if (!(o instanceof Data2)) return false; final Data2 other = (Data2)o; + if (!other.canEqual(this)) return false; if (this.getX() != other.getX()) return false; if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false; return true; } + @java.lang.SuppressWarnings("all") + public boolean canEqual(final java.lang.Object other) { + return other instanceof Data2; + } @java.lang.Override @java.lang.SuppressWarnings("all") public int hashCode() { @@ -90,3 +98,87 @@ class Data2 { return "Data2(x=" + this.getX() + ", name=" + this.getName() + ")"; } } +final class Data3 { + final int x; + String name; + @java.beans.ConstructorProperties({"x"}) + @java.lang.SuppressWarnings("all") + public Data3(final int x) { + this.x = x; + } + @java.lang.SuppressWarnings("all") + public int getX() { + return this.x; + } + @java.lang.SuppressWarnings("all") + public String getName() { + return this.name; + } + @java.lang.SuppressWarnings("all") + public void setName(final String name) { + this.name = name; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (!(o instanceof Data3)) return false; + final Data3 other = (Data3)o; + if (this.getX() != other.getX()) return false; + if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false; + return true; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * PRIME + this.getX(); + result = result * PRIME + (this.getName() == null ? 0 : this.getName().hashCode()); + return result; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "Data3(x=" + this.getX() + ", name=" + this.getName() + ")"; + } +} +final class Data4 extends java.util.Timer { + final int x; + Data4() { + super(); + } + @java.lang.SuppressWarnings("all") + public int getX() { + return this.x; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "Data4(x=" + this.getX() + ")"; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (!(o instanceof Data4)) return false; + final Data4 other = (Data4)o; + if (!other.canEqual(this)) return false; + if (!super.equals(o)) return false; + if (this.getX() != other.getX()) return false; + return true; + } + @java.lang.SuppressWarnings("all") + public boolean canEqual(final java.lang.Object other) { + return other instanceof Data4; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * PRIME + super.hashCode(); + result = result * PRIME + this.getX(); + return result; + } +} diff --git a/test/transform/resource/after-delombok/DataWithGetter.java b/test/transform/resource/after-delombok/DataWithGetter.java index 28b8dee0..1e181370 100644 --- a/test/transform/resource/after-delombok/DataWithGetter.java +++ b/test/transform/resource/after-delombok/DataWithGetter.java @@ -19,14 +19,18 @@ class DataWithGetter { @java.lang.SuppressWarnings("all") public boolean equals(final java.lang.Object o) { if (o == this) return true; - if (o == null) return false; - if (o.getClass() != this.getClass()) return false; + if (!(o instanceof DataWithGetter)) return false; final DataWithGetter other = (DataWithGetter)o; + if (!other.canEqual(this)) return false; if (this.getX() != other.getX()) return false; if (this.getY() != other.getY()) return false; if (this.getZ() == null ? other.getZ() != null : !this.getZ().equals(other.getZ())) return false; return true; } + @java.lang.SuppressWarnings("all") + public boolean canEqual(final java.lang.Object other) { + return other instanceof DataWithGetter; + } @java.lang.Override @java.lang.SuppressWarnings("all") public int hashCode() { diff --git a/test/transform/resource/after-delombok/DataWithGetterNone.java b/test/transform/resource/after-delombok/DataWithGetterNone.java index 161c70f9..b1cab7cf 100644 --- a/test/transform/resource/after-delombok/DataWithGetterNone.java +++ b/test/transform/resource/after-delombok/DataWithGetterNone.java @@ -19,14 +19,18 @@ class DataWithGetterNone { @java.lang.SuppressWarnings("all") public boolean equals(final java.lang.Object o) { if (o == this) return true; - if (o == null) return false; - if (o.getClass() != this.getClass()) return false; + if (!(o instanceof DataWithGetterNone)) return false; final DataWithGetterNone other = (DataWithGetterNone)o; + if (!other.canEqual(this)) return false; if (this.x != other.x) return false; if (this.y != other.y) return false; if (this.z == null ? other.z != null : !this.z.equals(other.z)) return false; return true; } + @java.lang.SuppressWarnings("all") + public boolean canEqual(final java.lang.Object other) { + return other instanceof DataWithGetterNone; + } @java.lang.Override @java.lang.SuppressWarnings("all") public int hashCode() { diff --git a/test/transform/resource/after-ecj/DataPlain.java b/test/transform/resource/after-ecj/DataPlain.java index b6e385f3..b0af873f 100644 --- a/test/transform/resource/after-ecj/DataPlain.java +++ b/test/transform/resource/after-ecj/DataPlain.java @@ -81,3 +81,77 @@ import lombok.Data; return (((("Data2(x=" + this.getX()) + ", name=") + this.getName()) + ")"); } } +final @Data class Data3 { + final int x; + String name; + public @java.beans.ConstructorProperties({"x"}) @java.lang.SuppressWarnings("all") Data3(final int x) { + super(); + this.x = x; + } + public @java.lang.SuppressWarnings("all") int getX() { + return this.x; + } + public @java.lang.SuppressWarnings("all") String getName() { + return this.name; + } + public @java.lang.SuppressWarnings("all") void setName(final String name) { + this.name = name; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) { + if ((o == this)) + return true; + if ((o == null)) + return false; + if ((o.getClass() != this.getClass())) + return false; + final Data3 other = (Data3) o; + if ((this.getX() != other.getX())) + return false; + if (((this.getName() == null) ? (other.getName() != null) : (! this.getName().equals(other.getName())))) + return false; + return true; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") int hashCode() { + final int PRIME = 31; + int result = 1; + result = ((result * PRIME) + this.getX()); + result = ((result * PRIME) + ((this.getName() == null) ? 0 : this.getName().hashCode())); + return result; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((("Data3(x=" + this.getX()) + ", name=") + this.getName()) + ")"); + } +} +final @Data @lombok.EqualsAndHashCode(callSuper = true) class Data4 extends java.util.Timer { + final int x; + public @java.lang.SuppressWarnings("all") int getX() { + return this.x; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (("Data4(x=" + this.getX()) + ")"); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) { + if ((o == this)) + return true; + if ((o == null)) + return false; + if ((o.getClass() != this.getClass())) + return false; + if ((! super.equals(o))) + return false; + final Data4 other = (Data4) o; + if ((this.getX() != other.getX())) + return false; + return true; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") int hashCode() { + final int PRIME = 31; + int result = 1; + result = ((result * PRIME) + super.hashCode()); + result = ((result * PRIME) + this.getX()); + return result; + } + Data4() { + super(); + } +} diff --git a/test/transform/resource/before/DataPlain.java b/test/transform/resource/before/DataPlain.java index 680ae46f..4151dd20 100644 --- a/test/transform/resource/before/DataPlain.java +++ b/test/transform/resource/before/DataPlain.java @@ -6,4 +6,16 @@ import lombok.Data; @Data class Data2 { final int x; String name; -}
\ No newline at end of file +} +final @Data class Data3 { + final int x; + String name; +} +@Data +@lombok.EqualsAndHashCode(callSuper=true) +final class Data4 extends java.util.Timer { + final int x; + Data4() { + super(); + } +} |