diff options
12 files changed, 301 insertions, 6 deletions
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<Boolean> EQUALS_AND_HASH_CODE_DO_NOT_USE_GETTERS = new ConfigurationKey<Boolean>("lombok.equalsAndHashCode.doNotUseGetters", "Don't call the getters but use the fields directly in the generated equalsAndHashCode method (default = false).") {}; + public static final ConfigurationKey<Boolean> EQUALS_AND_HASH_CODE_DO_NOT_USE_GETTERS = new ConfigurationKey<Boolean>("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<CallSuperType> EQUALS_AND_HASH_CODE_CALL_SUPER = new ConfigurationKey<CallSuperType>("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<EqualsAndH return; } - 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."); + if (implicitCallSuper && !isDirectDescendantOfObject) { + CallSuperType cst = typeNode.getAst().readConfiguration(ConfigurationKeys.EQUALS_AND_HASH_CODE_CALL_SUPER); + if (cst == null) cst = CallSuperType.WARN; + + switch (cst) { + default: + case WARN: + 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."); + callSuper = false; + break; + case SKIP: + callSuper = false; + break; + case CALL: + callSuper = true; + break; + } } List<EclipseNode> nodesForEquality = new ArrayList<EclipseNode>(); 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<EqualsAndHas return; } - if (!isDirectDescendantOfObject && !callSuper && implicitCallSuper) { - source.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."); + if (implicitCallSuper && !isDirectDescendantOfObject) { + CallSuperType cst = typeNode.getAst().readConfiguration(ConfigurationKeys.EQUALS_AND_HASH_CODE_CALL_SUPER); + if (cst == null) cst = CallSuperType.WARN; + + switch (cst) { + default: + case WARN: + source.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."); + callSuper = false; + break; + case SKIP: + callSuper = false; + break; + case CALL: + callSuper = true; + break; + } } ListBuffer<JavacNode> nodesForEquality = new ListBuffer<JavacNode>(); 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 <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 false if the super implementation thinks it is not equal to the passed in object. Be aware that not all <code>equals</code> implementations handle this situation properly. However, lombok-generated <code>equals</code> implementations <strong>do</strong> handle this situation properly, so you can safely call your superclass equals if it, too, has a lombok-generated <code>equals</code> method. If you have an explicit superclass you are forced to supply some value for <code>callSuper</code> to acknowledge that you've considered it; failure to do so results in a warning.<br /> </p><p> Setting <code>callSuper</code> to <em>true</em> when you don't extend anything (you extend <code>java.lang.Object</code>) is a compile-time error, because it would turn the generated <code>equals()</code> and <code>hashCode()</code> implementations into having the same behaviour as simply inheriting these methods from <code>java.lang.Object</code>: only the same object will be equal to each other and will have the same hashCode. Not setting <code>callSuper</code> to <em>true</em> 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 - <code>callSuper</code> chaining facility. + <code>callSuper</code> chaining facility. You can also use the <code>lombok.equalsAndHashCode.callSuper</code> config key. </p><p> <em>NEW in Lombok 0.10: </em>Unless your class is <code>final</code> and extends <code>java.lang.Object</code>, lombok generates a <code>canEqual</code> 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 @@ <dl> <dt><code>lombok.equalsAndHashCode.doNotUseGetters</code> = [<code>true</code> | <code>false</code>] (default: false)</dt> <dd>If set to <code>true</code>, lombok will access fields directly instead of using getters (if available) when generating <code>equals</code> and <code>hashCode</code> methods. The annotation parameter '<code>doNotUseGetters</code>', if explicitly specified, takes precedence over this setting.</dd> + <dt><code>lombok.equalsAndHashCode.callSuper</code> = [<code>call</code> | <code>skip</code> | <code>warn</code>] (default: warn)</dt> + <dd>If set to <code>call</code>, lombok will generate calls to the superclass implementation of <code>hashCode</code> and <code>equals</code> if your class extends something. If set to <code>skip</code> no such calls are generated. The default behaviour is like <code>skip</code>, with an additional warning.</dd> <dt><code>lombok.equalsAndHashCode.flagUsage</code> = [<code>warning</code> | <code>error</code>] (default: not set)</dt> <dd>Lombok will flag any usage of <code>@EqualsAndHashCode</code> as a warning or error if configured.</dd> </dl> |