88import java .util .Set ;
99import java .util .concurrent .CompletableFuture ;
1010import java .util .concurrent .CompletionStage ;
11- import java .util .function .BiFunction ;
1211import java .util .function .Function ;
1312import java .util .function .Predicate ;
1413import java .util .function .Supplier ;
@@ -103,9 +102,21 @@ public <K, V> Uni<V> getAsync(K key, Function<K, Uni<V>> valueLoader) {
103102 .completionStage (new Supplier <CompletionStage <V >>() {
104103 @ Override
105104 public CompletionStage <V > get () {
106- // When stats are enabled we need to use Map.compute() in order to call statsCounter.recordHits(1)
107- // Map.compute() is more costly compared to Map.computeIfAbsent() because the remapping function is always called and the returned value is replaced
108- return recordStats ? computeWithStats (key , valueLoader ) : computeWithoutStats (key , valueLoader );
105+ // When stats are enabled we need to call statsCounter.recordHits(1)/statsCounter.recordMisses(1) accordingly
106+ StatsRecorder recorder = recordStats ? new OperationalStatsRecorder () : NoopStatsRecorder .INSTANCE ;
107+ @ SuppressWarnings ("unchecked" )
108+ CompletionStage <V > result = (CompletionStage <V >) cache .asMap ().computeIfAbsent (key ,
109+ new Function <Object , CompletableFuture <Object >>() {
110+ @ Override
111+ public CompletableFuture <Object > apply (Object key ) {
112+ recorder .onValueAbsent ();
113+ return valueLoader .apply ((K ) key )
114+ .map (TO_CACHE_VALUE )
115+ .subscribeAsCompletionStage ();
116+ }
117+ });
118+ recorder .doRecord (key );
119+ return result ;
109120 }
110121 }).map (fromCacheValue ());
111122 }
@@ -126,6 +137,7 @@ public <V> CompletableFuture<V> getIfPresent(Object key) {
126137 // cast, but still throw the CacheException in case it fails
127138 return unwrapCacheValueOrThrowable (existingCacheValue )
128139 .thenApply (new Function <>() {
140+ @ SuppressWarnings ("unchecked" )
129141 @ Override
130142 public V apply (Object value ) {
131143 try {
@@ -227,6 +239,7 @@ public Set<Object> keySet() {
227239 return Collections .unmodifiableSet (new HashSet <>(cache .asMap ().keySet ()));
228240 }
229241
242+ @ SuppressWarnings ("unchecked" )
230243 @ Override
231244 public <V > void put (Object key , CompletableFuture <V > valueFuture ) {
232245 cache .put (key , (CompletableFuture <Object >) valueFuture );
@@ -288,41 +301,53 @@ private <T> T cast(Object value) {
288301 }
289302
290303 @ SuppressWarnings ("unchecked" )
291- private <K , V > CompletionStage <V > computeWithStats (K key , Function <K , Uni <V >> valueLoader ) {
292- return (CompletionStage <V >) cache .asMap ().compute (key ,
293- new BiFunction <Object , CompletableFuture <Object >, CompletableFuture <Object >>() {
294- @ Override
295- public CompletableFuture <Object > apply (Object key , CompletableFuture <Object > value ) {
296- if (value == null ) {
297- statsCounter .recordMisses (1 );
298- return valueLoader .apply ((K ) key )
299- .map (TO_CACHE_VALUE )
300- .subscribeAsCompletionStage ();
301- } else {
302- LOGGER .tracef ("Key [%s] found in cache [%s]" , key , cacheInfo .name );
303- statsCounter .recordHits (1 );
304- return value ;
305- }
306- }
307- });
304+ private <V > Function <V , V > fromCacheValue () {
305+ return (Function <V , V >) FROM_CACHE_VALUE ;
308306 }
309307
310- @ SuppressWarnings ("unchecked" )
311- private <K , V > CompletionStage <V > computeWithoutStats (K key , Function <K , Uni <V >> valueLoader ) {
312- return (CompletionStage <V >) cache .asMap ().computeIfAbsent (key ,
313- new Function <Object , CompletableFuture <Object >>() {
314- @ Override
315- public CompletableFuture <Object > apply (Object key ) {
316- return valueLoader .apply ((K ) key )
317- .map (TO_CACHE_VALUE )
318- .subscribeAsCompletionStage ();
319- }
320- });
308+ private interface StatsRecorder {
309+
310+ void onValueAbsent ();
311+
312+ <K > void doRecord (K key );
313+
321314 }
322315
323- @ SuppressWarnings ("unchecked" )
324- private <V > Function <V , V > fromCacheValue () {
325- return (Function <V , V >) FROM_CACHE_VALUE ;
316+ private static class NoopStatsRecorder implements StatsRecorder {
317+
318+ static final NoopStatsRecorder INSTANCE = new NoopStatsRecorder ();
319+
320+ @ Override
321+ public void onValueAbsent () {
322+ // no-op
323+ }
324+
325+ @ Override
326+ public <K > void doRecord (K key ) {
327+ // no-op
328+ }
329+
330+ }
331+
332+ private class OperationalStatsRecorder implements StatsRecorder {
333+
334+ private boolean valueAbsent ;
335+
336+ @ Override
337+ public void onValueAbsent () {
338+ valueAbsent = true ;
339+ }
340+
341+ @ Override
342+ public <K > void doRecord (K key ) {
343+ if (valueAbsent ) {
344+ statsCounter .recordMisses (1 );
345+ } else {
346+ LOGGER .tracef ("Key [%s] found in cache [%s]" , key , cacheInfo .name );
347+ statsCounter .recordHits (1 );
348+ }
349+ }
350+
326351 }
327352
328353 private static final Function <Object , Object > FROM_CACHE_VALUE = new Function <Object , Object >() {
0 commit comments