Attempt to implement cacheHashCode

This commit is contained in:
Andre Brait authored and Roel Spilker committed 2020-09-24 22:25:15 +02:00
1 parent f397c2b482
commit 3019e73f96
2 files changed
+52 -9

No files matched your search

+11
View File
@@ -71,6 +71,17 @@ public @interface EqualsAndHashCode {
* @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>
*
* @return If {@code true}, cache the result of {@code hashCode} to avoid recalculating it in future invocations.
*/
boolean cacheHashCode() default false;
/**
* Any annotations listed here are put on the generated parameter of {@code equals} and {@code canEqual}.
@@ -76,7 +76,8 @@ import lombok.javac.handlers.JavacHandlerUtil.MemberExistsResult;
public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHashCode> {
private static final String RESULT_NAME = "result";
private static final String PRIME_NAME = "PRIME";
private static final String HASH_CODE_CACHE_NAME = "$hashCodeCache";
@Override public void handle(AnnotationValues<EqualsAndHashCode> annotation, JCAnnotation ast, JavacNode annotationNode) {
handleFlagUsage(annotationNode, ConfigurationKeys.EQUALS_AND_HASH_CODE_FLAG_USAGE, "@EqualsAndHashCode");
@@ -92,8 +93,10 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas
Boolean doNotUseGettersConfiguration = annotationNode.getAst().readConfiguration(ConfigurationKeys.EQUALS_AND_HASH_CODE_DO_NOT_USE_GETTERS);
boolean doNotUseGetters = annotation.isExplicit("doNotUseGetters") || doNotUseGettersConfiguration == null ? ann.doNotUseGetters() : doNotUseGettersConfiguration;
FieldAccess fieldAccess = doNotUseGetters ? FieldAccess.PREFER_FIELD : FieldAccess.GETTER;
generateMethods(typeNode, annotationNode, members, callSuper, true, fieldAccess, onParam);
boolean cacheHashCode = ann.cacheHashCode();
generateMethods(typeNode, annotationNode, members, callSuper, true, cacheHashCode, fieldAccess, onParam);
}
public void generateEqualsAndHashCodeForType(JavacNode typeNode, JavacNode source) {
@@ -104,14 +107,14 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas
Boolean doNotUseGettersConfiguration = typeNode.getAst().readConfiguration(ConfigurationKeys.EQUALS_AND_HASH_CODE_DO_NOT_USE_GETTERS);
FieldAccess access = doNotUseGettersConfiguration == null || !doNotUseGettersConfiguration ? FieldAccess.GETTER : FieldAccess.PREFER_FIELD;
java.util.List<Included<JavacNode, EqualsAndHashCode.Include>> members = InclusionExclusionUtils.handleEqualsAndHashCodeMarking(typeNode, null, null);
generateMethods(typeNode, source, members, null, false, access, List.<JCAnnotation>nil());
generateMethods(typeNode, source, members, null, false, false, access, List.<JCAnnotation>nil());
}
public void generateMethods(JavacNode typeNode, JavacNode source, java.util.List<Included<JavacNode, EqualsAndHashCode.Include>> members,
Boolean callSuper, boolean whineIfExists, FieldAccess fieldAccess, List<JCAnnotation> onParam) {
Boolean callSuper, boolean whineIfExists, boolean cacheHashCode, FieldAccess fieldAccess, List<JCAnnotation> onParam) {
boolean notAClass = true;
if (typeNode.get() instanceof JCClassDecl) {
@@ -195,21 +198,46 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas
JCMethodDecl canEqualMethod = createCanEqual(typeNode, source.get(), onParam);
injectMethod(typeNode, canEqualMethod);
}
JCMethodDecl hashCodeMethod = createHashCode(typeNode, members, callSuper, fieldAccess, source.get());
if (cacheHashCode){
if (!isFinal) {
String msg = "Not caching the result of hashCode: Annotated type is not final.";
source.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);
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());
}
}
JCMethodDecl hashCodeMethod = createHashCode(typeNode, members, callSuper, cacheHashCode, fieldAccess, source.get());
injectMethod(typeNode, hashCodeMethod);
}
public JCMethodDecl createHashCode(JavacNode typeNode, java.util.List<Included<JavacNode, EqualsAndHashCode.Include>> members, boolean callSuper, FieldAccess fieldAccess, JCTree source) {
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();
JCAnnotation overrideAnnotation = maker.Annotation(genJavaLangTypeRef(typeNode, "Override"), List.<JCExpression>nil());
List<JCAnnotation> annsOnMethod = List.of(overrideAnnotation);
CheckerFrameworkVersion checkerFramework = getCheckerFrameworkVersion(typeNode);
// TODO: maybe not add this annotation if cacheHashCode is true because we *do* modify a field
if (checkerFramework.generateSideEffectFree()) annsOnMethod = annsOnMethod.prepend(maker.Annotation(genTypeRef(typeNode, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.<JCExpression>nil()));
JCModifiers mods = maker.Modifiers(Flags.PUBLIC, annsOnMethod);
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);
@@ -305,6 +333,10 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas
statements.append(createResultCalculation(typeNode, maker.Parens(maker.Conditional(thisEqualsNull, maker.Literal(HandlerUtil.primeForNull()), hcCall))));
}
}
if (cacheHashCode) {
statements.append(maker.Exec(maker.Assign(maker.Ident(cacheHashCodeName), maker.Ident(resultName))));
}
/* return result; */ {
statements.append(maker.Return(maker.Ident(resultName)));