aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xAUTHORS1
-rw-r--r--src/core/lombok/EqualsAndHashCode.java139
-rwxr-xr-xsrc/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java77
-rw-r--r--src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java38
-rw-r--r--test/transform/resource/after-delombok/EqualsAndHashCodeCache.java151
-rw-r--r--test/transform/resource/after-ecj/EqualsAndHashCodeCache.java167
-rw-r--r--test/transform/resource/before/EqualsAndHashCodeCache.java29
7 files changed, 531 insertions, 71 deletions
diff --git a/AUTHORS b/AUTHORS
index 9adf2005..1b315c1b 100755
--- a/AUTHORS
+++ b/AUTHORS
@@ -2,6 +2,7 @@ Lombok contributors in alphabetical order:
Adam Juraszek <juriad@gmail.com>
Aleksandr Zhelezniak <lekan1992@gmail.com>
+Andre Brait <andrebrait@gmail.com>
Bulgakov Alexander <buls@yandex.ru>
Caleb Brinkman <floralvikings@gmail.com>
Christian Nüssgens <christian@nuessgens.com>
diff --git a/src/core/lombok/EqualsAndHashCode.java b/src/core/lombok/EqualsAndHashCode.java
index d73afe13..7f60880c 100644
--- a/src/core/lombok/EqualsAndHashCode.java
+++ b/src/core/lombok/EqualsAndHashCode.java
@@ -27,18 +27,21 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * Generates implementations for the {@code equals} and {@code hashCode} methods inherited by all objects, based on relevant fields.
+ * Generates implementations for the {@code equals} and {@code hashCode} methods
+ * inherited by all objects, based on relevant fields.
* <p>
- * Complete documentation is found at <a href="https://projectlombok.org/features/EqualsAndHashCode">the project lombok features page for &#64;EqualsAndHashCode</a>.
+ * Complete documentation is found at
+ * <a href="https://projectlombok.org/features/EqualsAndHashCode">the project
+ * lombok features page for &#64;EqualsAndHashCode</a>.
*/
-@Target(ElementType.TYPE)
-@Retention(RetentionPolicy.SOURCE)
-public @interface EqualsAndHashCode {
+@Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) public @interface EqualsAndHashCode {
/**
- * Any fields listed here will not be taken into account in the generated {@code equals} and {@code hashCode} implementations.
- * Mutually exclusive with {@link #of()}.
+ * Any fields listed here will not be taken into account in the generated
+ * {@code equals} and {@code hashCode} implementations. Mutually exclusive
+ * with {@link #of()}.
* <p>
- * Will soon be marked {@code @Deprecated}; use the {@code @EqualsAndHashCode.Exclude} annotation instead.
+ * Will soon be marked {@code @Deprecated}; use the
+ * {@code @EqualsAndHashCode.Exclude} annotation instead.
*
* @return A list of fields to exclude.
*/
@@ -50,97 +53,129 @@ public @interface EqualsAndHashCode {
* <p>
* Mutually exclusive with {@link #exclude()}.
* <p>
- * Will soon be marked {@code @Deprecated}; use the {@code @EqualsAndHashCode.Include} annotation together with {@code @EqualsAndHashCode(onlyExplicitlyIncluded = true)}.
+ * Will soon be marked {@code @Deprecated}; use the
+ * {@code @EqualsAndHashCode.Include} annotation together with
+ * {@code @EqualsAndHashCode(onlyExplicitlyIncluded = true)}.
*
* @return A list of fields to use (<em>default</em>: all of them).
*/
String[] of() default {};
/**
- * Call on the superclass's implementations of {@code equals} and {@code hashCode} before calculating for the fields in this class.
+ * Call on the superclass's implementations of {@code equals} and
+ * {@code hashCode} before calculating for the fields in this class.
* <strong>default: false</strong>
*
- * @return Whether to call the superclass's {@code equals} implementation as part of the generated equals algorithm.
+ * @return Whether to call the superclass's {@code equals} implementation as
+ * part of the generated equals algorithm.
*/
boolean callSuper() default false;
/**
- * Normally, if getters are available, those are called. To suppress this and let the generated code use the fields directly, set this to {@code true}.
- * <strong>default: false</strong>
+ * Normally, if getters are available, those are called. To suppress this
+ * and let the generated code use the fields directly, set this to
+ * {@code true}. <strong>default: false</strong>
*
- * @return If {@code true}, always use direct field access instead of calling the getter method.
+ * @return If {@code true}, always use direct field access instead of
+ * calling the getter method.
*/
boolean doNotUseGetters() default false;
-
+
/**
- * Enables caching the result of {@code hashCode}.
- * This is useful to prevent running expensive calculations of {@code hashCode} multiple times for fully immutable objects, where it would always return the same result.
- * It is similar to what {@link java.lang.String#hashCode} does.
- * This should only be used for fully immutable classes (classes with all-immutable fields).
- * <strong>default: false</strong>
+ * Determines how the result of the {@code hashCode} method will be cached.
+ * <strong>default: {@link CacheStrategy#NEVER}</strong>
*
- * @return If {@code true}, cache the result of {@code hashCode} to avoid recalculating it in future invocations.
+ * @return The {@code hashCode} cache strategy to be used.
*/
- boolean cacheHashCode() default false;
+ CacheStrategy cacheStrategy() default CacheStrategy.NEVER;
/**
- * Any annotations listed here are put on the generated parameter of {@code equals} and {@code canEqual}.
- * This is useful to add for example a {@code Nullable} annotation.<br>
- * The syntax for this feature depends on JDK version (nothing we can do about that; it's to work around javac bugs).<br>
+ * Any annotations listed here are put on the generated parameter of
+ * {@code equals} and {@code canEqual}. This is useful to add for example a
+ * {@code Nullable} annotation.<br>
+ * The syntax for this feature depends on JDK version (nothing we can do
+ * about that; it's to work around javac bugs).<br>
* up to JDK7:<br>
- * {@code @EqualsAndHashCode(onParam=@__({@AnnotationsGoHere}))}<br>
+ * {@code @EqualsAndHashCode(onParam=@__({@AnnotationsGoHere}))}<br>
* from JDK8:<br>
- * {@code @EqualsAndHashCode(onParam_={@AnnotationsGohere})} // note the underscore after {@code onParam}.
- *
- * @return List of annotations to apply to the generated parameter in the {@code equals()} method.
+ * {@code @EqualsAndHashCode(onParam_={@AnnotationsGohere})} // note the
+ * underscore after {@code onParam}.
+ *
+ * @return List of annotations to apply to the generated parameter in the
+ * {@code equals()} method.
*/
AnyAnnotation[] onParam() default {};
/**
- * Placeholder annotation to enable the placement of annotations on the generated code.
+ * Placeholder annotation to enable the placement of annotations on the
+ * generated code.
+ *
* @deprecated Don't use this annotation, ever - Read the documentation.
*/
- @Deprecated
- @Retention(RetentionPolicy.SOURCE)
- @Target({})
- @interface AnyAnnotation {}
+ @Deprecated @Retention(RetentionPolicy.SOURCE) @Target({})
+ @interface AnyAnnotation {
+ }
/**
- * Only include fields and methods explicitly marked with {@code @EqualsAndHashCode.Include}.
- * Normally, all (non-static, non-transient) fields are included by default.
+ * Only include fields and methods explicitly marked with
+ * {@code @EqualsAndHashCode.Include}. Normally, all (non-static,
+ * non-transient) fields are included by default.
*
- * @return If {@code true}, don't include non-static non-transient fields automatically (default: {@code false}).
+ * @return If {@code true}, don't include non-static non-transient fields
+ * automatically (default: {@code false}).
*/
boolean onlyExplicitlyIncluded() default false;
/**
- * If present, do not include this field in the generated {@code equals} and {@code hashCode} methods.
+ * If present, do not include this field in the generated {@code equals} and
+ * {@code hashCode} methods.
*/
- @Target(ElementType.FIELD)
- @Retention(RetentionPolicy.SOURCE)
- public @interface Exclude {}
+ @Target(ElementType.FIELD) @Retention(RetentionPolicy.SOURCE) public @interface Exclude {
+ }
/**
- * Configure the behaviour of how this member is treated in the {@code equals} and {@code hashCode} implementation; if on a method, include the method's return value as part of calculating hashCode/equality.
+ * Configure the behaviour of how this member is treated in the
+ * {@code equals} and {@code hashCode} implementation; if on a method,
+ * include the method's return value as part of calculating
+ * hashCode/equality.
*/
- @Target({ElementType.FIELD, ElementType.METHOD})
- @Retention(RetentionPolicy.SOURCE)
- public @interface Include {
+ @Target({ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.SOURCE) public @interface Include {
/**
- * Defaults to the method name of the annotated member.
- * If on a method and the name equals the name of a default-included field, this member takes its place.
+ * Defaults to the method name of the annotated member. If on a method
+ * and the name equals the name of a default-included field, this member
+ * takes its place.
*
- * @return If present, this method serves as replacement for the named field.
+ * @return If present, this method serves as replacement for the named
+ * field.
*/
String replaces() default "";
-
+
/**
- * Higher ranks are considered first. Members of the same rank are considered in the order they appear in the source file.
+ * Higher ranks are considered first. Members of the same rank are
+ * considered in the order they appear in the source file.
*
- * If not explicitly set, the {@code default} rank for primitives is 1000, and for primitive wrappers 800.
+ * If not explicitly set, the {@code default} rank for primitives is
+ * 1000, and for primitive wrappers 800.
*
- * @return ordering within the generating {@code equals} and {@code hashCode} methods; higher numbers are considered first.
+ * @return ordering within the generating {@code equals} and
+ * {@code hashCode} methods; higher numbers are considered
+ * first.
*/
int rank() default 0;
}
+
+ public enum CacheStrategy {
+ /**
+ * Never cache. Perform the calculation every time the method is called.
+ */
+ NEVER,
+ /**
+ * Cache the result of the first invocation of {@code hashCode} and use it for subsequent invocations.
+ * This can improve performance in if all fields used for calculating the {@code hashCode} are immutable
+ * and thus every invocation of {@code hashCode} will always return the same value.
+ * <strong>Do not use this if there's <em>any</em> chance that different invocations of {@code hashCode}
+ * might return different values.</strong>
+ */
+ LAZY
+ }
}
diff --git a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java
index 7147343e..76a46814 100755
--- a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java
+++ b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java
@@ -36,6 +36,7 @@ import java.util.Set;
import lombok.AccessLevel;
import lombok.ConfigurationKeys;
import lombok.EqualsAndHashCode;
+import lombok.EqualsAndHashCode.CacheStrategy;
import lombok.core.AST.Kind;
import lombok.core.handlers.HandlerUtil;
import lombok.core.handlers.InclusionExclusionUtils;
@@ -47,6 +48,7 @@ import lombok.eclipse.Eclipse;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
import lombok.eclipse.handlers.EclipseHandlerUtil.MemberExistsResult;
+import lombok.javac.JavacTreeMaker;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
@@ -59,9 +61,11 @@ import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression;
import org.eclipse.jdt.internal.compiler.ast.EqualExpression;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.FalseLiteral;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.IfStatement;
import org.eclipse.jdt.internal.compiler.ast.InstanceOfExpression;
import org.eclipse.jdt.internal.compiler.ast.IntLiteral;
+import org.eclipse.jdt.internal.compiler.ast.IntLiteralMinValue;
import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
import org.eclipse.jdt.internal.compiler.ast.MarkerAnnotation;
import org.eclipse.jdt.internal.compiler.ast.MessageSend;
@@ -76,6 +80,7 @@ import org.eclipse.jdt.internal.compiler.ast.ReturnStatement;
import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference;
import org.eclipse.jdt.internal.compiler.ast.Statement;
+import org.eclipse.jdt.internal.compiler.ast.StringLiteral;
import org.eclipse.jdt.internal.compiler.ast.SuperReference;
import org.eclipse.jdt.internal.compiler.ast.ThisReference;
import org.eclipse.jdt.internal.compiler.ast.TrueLiteral;
@@ -84,16 +89,24 @@ import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.ast.UnaryExpression;
import org.eclipse.jdt.internal.compiler.ast.Wildcard;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.mangosdk.spi.ProviderFor;
+import com.sun.tools.javac.code.Flags;
+import com.sun.tools.javac.tree.JCTree.JCModifiers;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+
/**
* Handles the {@code EqualsAndHashCode} annotation for eclipse.
*/
@ProviderFor(EclipseAnnotationHandler.class)
public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndHashCode> {
+ private static final String HASH_CODE_CACHE_NAME = "$hashCodeCache";
+
+ private final char[] HASH_CODE_CACHE_NAME_ARR = HASH_CODE_CACHE_NAME.toCharArray();
private final char[] PRIME = "PRIME".toCharArray();
private final char[] RESULT = "result".toCharArray();
@@ -116,7 +129,9 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH
boolean doNotUseGetters = annotation.isExplicit("doNotUseGetters") || doNotUseGettersConfiguration == null ? ann.doNotUseGetters() : doNotUseGettersConfiguration;
FieldAccess fieldAccess = doNotUseGetters ? FieldAccess.PREFER_FIELD : FieldAccess.GETTER;
- generateMethods(annotationNode.up(), annotationNode, members, callSuper, true, fieldAccess, onParam);
+ boolean cacheHashCode = ann.cacheStrategy() == CacheStrategy.LAZY;
+
+ generateMethods(annotationNode.up(), annotationNode, members, callSuper, true, cacheHashCode, fieldAccess, onParam);
}
public void generateEqualsAndHashCodeForType(EclipseNode typeNode, EclipseNode errorNode) {
@@ -130,11 +145,11 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH
Boolean doNotUseGettersConfiguration = typeNode.getAst().readConfiguration(ConfigurationKeys.EQUALS_AND_HASH_CODE_DO_NOT_USE_GETTERS);
FieldAccess access = doNotUseGettersConfiguration == null || !doNotUseGettersConfiguration ? FieldAccess.GETTER : FieldAccess.PREFER_FIELD;
- generateMethods(typeNode, errorNode, members, null, false, access, new ArrayList<Annotation>());
+ generateMethods(typeNode, errorNode, members, null, false, false, access, new ArrayList<Annotation>());
}
public void generateMethods(EclipseNode typeNode, EclipseNode errorNode, List<Included<EclipseNode, EqualsAndHashCode.Include>> members,
- Boolean callSuper, boolean whineIfExists, FieldAccess fieldAccess, List<Annotation> onParam) {
+ Boolean callSuper, boolean whineIfExists, boolean cacheHashCode, FieldAccess fieldAccess, List<Annotation> onParam) {
TypeDeclaration typeDecl = null;
@@ -222,12 +237,38 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH
injectMethod(typeNode, canEqualMethod);
}
- MethodDeclaration hashCodeMethod = createHashCode(typeNode, members, callSuper, errorNode.get(), fieldAccess);
+ if (cacheHashCode){
+ if (!isFinal) {
+ String msg = "Not caching the result of hashCode: Annotated type is not final.";
+ errorNode.addWarning(msg);
+ cacheHashCode = false;
+ } else if (fieldExists(HASH_CODE_CACHE_NAME, typeNode) != MemberExistsResult.NOT_EXISTS) {
+ String msg = String.format("Not caching the result of hashCode: A field named %s already exists.", HASH_CODE_CACHE_NAME);
+ errorNode.addWarning(msg);
+ cacheHashCode = false;
+ } else {
+ createHashCodeCacheField(typeNode, errorNode.get());
+ }
+ }
+
+ MethodDeclaration hashCodeMethod = createHashCode(typeNode, members, callSuper, cacheHashCode, errorNode.get(), fieldAccess);
hashCodeMethod.traverse(new SetGeneratedByVisitor(errorNode.get()), ((TypeDeclaration)typeNode.get()).scope);
injectMethod(typeNode, hashCodeMethod);
}
+
+ private void createHashCodeCacheField(EclipseNode typeNode, ASTNode source) {
+ FieldDeclaration hashCodeCacheDecl = new FieldDeclaration(HASH_CODE_CACHE_NAME_ARR, 0, -1);
+ hashCodeCacheDecl.modifiers = ClassFileConstants.AccPrivate | ClassFileConstants.AccTransient;
+ hashCodeCacheDecl.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG;
+ hashCodeCacheDecl.type = TypeReference.baseTypeReference(TypeIds.T_int, 0);
+ hashCodeCacheDecl.declarationSourceEnd = -1;
+ hashCodeCacheDecl.initialization = makeIntLiteral("0".toCharArray(), source);
+ injectFieldAndMarkGenerated(typeNode, hashCodeCacheDecl);
+ setGeneratedBy(hashCodeCacheDecl, source);
+ setGeneratedBy(hashCodeCacheDecl.type, source);
+ }
- public MethodDeclaration createHashCode(EclipseNode type, Collection<Included<EclipseNode, EqualsAndHashCode.Include>> members, boolean callSuper, ASTNode source, FieldAccess fieldAccess) {
+ public MethodDeclaration createHashCode(EclipseNode type, Collection<Included<EclipseNode, EqualsAndHashCode.Include>> members, boolean callSuper, boolean cacheHashCode, ASTNode source, FieldAccess fieldAccess) {
int pS = source.sourceStart, pE = source.sourceEnd;
long p = (long) pS << 32 | pE;
@@ -262,6 +303,20 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH
}
}
+ /* if ($hashCodeCache != 0) return $hashCodeCache; */ {
+ if (cacheHashCode) {
+ SingleNameReference hashCodeCacheRef = new SingleNameReference(HASH_CODE_CACHE_NAME_ARR, p);
+ setGeneratedBy(hashCodeCacheRef, source);
+ EqualExpression cacheNotZero = new EqualExpression(hashCodeCacheRef, makeIntLiteral("0".toCharArray(), source), OperatorIds.NOT_EQUAL);
+ setGeneratedBy(cacheNotZero, source);
+ ReturnStatement returnCache = new ReturnStatement(hashCodeCacheRef, pS, pS);
+ setGeneratedBy(returnCache, source);
+ IfStatement ifStatement = new IfStatement(cacheNotZero, returnCache, pS, pE);
+ setGeneratedBy(ifStatement, source);
+ statements.add(ifStatement);
+ }
+ }
+
/* final int PRIME = X; */ {
/* Without members, PRIME isn't used, as that would trigger a 'local variable not used' warning. */
if (!isEmpty) {
@@ -391,6 +446,18 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH
}
}
+ /* $hashCodeCache = result; */ {
+ if (cacheHashCode) {
+ SingleNameReference hashCodeCacheRef = new SingleNameReference(HASH_CODE_CACHE_NAME_ARR, p);
+ setGeneratedBy(hashCodeCacheRef, source);
+ SingleNameReference resultRef = new SingleNameReference(RESULT, p);
+ setGeneratedBy(resultRef, source);
+ Assignment cacheResult = new Assignment(hashCodeCacheRef, resultRef, pE);
+ setGeneratedBy(cacheResult, source);
+ statements.add(cacheResult);
+ }
+ }
+
/* return result; */ {
SingleNameReference resultRef = new SingleNameReference(RESULT, p);
setGeneratedBy(resultRef, source);
diff --git a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java
index 6c3eccf4..19f6d750 100644
--- a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java
+++ b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java
@@ -56,6 +56,7 @@ import com.sun.tools.javac.util.Name;
import lombok.ConfigurationKeys;
import lombok.EqualsAndHashCode;
+import lombok.EqualsAndHashCode.CacheStrategy;
import lombok.core.AST.Kind;
import lombok.core.AnnotationValues;
import lombok.core.configuration.CallSuperType;
@@ -94,7 +95,7 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas
boolean doNotUseGetters = annotation.isExplicit("doNotUseGetters") || doNotUseGettersConfiguration == null ? ann.doNotUseGetters() : doNotUseGettersConfiguration;
FieldAccess fieldAccess = doNotUseGetters ? FieldAccess.PREFER_FIELD : FieldAccess.GETTER;
- boolean cacheHashCode = ann.cacheHashCode();
+ boolean cacheHashCode = ann.cacheStrategy() == CacheStrategy.LAZY;
generateMethods(typeNode, annotationNode, members, callSuper, true, cacheHashCode, fieldAccess, onParam);
}
@@ -209,17 +210,21 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas
source.addWarning(msg);
cacheHashCode = false;
} else {
- JavacTreeMaker maker = typeNode.getTreeMaker();
- JCModifiers mods = maker.Modifiers(Flags.PRIVATE | Flags.TRANSIENT);
- JCVariableDecl hashCodeCacheField = maker.VarDef(mods, typeNode.toName(HASH_CODE_CACHE_NAME), maker.TypeIdent(CTC_INT), maker.Literal(CTC_INT, 0));
- injectFieldAndMarkGenerated(typeNode, hashCodeCacheField);
- recursiveSetGeneratedBy(hashCodeCacheField, source.get(), typeNode.getContext());
+ createHashCodeCacheField(typeNode, source.get());
}
}
JCMethodDecl hashCodeMethod = createHashCode(typeNode, members, callSuper, cacheHashCode, fieldAccess, source.get());
injectMethod(typeNode, hashCodeMethod);
}
+
+ private void createHashCodeCacheField(JavacNode typeNode, JCTree source) {
+ JavacTreeMaker maker = typeNode.getTreeMaker();
+ JCModifiers mods = maker.Modifiers(Flags.PRIVATE | Flags.TRANSIENT);
+ JCVariableDecl hashCodeCacheField = maker.VarDef(mods, typeNode.toName(HASH_CODE_CACHE_NAME), maker.TypeIdent(CTC_INT), maker.Literal(CTC_INT, 0));
+ injectFieldAndMarkGenerated(typeNode, hashCodeCacheField);
+ recursiveSetGeneratedBy(hashCodeCacheField, source, typeNode.getContext());
+ }
public JCMethodDecl createHashCode(JavacNode typeNode, java.util.List<Included<JavacNode, EqualsAndHashCode.Include>> members, boolean callSuper, boolean cacheHashCode, FieldAccess fieldAccess, JCTree source) {
JavacTreeMaker maker = typeNode.getTreeMaker();
@@ -233,18 +238,20 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas
JCExpression returnType = maker.TypeIdent(CTC_INT);
ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>();
- Name cacheHashCodeName = typeNode.toName(HASH_CODE_CACHE_NAME);
- if (cacheHashCode) {
- JCExpression cacheNotZero = maker.Binary(CTC_NOT_EQUAL, maker.Ident(cacheHashCodeName), maker.Literal(CTC_INT, 0));
- statements.append(maker.If(cacheNotZero, maker.Return(maker.Ident(cacheHashCodeName)), null));
- }
-
Name primeName = typeNode.toName(PRIME_NAME);
Name resultName = typeNode.toName(RESULT_NAME);
long finalFlag = JavacHandlerUtil.addFinalIfNeeded(0L, typeNode.getContext());
boolean isEmpty = members.isEmpty();
+ /* if ($hashCodeCache != 0) return $hashCodeCache; */ {
+ if (cacheHashCode) {
+ Name cacheHashCodeName = typeNode.toName(HASH_CODE_CACHE_NAME);
+ JCExpression cacheNotZero = maker.Binary(CTC_NOT_EQUAL, maker.Ident(cacheHashCodeName), maker.Literal(CTC_INT, 0));
+ statements.append(maker.If(cacheNotZero, maker.Return(maker.Ident(cacheHashCodeName)), null));
+ }
+ }
+
/* final int PRIME = X; */ {
if (!isEmpty) {
statements.append(maker.VarDef(maker.Modifiers(finalFlag), primeName, maker.TypeIdent(CTC_INT), maker.Literal(HandlerUtil.primeForHashcode())));
@@ -334,8 +341,11 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas
}
}
- if (cacheHashCode) {
- statements.append(maker.Exec(maker.Assign(maker.Ident(cacheHashCodeName), maker.Ident(resultName))));
+ /* $hashCodeCache = result; */ {
+ if (cacheHashCode) {
+ Name cacheHashCodeName = typeNode.toName(HASH_CODE_CACHE_NAME);
+ statements.append(maker.Exec(maker.Assign(maker.Ident(cacheHashCodeName), maker.Ident(resultName))));
+ }
}
/* return result; */ {
diff --git a/test/transform/resource/after-delombok/EqualsAndHashCodeCache.java b/test/transform/resource/after-delombok/EqualsAndHashCodeCache.java
new file mode 100644
index 00000000..e3e266aa
--- /dev/null
+++ b/test/transform/resource/after-delombok/EqualsAndHashCodeCache.java
@@ -0,0 +1,151 @@
+class EqualsAndHashCode {
+ int x;
+ boolean[] y;
+ Object[] z;
+ String a;
+ String b;
+ @java.lang.Override
+ @java.lang.SuppressWarnings("all")
+ public boolean equals(final java.lang.Object o) {
+ if (o == this) return true;
+ if (!(o instanceof EqualsAndHashCode)) return false;
+ final EqualsAndHashCode other = (EqualsAndHashCode) o;
+ if (!other.canEqual((java.lang.Object) this)) return false;
+ if (this.x != other.x) return false;
+ if (!java.util.Arrays.equals(this.y, other.y)) return false;
+ if (!java.util.Arrays.deepEquals(this.z, other.z)) return false;
+ final java.lang.Object this$a = this.a;
+ final java.lang.Object other$a = other.a;
+ if (this$a == null ? other$a != null : !this$a.equals(other$a)) return false;
+ final java.lang.Object this$b = this.b;
+ final java.lang.Object other$b = other.b;
+ if (this$b == null ? other$b != null : !this$b.equals(other$b)) return false;
+ return true;
+ }
+ @java.lang.SuppressWarnings("all")
+ protected boolean canEqual(final java.lang.Object other) {
+ return other instanceof EqualsAndHashCode;
+ }
+ @java.lang.Override
+ @java.lang.SuppressWarnings("all")
+ public int hashCode() {
+ final int PRIME = 59;
+ int result = 1;
+ result = result * PRIME + this.x;
+ result = result * PRIME + java.util.Arrays.hashCode(this.y);
+ result = result * PRIME + java.util.Arrays.deepHashCode(this.z);
+ final java.lang.Object $a = this.a;
+ result = result * PRIME + ($a == null ? 43 : $a.hashCode());
+ final java.lang.Object $b = this.b;
+ result = result * PRIME + ($b == null ? 43 : $b.hashCode());
+ return result;
+ }
+}
+final class EqualsAndHashCode2 {
+ int x;
+ long y;
+ float f;
+ double d;
+ boolean b;
+ private transient int $hashCodeCache = 0;
+ @java.lang.Override
+ @java.lang.SuppressWarnings("all")
+ public boolean equals(final java.lang.Object o) {
+ if (o == this) return true;
+ if (!(o instanceof EqualsAndHashCode2)) return false;
+ final EqualsAndHashCode2 other = (EqualsAndHashCode2) o;
+ if (this.x != other.x) return false;
+ if (this.y != other.y) return false;
+ if (java.lang.Float.compare(this.f, other.f) != 0) return false;
+ if (java.lang.Double.compare(this.d, other.d) != 0) return false;
+ if (this.b != other.b) return false;
+ return true;
+ }
+ @java.lang.Override
+ @java.lang.SuppressWarnings("all")
+ public int hashCode() {
+ if ($hashCodeCache != 0) return $hashCodeCache;
+ final int PRIME = 59;
+ int result = 1;
+ result = result * PRIME + this.x;
+ final long $y = this.y;
+ result = result * PRIME + (int) ($y >>> 32 ^ $y);
+ result = result * PRIME + java.lang.Float.floatToIntBits(this.f);
+ final long $d = java.lang.Double.doubleToLongBits(this.d);
+ result = result * PRIME + (int) ($d >>> 32 ^ $d);
+ result = result * PRIME + (this.b ? 79 : 97);
+ $hashCodeCache = result;
+ return result;
+ }
+}
+final class EqualsAndHashCode3 extends EqualsAndHashCode {
+ private transient int $hashCodeCache = 0;
+ @java.lang.Override
+ @java.lang.SuppressWarnings("all")
+ public boolean equals(final java.lang.Object o) {
+ if (o == this) return true;
+ if (!(o instanceof EqualsAndHashCode3)) return false;
+ final EqualsAndHashCode3 other = (EqualsAndHashCode3) o;
+ if (!other.canEqual((java.lang.Object) this)) return false;
+ return true;
+ }
+ @java.lang.SuppressWarnings("all")
+ protected boolean canEqual(final java.lang.Object other) {
+ return other instanceof EqualsAndHashCode3;
+ }
+ @java.lang.Override
+ @java.lang.SuppressWarnings("all")
+ public int hashCode() {
+ if ($hashCodeCache != 0) return $hashCodeCache;
+ final int result = 1;
+ $hashCodeCache = result;
+ return result;
+ }
+}
+class EqualsAndHashCode4 extends EqualsAndHashCode {
+ @java.lang.Override
+ @java.lang.SuppressWarnings("all")
+ public boolean equals(final java.lang.Object o) {
+ if (o == this) return true;
+ if (!(o instanceof EqualsAndHashCode4)) return false;
+ final EqualsAndHashCode4 other = (EqualsAndHashCode4) o;
+ if (!other.canEqual((java.lang.Object) this)) return false;
+ if (!super.equals(o)) return false;
+ return true;
+ }
+ @java.lang.SuppressWarnings("all")
+ protected boolean canEqual(final java.lang.Object other) {
+ return other instanceof EqualsAndHashCode4;
+ }
+ @java.lang.Override
+ @java.lang.SuppressWarnings("all")
+ public int hashCode() {
+ final int result = super.hashCode();
+ return result;
+ }
+}
+class EqualsAndHashCode5 extends EqualsAndHashCode {
+ private transient int $hashCodeCache = 0;
+ @java.lang.Override
+ @java.lang.SuppressWarnings("all")
+ public boolean equals(final java.lang.Object o) {
+ if (o == this) return true;
+ if (!(o instanceof EqualsAndHashCode5)) return false;
+ final EqualsAndHashCode5 other = (EqualsAndHashCode4) o;
+ if (!other.canEqual((java.lang.Object) this)) return false;
+ if (!super.equals(o)) return false;
+ return true;
+ }
+ @java.lang.SuppressWarnings("all")
+ protected boolean canEqual(final java.lang.Object other) {
+ return other instanceof EqualsAndHashCode5;
+ }
+ @java.lang.Override
+ @java.lang.SuppressWarnings("all")
+ public int hashCode() {
+ if ($hashCodeCache != 0) return $hashCodeCache;
+ final int result = super.hashCode();
+ $hashCodeCache = result;
+ return result;
+ }
+}
diff --git a/test/transform/resource/after-ecj/EqualsAndHashCodeCache.java b/test/transform/resource/after-ecj/EqualsAndHashCodeCache.java
new file mode 100644
index 00000000..4eb4fbb3
--- /dev/null
+++ b/test/transform/resource/after-ecj/EqualsAndHashCodeCache.java
@@ -0,0 +1,167 @@
+@lombok.EqualsAndHashCode(cacheStrategy = lombok.EqualsAndHashCode.CacheStrategy.LAZY) class EqualsAndHashCode {
+ int x;
+ boolean[] y;
+ Object[] z;
+ String a;
+ String b;
+ EqualsAndHashCode() {
+ super();
+ }
+ public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) {
+ if ((o == this))
+ return true;
+ if ((! (o instanceof EqualsAndHashCode)))
+ return false;
+ final EqualsAndHashCode other = (EqualsAndHashCode) o;
+ if ((! other.canEqual((java.lang.Object) this)))
+ return false;
+ if ((this.x != other.x))
+ return false;
+ if ((! java.util.Arrays.equals(this.y, other.y)))
+ return false;
+ if ((! java.util.Arrays.deepEquals(this.z, other.z)))
+ return false;
+ final java.lang.Object this$a = this.a;
+ final java.lang.Object other$a = other.a;
+ if (((this$a == null) ? (other$a != null) : (! this$a.equals(other$a))))
+ return false;
+ final java.lang.Object this$b = this.b;
+ final java.lang.Object other$b = other.b;
+ if (((this$b == null) ? (other$b != null) : (! this$b.equals(other$b))))
+ return false;
+ return true;
+ }
+ protected @java.lang.SuppressWarnings("all") boolean canEqual(final java.lang.Object other) {
+ return (other instanceof EqualsAndHashCode);
+ }
+ public @java.lang.Override @java.lang.SuppressWarnings("all") int hashCode() {
+ final int PRIME = 59;
+ int result = 1;
+ result = ((result * PRIME) + this.x);
+ result = ((result * PRIME) + java.util.Arrays.hashCode(this.y));
+ result = ((result * PRIME) + java.util.Arrays.deepHashCode(this.z));
+ final java.lang.Object $a = this.a;
+ result = ((result * PRIME) + (($a == null) ? 43 : $a.hashCode()));
+ final java.lang.Object $b = this.b;
+ result = ((result * PRIME) + (($b == null) ? 43 : $b.hashCode()));
+ return result;
+ }
+}
+final @lombok.EqualsAndHashCode(cacheStrategy = lombok.EqualsAndHashCode.CacheStrategy.LAZY) class EqualsAndHashCode2 {
+ int x;
+ long y;
+ float f;
+ double d;
+ boolean b;
+ private transient int $hashCodeCache = 0;
+ EqualsAndHashCode2() {
+ super();
+ }
+ public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) {
+ if ((o == this))
+ return true;
+ if ((! (o instanceof EqualsAndHashCode2)))
+ return false;
+ final EqualsAndHashCode2 other = (EqualsAndHashCode2) o;
+ if ((this.x != other.x))
+ return false;
+ if ((this.y != other.y))
+ return false;
+ if ((java.lang.Float.compare(this.f, other.f) != 0))
+ return false;
+ if ((java.lang.Double.compare(this.d, other.d) != 0))
+ return false;
+ if ((this.b != other.b))
+ return false;
+ return true;
+ }
+ public @java.lang.Override @java.lang.SuppressWarnings("all") int hashCode() {
+ if ($hashCodeCache != 0)
+ return $hashCodeCache;
+ final int PRIME = 59;
+ int result = 1;
+ result = ((result * PRIME) + this.x);
+ final long $y = this.y;
+ result = ((result * PRIME) + (int) ($y ^ ($y >>> 32)));
+ result = ((result * PRIME) + java.lang.Float.floatToIntBits(this.f));
+ final long $d = java.lang.Double.doubleToLongBits(this.d);
+ result = ((result * PRIME) + (int) ($d ^ ($d >>> 32)));
+ result = ((result * PRIME) + (this.b ? 79 : 97));
+ $hashCodeCache = result;
+ return result;
+ }
+}
+final @lombok.EqualsAndHashCode(callSuper=false, cacheStrategy = lombok.EqualsAndHashCode.CacheStrategy.LAZY) class EqualsAndHashCode3 extends EqualsAndHashCode {
+ EqualsAndHashCode3() {
+ super();
+ }
+ public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) {
+ if ((o == this))
+ return true;
+ if ((! (o instanceof EqualsAndHashCode3)))
+ return false;
+ final EqualsAndHashCode3 other = (EqualsAndHashCode3) o;
+ if ((! other.canEqual((java.lang.Object) this)))
+ return false;
+ return true;
+ }
+ protected @java.lang.SuppressWarnings("all") boolean canEqual(final java.lang.Object other) {
+ return (other instanceof EqualsAndHashCode3);
+ }
+ public @java.lang.Override @java.lang.SuppressWarnings("all") int hashCode() {
+ final int result = 1;
+ return result;
+ }
+}
+@lombok.EqualsAndHashCode(callSuper=true, cacheStrategy = lombok.EqualsAndHashCode.CacheStrategy.LAZY) class EqualsAndHashCode4 extends EqualsAndHashCode {
+ EqualsAndHashCode4() {
+ super();
+ }
+ public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) {
+ if ((o == this))
+ return true;
+ if ((! (o instanceof EqualsAndHashCode4)))
+ return false;
+ final EqualsAndHashCode4 other = (EqualsAndHashCode4) o;
+ if ((! other.canEqual((java.lang.Object) this)))
+ return false;
+ if ((! super.equals(o)))
+ return false;
+ return true;
+ }
+ protected @java.lang.SuppressWarnings("all") boolean canEqual(final java.lang.Object other) {
+ return (other instanceof EqualsAndHashCode4);
+ }
+ public @java.lang.Override @java.lang.SuppressWarnings("all") int hashCode() {
+ final int result = super.hashCode();
+ return result;
+ }
+}
+@lombok.EqualsAndHashCode(callSuper=true, cacheStrategy = lombok.EqualsAndHashCode.CacheStrategy.LAZY) class EqualsAndHashCode5 extends EqualsAndHashCode {
+ private transient int $hashCodeCache = 0;
+ EqualsAndHashCode5() {
+ super();
+ }
+ public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) {
+ if ((o == this))
+ return true;
+ if ((! (o instanceof EqualsAndHashCode5)))
+ return false;
+ final EqualsAndHashCode5 other = (EqualsAndHashCode5) o;
+ if ((! other.canEqual((java.lang.Object) this)))
+ return false;
+ if ((! super.equals(o)))
+ return false;
+ return true;
+ }
+ protected @java.lang.SuppressWarnings("all") boolean canEqual(final java.lang.Object other) {
+ return (other instanceof EqualsAndHashCode5);
+ }
+ public @java.lang.Override @java.lang.SuppressWarnings("all") int hashCode() {
+ if ($hashCodeCache != 0)
+ return $hashCodeCache;
+ final int result = super.hashCode();
+ $hashCodeCache = result;
+ return result;
+ }
+}
diff --git a/test/transform/resource/before/EqualsAndHashCodeCache.java b/test/transform/resource/before/EqualsAndHashCodeCache.java
new file mode 100644
index 00000000..3c84ee59
--- /dev/null
+++ b/test/transform/resource/before/EqualsAndHashCodeCache.java
@@ -0,0 +1,29 @@
+@lombok.EqualsAndHashCode(cacheStrategy = lombok.EqualsAndHashCode.CacheStrategy.LAZY)
+class EqualsAndHashCode {
+ int x;
+ boolean[] y;
+ Object[] z;
+ String a;
+ String b;
+}
+
+@lombok.EqualsAndHashCode(cacheStrategy = lombok.EqualsAndHashCode.CacheStrategy.LAZY)
+final class EqualsAndHashCode2 {
+ int x;
+ long y;
+ float f;
+ double d;
+ boolean b;
+}
+
+@lombok.EqualsAndHashCode(callSuper=false, cacheStrategy = lombok.EqualsAndHashCode.CacheStrategy.LAZY)
+final class EqualsAndHashCode3 extends EqualsAndHashCode {
+}
+
+@lombok.EqualsAndHashCode(callSuper=true, cacheStrategy = lombok.EqualsAndHashCode.CacheStrategy.LAZY)
+class EqualsAndHashCode4 extends EqualsAndHashCode {
+}
+
+@lombok.EqualsAndHashCode(callSuper=true, cacheStrategy = lombok.EqualsAndHashCode.CacheStrategy.LAZY)
+final class EqualsAndHashCode5 extends EqualsAndHashCode {
+} \ No newline at end of file