36
36
import lombok .AccessLevel ;
37
37
import lombok .ConfigurationKeys ;
38
38
import lombok .EqualsAndHashCode ;
39
+ import lombok .EqualsAndHashCode .CacheStrategy ;
39
40
import lombok .core .AST .Kind ;
40
41
import lombok .core .handlers .HandlerUtil ;
41
42
import lombok .core .handlers .InclusionExclusionUtils ;
59
60
import org .eclipse .jdt .internal .compiler .ast .EqualExpression ;
60
61
import org .eclipse .jdt .internal .compiler .ast .Expression ;
61
62
import org .eclipse .jdt .internal .compiler .ast .FalseLiteral ;
63
+ import org .eclipse .jdt .internal .compiler .ast .FieldDeclaration ;
64
+ import org .eclipse .jdt .internal .compiler .ast .FieldReference ;
62
65
import org .eclipse .jdt .internal .compiler .ast .IfStatement ;
63
66
import org .eclipse .jdt .internal .compiler .ast .InstanceOfExpression ;
64
67
import org .eclipse .jdt .internal .compiler .ast .IntLiteral ;
94
97
@ ProviderFor (EclipseAnnotationHandler .class )
95
98
public class HandleEqualsAndHashCode extends EclipseAnnotationHandler <EqualsAndHashCode > {
96
99
100
+ private static final String HASH_CODE_CACHE_NAME = "$hashCodeCache" ;
101
+
102
+ private final char [] HASH_CODE_CACHE_NAME_ARR = HASH_CODE_CACHE_NAME .toCharArray ();
97
103
private final char [] PRIME = "PRIME" .toCharArray ();
98
104
private final char [] RESULT = "result" .toCharArray ();
99
105
@@ -116,7 +122,9 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH
116
122
boolean doNotUseGetters = annotation .isExplicit ("doNotUseGetters" ) || doNotUseGettersConfiguration == null ? ann .doNotUseGetters () : doNotUseGettersConfiguration ;
117
123
FieldAccess fieldAccess = doNotUseGetters ? FieldAccess .PREFER_FIELD : FieldAccess .GETTER ;
118
124
119
- generateMethods (annotationNode .up (), annotationNode , members , callSuper , true , fieldAccess , onParam );
125
+ boolean cacheHashCode = ann .cacheStrategy () == CacheStrategy .LAZY ;
126
+
127
+ generateMethods (annotationNode .up (), annotationNode , members , callSuper , true , cacheHashCode , fieldAccess , onParam );
120
128
}
121
129
122
130
public void generateEqualsAndHashCodeForType (EclipseNode typeNode , EclipseNode errorNode ) {
@@ -130,11 +138,11 @@ public void generateEqualsAndHashCodeForType(EclipseNode typeNode, EclipseNode e
130
138
Boolean doNotUseGettersConfiguration = typeNode .getAst ().readConfiguration (ConfigurationKeys .EQUALS_AND_HASH_CODE_DO_NOT_USE_GETTERS );
131
139
FieldAccess access = doNotUseGettersConfiguration == null || !doNotUseGettersConfiguration ? FieldAccess .GETTER : FieldAccess .PREFER_FIELD ;
132
140
133
- generateMethods (typeNode , errorNode , members , null , false , access , new ArrayList <Annotation >());
141
+ generateMethods (typeNode , errorNode , members , null , false , false , access , new ArrayList <Annotation >());
134
142
}
135
143
136
144
public void generateMethods (EclipseNode typeNode , EclipseNode errorNode , List <Included <EclipseNode , EqualsAndHashCode .Include >> members ,
137
- Boolean callSuper , boolean whineIfExists , FieldAccess fieldAccess , List <Annotation > onParam ) {
145
+ Boolean callSuper , boolean whineIfExists , boolean cacheHashCode , FieldAccess fieldAccess , List <Annotation > onParam ) {
138
146
139
147
TypeDeclaration typeDecl = null ;
140
148
@@ -222,12 +230,33 @@ public void generateMethods(EclipseNode typeNode, EclipseNode errorNode, List<In
222
230
injectMethod (typeNode , canEqualMethod );
223
231
}
224
232
225
- MethodDeclaration hashCodeMethod = createHashCode (typeNode , members , callSuper , errorNode .get (), fieldAccess );
233
+ if (cacheHashCode ){
234
+ if (fieldExists (HASH_CODE_CACHE_NAME , typeNode ) != MemberExistsResult .NOT_EXISTS ) {
235
+ String msg = String .format ("Not caching the result of hashCode: A field named %s already exists." , HASH_CODE_CACHE_NAME );
236
+ errorNode .addWarning (msg );
237
+ cacheHashCode = false ;
238
+ } else {
239
+ createHashCodeCacheField (typeNode , errorNode .get ());
240
+ }
241
+ }
242
+
243
+ MethodDeclaration hashCodeMethod = createHashCode (typeNode , members , callSuper , cacheHashCode , errorNode .get (), fieldAccess );
226
244
hashCodeMethod .traverse (new SetGeneratedByVisitor (errorNode .get ()), ((TypeDeclaration )typeNode .get ()).scope );
227
245
injectMethod (typeNode , hashCodeMethod );
228
246
}
247
+
248
+ private void createHashCodeCacheField (EclipseNode typeNode , ASTNode source ) {
249
+ FieldDeclaration hashCodeCacheDecl = new FieldDeclaration (HASH_CODE_CACHE_NAME_ARR , 0 , 0 );
250
+ hashCodeCacheDecl .modifiers = ClassFileConstants .AccPrivate | ClassFileConstants .AccTransient ;
251
+ hashCodeCacheDecl .bits |= Eclipse .ECLIPSE_DO_NOT_TOUCH_FLAG ;
252
+ hashCodeCacheDecl .type = TypeReference .baseTypeReference (TypeIds .T_int , 0 );
253
+ hashCodeCacheDecl .declarationSourceEnd = -1 ;
254
+ injectFieldAndMarkGenerated (typeNode , hashCodeCacheDecl );
255
+ setGeneratedBy (hashCodeCacheDecl , source );
256
+ setGeneratedBy (hashCodeCacheDecl .type , source );
257
+ }
229
258
230
- public MethodDeclaration createHashCode (EclipseNode type , Collection <Included <EclipseNode , EqualsAndHashCode .Include >> members , boolean callSuper , ASTNode source , FieldAccess fieldAccess ) {
259
+ public MethodDeclaration createHashCode (EclipseNode type , Collection <Included <EclipseNode , EqualsAndHashCode .Include >> members , boolean callSuper , boolean cacheHashCode , ASTNode source , FieldAccess fieldAccess ) {
231
260
int pS = source .sourceStart , pE = source .sourceEnd ;
232
261
long p = (long ) pS << 32 | pE ;
233
262
@@ -238,7 +267,10 @@ public MethodDeclaration createHashCode(EclipseNode type, Collection<Included<Ec
238
267
method .returnType = TypeReference .baseTypeReference (TypeIds .T_int , 0 );
239
268
setGeneratedBy (method .returnType , source );
240
269
Annotation overrideAnnotation = makeMarkerAnnotation (TypeConstants .JAVA_LANG_OVERRIDE , source );
241
- if (getCheckerFrameworkVersion (type ).generateSideEffectFree ()) {
270
+ CheckerFrameworkVersion checkerFramework = getCheckerFrameworkVersion (type );
271
+ if (cacheHashCode && checkerFramework .generatePure ()) {
272
+ method .annotations = new Annotation [] { overrideAnnotation , generateNamedAnnotation (source , CheckerFrameworkVersion .NAME__PURE ) };
273
+ } else if (checkerFramework .generateSideEffectFree ()) {
242
274
method .annotations = new Annotation [] { overrideAnnotation , generateNamedAnnotation (source , CheckerFrameworkVersion .NAME__SIDE_EFFECT_FREE ) };
243
275
} else {
244
276
method .annotations = new Annotation [] { overrideAnnotation };
@@ -262,6 +294,22 @@ public MethodDeclaration createHashCode(EclipseNode type, Collection<Included<Ec
262
294
}
263
295
}
264
296
297
+ /* if (this.$hashCodeCache != 0) return this.$hashCodeCache; */ {
298
+ if (cacheHashCode ) {
299
+ FieldReference hashCodeCacheRef = new FieldReference (HASH_CODE_CACHE_NAME_ARR , p );
300
+ hashCodeCacheRef .receiver = new ThisReference (pS , pE );
301
+ setGeneratedBy (hashCodeCacheRef , source );
302
+ setGeneratedBy (hashCodeCacheRef .receiver , source );
303
+ EqualExpression cacheNotZero = new EqualExpression (hashCodeCacheRef , makeIntLiteral ("0" .toCharArray (), source ), OperatorIds .NOT_EQUAL );
304
+ setGeneratedBy (cacheNotZero , source );
305
+ ReturnStatement returnCache = new ReturnStatement (hashCodeCacheRef , pS , pE );
306
+ setGeneratedBy (returnCache , source );
307
+ IfStatement ifStatement = new IfStatement (cacheNotZero , returnCache , pS , pE );
308
+ setGeneratedBy (ifStatement , source );
309
+ statements .add (ifStatement );
310
+ }
311
+ }
312
+
265
313
/* final int PRIME = X; */ {
266
314
/* Without members, PRIME isn't used, as that would trigger a 'local variable not used' warning. */
267
315
if (!isEmpty ) {
@@ -296,7 +344,7 @@ public MethodDeclaration createHashCode(EclipseNode type, Collection<Included<Ec
296
344
resultDecl .initialization = init ;
297
345
resultDecl .type = TypeReference .baseTypeReference (TypeIds .T_int , 0 );
298
346
resultDecl .type .sourceStart = pS ; resultDecl .type .sourceEnd = pE ;
299
- if (isEmpty ) resultDecl .modifiers |= Modifier .FINAL ;
347
+ if (isEmpty && ! cacheHashCode ) resultDecl .modifiers |= Modifier .FINAL ;
300
348
setGeneratedBy (resultDecl .type , source );
301
349
statements .add (resultDecl );
302
350
}
@@ -391,6 +439,49 @@ public MethodDeclaration createHashCode(EclipseNode type, Collection<Included<Ec
391
439
}
392
440
}
393
441
442
+ /*
443
+ * if (result == 0) result = Integer.MIN_VALUE;
444
+ * this.$hashCodeCache = result;
445
+ *
446
+ */ {
447
+ if (cacheHashCode ) {
448
+ SingleNameReference resultRef = new SingleNameReference (RESULT , p );
449
+ setGeneratedBy (resultRef , source );
450
+
451
+ EqualExpression resultIsZero = new EqualExpression (resultRef , makeIntLiteral ("0" .toCharArray (), source ), OperatorIds .EQUAL_EQUAL );
452
+ setGeneratedBy (resultIsZero , source );
453
+
454
+ resultRef = new SingleNameReference (RESULT , p );
455
+ setGeneratedBy (resultRef , source );
456
+
457
+ FieldReference integerMinValue = new FieldReference ("MIN_VALUE" .toCharArray (), p );
458
+ integerMinValue .receiver = generateQualifiedNameRef (source , TypeConstants .JAVA_LANG_INTEGER );
459
+ setGeneratedBy (integerMinValue , source );
460
+
461
+ Assignment newResult = new Assignment (resultRef , integerMinValue , pE );
462
+ newResult .sourceStart = pS ; newResult .statementEnd = newResult .sourceEnd = pE ;
463
+ setGeneratedBy (newResult , source );
464
+
465
+ IfStatement ifStatement = new IfStatement (resultIsZero , newResult , pS , pE );
466
+ setGeneratedBy (ifStatement , source );
467
+ statements .add (ifStatement );
468
+
469
+
470
+ FieldReference hashCodeCacheRef = new FieldReference (HASH_CODE_CACHE_NAME_ARR , p );
471
+ hashCodeCacheRef .receiver = new ThisReference (pS , pE );
472
+ setGeneratedBy (hashCodeCacheRef , source );
473
+ setGeneratedBy (hashCodeCacheRef .receiver , source );
474
+
475
+ resultRef = new SingleNameReference (RESULT , p );
476
+ setGeneratedBy (resultRef , source );
477
+
478
+ Assignment cacheResult = new Assignment (hashCodeCacheRef , resultRef , pE );
479
+ cacheResult .sourceStart = pS ; cacheResult .statementEnd = cacheResult .sourceEnd = pE ;
480
+ setGeneratedBy (cacheResult , source );
481
+ statements .add (cacheResult );
482
+ }
483
+ }
484
+
394
485
/* return result; */ {
395
486
SingleNameReference resultRef = new SingleNameReference (RESULT , p );
396
487
setGeneratedBy (resultRef , source );
0 commit comments