From 728ae6421dd03bc78e37c0eb5d860f932b5fff2c Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 23 Nov 2015 03:12:50 +0100 Subject: [Fixes #965] Adds a config key to automatically determine the behaviour of equals and hashCode generation when --- doc/changelog.markdown | 1 + src/core/lombok/ConfigurationKeys.java | 10 +++- .../lombok/core/configuration/CallSuperType.java | 27 +++++++++++ .../eclipse/handlers/HandleEqualsAndHashCode.java | 20 +++++++- .../javac/handlers/HandleEqualsAndHashCode.java | 20 +++++++- .../EqualsAndHashCodeConfigKeys1.java | 52 +++++++++++++++++++++ .../EqualsAndHashCodeConfigKeys2.java | 54 ++++++++++++++++++++++ .../after-ecj/EqualsAndHashCodeConfigKeys1.java | 49 ++++++++++++++++++++ .../after-ecj/EqualsAndHashCodeConfigKeys2.java | 52 +++++++++++++++++++++ .../before/EqualsAndHashCodeConfigKeys1.java | 9 ++++ .../before/EqualsAndHashCodeConfigKeys2.java | 9 ++++ website/features/EqualsAndHashCode.html | 4 +- 12 files changed, 301 insertions(+), 6 deletions(-) create mode 100644 src/core/lombok/core/configuration/CallSuperType.java create mode 100644 test/transform/resource/after-delombok/EqualsAndHashCodeConfigKeys1.java create mode 100644 test/transform/resource/after-delombok/EqualsAndHashCodeConfigKeys2.java create mode 100644 test/transform/resource/after-ecj/EqualsAndHashCodeConfigKeys1.java create mode 100644 test/transform/resource/after-ecj/EqualsAndHashCodeConfigKeys2.java create mode 100644 test/transform/resource/before/EqualsAndHashCodeConfigKeys1.java create mode 100644 test/transform/resource/before/EqualsAndHashCodeConfigKeys2.java diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 552ce261..7b858ce7 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -6,6 +6,7 @@ Lombok Changelog * FEATURE: `@Builder` updates: The annotation can now be put on instance methods. [Issue #63](https://github.com/rzwitserloot/lombok/issues/63). * FEATURE: `@Builder` updates: `@Singular` now supports guava's ImmutableTable [Issue #937](https://github.com/rzwitserloot/lombok/issues/937). * FEATURE: A `lombok.config` key can now be used to make your fields `final` and/or `private`... __everywhere__. We'll be monitoring the performance impact of this for a while. We'll touch every source file if you turn these on, and even if you don't, we have to call into the lombok config system for every file. +* FEATURE: A `lombok.config` key can now be used to set the default behaviour of `@EqualsAndHashCode` when generating methods for a class that extends something in regards to calling the superclass implementations of `equals` and `hashCode` or not. [Issue #965](https://github.com/rzwitserloot/lombok/issues/965). * BUGFIX: lambdas with 1 argument that has an explicit type did not pretty print correctly. [Issue #972](https://github.com/rzwitserloot/lombok/issues/972). * BUGFIX: `@Value` and `@FieldDefaults` no longer make uninitialized static fields final. [Issue #928](https://github.com/rzwitserloot/lombok/issues/928). * BUGFIX: When using delombok, a source file with only `@NonNull` annotations on parameters as lombok feature would not get properly delomboked. [Issue #950](https://github.com/rzwitserloot/lombok/issues/950). diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index dd6732ed..73caf295 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -23,6 +23,7 @@ package lombok; import java.util.List; +import lombok.core.configuration.CallSuperType; import lombok.core.configuration.ConfigurationKey; import lombok.core.configuration.FlagUsageType; import lombok.core.configuration.NullCheckExceptionType; @@ -153,7 +154,14 @@ public class ConfigurationKeys { * * For any class without an {@code @EqualsAndHashCode} that explicitly defines the {@code doNotUseGetters} option, this value is used (default = false). */ - public static final ConfigurationKey EQUALS_AND_HASH_CODE_DO_NOT_USE_GETTERS = new ConfigurationKey("lombok.equalsAndHashCode.doNotUseGetters", "Don't call the getters but use the fields directly in the generated equalsAndHashCode method (default = false).") {}; + public static final ConfigurationKey EQUALS_AND_HASH_CODE_DO_NOT_USE_GETTERS = new ConfigurationKey("lombok.equalsAndHashCode.doNotUseGetters", "Don't call the getters but use the fields directly in the generated equals and hashCode method (default = false).") {}; + + /** + * lombok configuration: {@code lombok.equalsAndHashCode.callSuper} = {@code call} | {@code ignore} | {@code warn}. + * + * For any class with an {@code @EqualsAndHashCode} annotation which extends a class other than {@code java.lang.Object}, should a call to superclass's implementation of {@code equals} and {@code hashCode} be included in the generated methods? (Default = warn) + */ + public static final ConfigurationKey EQUALS_AND_HASH_CODE_CALL_SUPER = new ConfigurationKey("lombok.equalsAndHashCode.callSuper", "When generating equals and hashCode for classes that don't extend Object, either automatically take into account superclass implementation (call), or don't (skip), or warn and don't (warn). (default = warn).") {}; /** * lombok configuration: {@code lombok.equalsAndHashCode.flagUsage} = {@code WARNING} | {@code ERROR}. diff --git a/src/core/lombok/core/configuration/CallSuperType.java b/src/core/lombok/core/configuration/CallSuperType.java new file mode 100644 index 00000000..b7152ea4 --- /dev/null +++ b/src/core/lombok/core/configuration/CallSuperType.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2015 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core.configuration; + +/** Used for lombok configuration for configuration whether or not to call the super implementation for certain lombok feature. */ +public enum CallSuperType { + CALL, SKIP, WARN; +} diff --git a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java index 77fe3a52..53785845 100644 --- a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java @@ -39,6 +39,7 @@ import lombok.EqualsAndHashCode; import lombok.core.AST.Kind; import lombok.core.handlers.HandlerUtil; import lombok.core.AnnotationValues; +import lombok.core.configuration.CallSuperType; import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; @@ -185,8 +186,23 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler nodesForEquality = new ArrayList(); diff --git a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java index 4bc79f03..f41fbd2f 100644 --- a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java @@ -32,6 +32,7 @@ import java.util.Collections; import lombok.ConfigurationKeys; import lombok.EqualsAndHashCode; import lombok.core.AST.Kind; +import lombok.core.configuration.CallSuperType; import lombok.core.AnnotationValues; import lombok.core.handlers.HandlerUtil; import lombok.javac.Javac; @@ -158,8 +159,23 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler nodesForEquality = new ListBuffer(); diff --git a/test/transform/resource/after-delombok/EqualsAndHashCodeConfigKeys1.java b/test/transform/resource/after-delombok/EqualsAndHashCodeConfigKeys1.java new file mode 100644 index 00000000..449a1b2e --- /dev/null +++ b/test/transform/resource/after-delombok/EqualsAndHashCodeConfigKeys1.java @@ -0,0 +1,52 @@ +class EqualsAndHashCodeConfigKeys1Parent { + @java.lang.Override + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (!(o instanceof EqualsAndHashCodeConfigKeys1Parent)) return false; + final EqualsAndHashCodeConfigKeys1Parent other = (EqualsAndHashCodeConfigKeys1Parent) o; + if (!other.canEqual((java.lang.Object) this)) return false; + return true; + } + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + protected boolean canEqual(final java.lang.Object other) { + return other instanceof EqualsAndHashCodeConfigKeys1Parent; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public int hashCode() { + int result = 1; + return result; + } +} +class EqualsAndHashCodeConfigKeys1 extends EqualsAndHashCodeConfigKeys1Parent { + int x; + @java.lang.Override + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (!(o instanceof EqualsAndHashCodeConfigKeys1)) return false; + final EqualsAndHashCodeConfigKeys1 other = (EqualsAndHashCodeConfigKeys1) o; + if (!other.canEqual((java.lang.Object) this)) return false; + if (this.x != other.x) return false; + return true; + } + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + protected boolean canEqual(final java.lang.Object other) { + return other instanceof EqualsAndHashCodeConfigKeys1; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public int hashCode() { + final int PRIME = 59; + int result = 1; + result = result * PRIME + this.x; + return result; + } +} diff --git a/test/transform/resource/after-delombok/EqualsAndHashCodeConfigKeys2.java b/test/transform/resource/after-delombok/EqualsAndHashCodeConfigKeys2.java new file mode 100644 index 00000000..43c271fa --- /dev/null +++ b/test/transform/resource/after-delombok/EqualsAndHashCodeConfigKeys2.java @@ -0,0 +1,54 @@ +class EqualsAndHashCodeConfigKeys2Parent { + @java.lang.Override + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (!(o instanceof EqualsAndHashCodeConfigKeys2Parent)) return false; + final EqualsAndHashCodeConfigKeys2Parent other = (EqualsAndHashCodeConfigKeys2Parent) o; + if (!other.canEqual((java.lang.Object) this)) return false; + return true; + } + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + protected boolean canEqual(final java.lang.Object other) { + return other instanceof EqualsAndHashCodeConfigKeys2Parent; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public int hashCode() { + int result = 1; + return result; + } +} +class EqualsAndHashCodeConfigKeys2 extends EqualsAndHashCodeConfigKeys2Parent { + int x; + @java.lang.Override + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (!(o instanceof EqualsAndHashCodeConfigKeys2)) return false; + final EqualsAndHashCodeConfigKeys2 other = (EqualsAndHashCodeConfigKeys2) o; + if (!other.canEqual((java.lang.Object) this)) return false; + if (!super.equals(o)) return false; + if (this.x != other.x) return false; + return true; + } + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + protected boolean canEqual(final java.lang.Object other) { + return other instanceof EqualsAndHashCodeConfigKeys2; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public int hashCode() { + final int PRIME = 59; + int result = 1; + result = result * PRIME + super.hashCode(); + result = result * PRIME + this.x; + return result; + } +} diff --git a/test/transform/resource/after-ecj/EqualsAndHashCodeConfigKeys1.java b/test/transform/resource/after-ecj/EqualsAndHashCodeConfigKeys1.java new file mode 100644 index 00000000..d2c7b201 --- /dev/null +++ b/test/transform/resource/after-ecj/EqualsAndHashCodeConfigKeys1.java @@ -0,0 +1,49 @@ +@lombok.EqualsAndHashCode class EqualsAndHashCodeConfigKeys1Parent { + EqualsAndHashCodeConfigKeys1Parent() { + super(); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") boolean equals(final java.lang.Object o) { + if ((o == this)) + return true; + if ((! (o instanceof EqualsAndHashCodeConfigKeys1Parent))) + return false; + final EqualsAndHashCodeConfigKeys1Parent other = (EqualsAndHashCodeConfigKeys1Parent) o; + if ((! other.canEqual((java.lang.Object) this))) + return false; + return true; + } + protected @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") boolean canEqual(final java.lang.Object other) { + return (other instanceof EqualsAndHashCodeConfigKeys1Parent); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") int hashCode() { + int result = 1; + return result; + } +} +@lombok.EqualsAndHashCode class EqualsAndHashCodeConfigKeys1 extends EqualsAndHashCodeConfigKeys1Parent { + int x; + EqualsAndHashCodeConfigKeys1() { + super(); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") boolean equals(final java.lang.Object o) { + if ((o == this)) + return true; + if ((! (o instanceof EqualsAndHashCodeConfigKeys1))) + return false; + final EqualsAndHashCodeConfigKeys1 other = (EqualsAndHashCodeConfigKeys1) o; + if ((! other.canEqual((java.lang.Object) this))) + return false; + if ((this.x != other.x)) + return false; + return true; + } + protected @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") boolean canEqual(final java.lang.Object other) { + return (other instanceof EqualsAndHashCodeConfigKeys1); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") int hashCode() { + final int PRIME = 59; + int result = 1; + result = ((result * PRIME) + this.x); + return result; + } +} \ No newline at end of file diff --git a/test/transform/resource/after-ecj/EqualsAndHashCodeConfigKeys2.java b/test/transform/resource/after-ecj/EqualsAndHashCodeConfigKeys2.java new file mode 100644 index 00000000..0b93f980 --- /dev/null +++ b/test/transform/resource/after-ecj/EqualsAndHashCodeConfigKeys2.java @@ -0,0 +1,52 @@ +@lombok.EqualsAndHashCode class EqualsAndHashCodeConfigKeys2Parent { + EqualsAndHashCodeConfigKeys2Parent() { + super(); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") boolean equals(final java.lang.Object o) { + if ((o == this)) + return true; + if ((! (o instanceof EqualsAndHashCodeConfigKeys2Parent))) + return false; + final EqualsAndHashCodeConfigKeys2Parent other = (EqualsAndHashCodeConfigKeys2Parent) o; + if ((! other.canEqual((java.lang.Object) this))) + return false; + return true; + } + protected @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") boolean canEqual(final java.lang.Object other) { + return (other instanceof EqualsAndHashCodeConfigKeys2Parent); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") int hashCode() { + int result = 1; + return result; + } +} +@lombok.EqualsAndHashCode class EqualsAndHashCodeConfigKeys2 extends EqualsAndHashCodeConfigKeys2Parent { + int x; + EqualsAndHashCodeConfigKeys2() { + super(); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") boolean equals(final java.lang.Object o) { + if ((o == this)) + return true; + if ((! (o instanceof EqualsAndHashCodeConfigKeys2))) + return false; + final EqualsAndHashCodeConfigKeys2 other = (EqualsAndHashCodeConfigKeys2) o; + if ((! other.canEqual((java.lang.Object) this))) + return false; + if ((! super.equals(o))) + return false; + if ((this.x != other.x)) + return false; + return true; + } + protected @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") boolean canEqual(final java.lang.Object other) { + return (other instanceof EqualsAndHashCodeConfigKeys2); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") int hashCode() { + final int PRIME = 59; + int result = 1; + result = ((result * PRIME) + super.hashCode()); + result = ((result * PRIME) + this.x); + return result; + } +} \ No newline at end of file diff --git a/test/transform/resource/before/EqualsAndHashCodeConfigKeys1.java b/test/transform/resource/before/EqualsAndHashCodeConfigKeys1.java new file mode 100644 index 00000000..38520d8f --- /dev/null +++ b/test/transform/resource/before/EqualsAndHashCodeConfigKeys1.java @@ -0,0 +1,9 @@ +//CONF: lombok.equalsAndHashCode.callSuper = skip + +@lombok.EqualsAndHashCode +class EqualsAndHashCodeConfigKeys1Parent { +} +@lombok.EqualsAndHashCode +class EqualsAndHashCodeConfigKeys1 extends EqualsAndHashCodeConfigKeys1Parent { + int x; +} diff --git a/test/transform/resource/before/EqualsAndHashCodeConfigKeys2.java b/test/transform/resource/before/EqualsAndHashCodeConfigKeys2.java new file mode 100644 index 00000000..e3cf5a9c --- /dev/null +++ b/test/transform/resource/before/EqualsAndHashCodeConfigKeys2.java @@ -0,0 +1,9 @@ +//CONF: lombok.equalsAndHashCode.callSuper = call + +@lombok.EqualsAndHashCode +class EqualsAndHashCodeConfigKeys2Parent { +} +@lombok.EqualsAndHashCode +class EqualsAndHashCodeConfigKeys2 extends EqualsAndHashCodeConfigKeys2Parent { + int x; +} diff --git a/website/features/EqualsAndHashCode.html b/website/features/EqualsAndHashCode.html index b220e955..ab43fd5a 100644 --- a/website/features/EqualsAndHashCode.html +++ b/website/features/EqualsAndHashCode.html @@ -21,7 +21,7 @@ this code will not be generated. By setting callSuper to true, you can include the equals and hashCode methods of your superclass in the generated methods. For hashCode, the result of super.hashCode() is included in the hash algorithm, and for equals, the generated method will return false if the super implementation thinks it is not equal to the passed in object. Be aware that not all equals implementations handle this situation properly. However, lombok-generated equals implementations do handle this situation properly, so you can safely call your superclass equals if it, too, has a lombok-generated equals method. If you have an explicit superclass you are forced to supply some value for callSuper to acknowledge that you've considered it; failure to do so results in a warning.

Setting callSuper to true when you don't extend anything (you extend java.lang.Object) is a compile-time error, because it would turn the generated equals() and hashCode() implementations into having the same behaviour as simply inheriting these methods from java.lang.Object: only the same object will be equal to each other and will have the same hashCode. Not setting callSuper to true when you extend another class generates a warning, because unless the superclass has no (equality-important) fields, lombok cannot generate an implementation for you that takes into account the fields declared by your superclasses. You'll need to write your own implementations, or rely on the - callSuper chaining facility. + callSuper chaining facility. You can also use the lombok.equalsAndHashCode.callSuper config key.

NEW in Lombok 0.10: Unless your class is final and extends java.lang.Object, lombok generates a canEqual method which means JPA proxies can still be equal to their base class, but subclasses that add new state don't break the equals contract. The complicated reasons for @@ -49,6 +49,8 @@

lombok.equalsAndHashCode.doNotUseGetters = [true | false] (default: false)
If set to true, lombok will access fields directly instead of using getters (if available) when generating equals and hashCode methods. The annotation parameter 'doNotUseGetters', if explicitly specified, takes precedence over this setting.
+
lombok.equalsAndHashCode.callSuper = [call | skip | warn] (default: warn)
+
If set to call, lombok will generate calls to the superclass implementation of hashCode and equals if your class extends something. If set to skip no such calls are generated. The default behaviour is like skip, with an additional warning.
lombok.equalsAndHashCode.flagUsage = [warning | error] (default: not set)
Lombok will flag any usage of @EqualsAndHashCode as a warning or error if configured.
-- cgit