• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ben-manes / caffeine / #5612

11 Jul 2026 03:47PM UTC coverage: 71.292% (-28.7%) from 100.0%
#5612

push

github

ben-manes
Preserve timestamps on a raced same-instance refresh

The automatic refreshIfNeeded path checked for an equal-value reload
before the ABA/ownership check and returned without preserving the
timestamps, so a reload completing after a concurrent put(k,
sameInstance) did a full update — shifting the entry's expiration
forward by the reload's in-flight duration and stomping the write.
Mirror the explicit-refresh fix: check ownership first. An owned
reload installs the value (refreshing metadata even for a same
instance), while a raced reload preserves the write's timestamps and
only notifies REPLACED when the value differs.

2957 of 4134 branches covered (71.53%)

3 of 3 new or added lines in 1 file covered. (100.0%)

2408 existing lines in 53 files now uncovered.

5980 of 8388 relevant lines covered (71.29%)

0.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.46
/caffeine/src/main/java/com/github/benmanes/caffeine/cache/UnboundedLocalCache.java
1
/*
2
 * Copyright 2014 Ben Manes. All Rights Reserved.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package com.github.benmanes.caffeine.cache;
17

18
import static com.github.benmanes.caffeine.cache.Caffeine.calculateHashMapCapacity;
19
import static com.github.benmanes.caffeine.cache.LocalLoadingCache.newBulkMappingFunction;
20
import static com.github.benmanes.caffeine.cache.LocalLoadingCache.newMappingFunction;
21
import static java.lang.invoke.ConstantBootstraps.fieldVarHandle;
22
import static java.util.Objects.requireNonNull;
23

24
import java.io.InvalidObjectException;
25
import java.io.ObjectInputStream;
26
import java.io.Serializable;
27
import java.lang.System.Logger;
28
import java.lang.System.Logger.Level;
29
import java.lang.invoke.MethodHandles;
30
import java.lang.invoke.VarHandle;
31
import java.util.AbstractCollection;
32
import java.util.AbstractSet;
33
import java.util.Collection;
34
import java.util.Collections;
35
import java.util.HashMap;
36
import java.util.Iterator;
37
import java.util.LinkedHashMap;
38
import java.util.List;
39
import java.util.Map;
40
import java.util.Objects;
41
import java.util.Optional;
42
import java.util.Set;
43
import java.util.Spliterator;
44
import java.util.concurrent.CompletableFuture;
45
import java.util.concurrent.ConcurrentHashMap;
46
import java.util.concurrent.ConcurrentMap;
47
import java.util.concurrent.Executor;
48
import java.util.function.BiConsumer;
49
import java.util.function.BiFunction;
50
import java.util.function.Consumer;
51
import java.util.function.Function;
52
import java.util.function.Predicate;
53

54
import org.jspecify.annotations.Nullable;
55

56
import com.github.benmanes.caffeine.cache.stats.StatsCounter;
57
import com.google.errorprone.annotations.CanIgnoreReturnValue;
58
import com.google.errorprone.annotations.Var;
59

60
/**
61
 * An in-memory cache that has no capabilities for bounding the map. This implementation provides
62
 * a lightweight wrapper on top of {@link ConcurrentHashMap}.
63
 *
64
 * @author ben.manes@gmail.com (Ben Manes)
65
 */
66
@SuppressWarnings("serial")
67
final class UnboundedLocalCache<K, V> implements LocalCache<K, V> {
68
  static final Logger logger = System.getLogger(UnboundedLocalCache.class.getName());
1✔
69
  static final VarHandle REFRESHES = fieldVarHandle(MethodHandles.lookup(),
1✔
70
      "refreshes", VarHandle.class, UnboundedLocalCache.class, ConcurrentMap.class);
71

72
  final @Nullable RemovalListener<K, V> removalListener;
73
  final ConcurrentHashMap<K, V> data;
74
  final StatsCounter statsCounter;
75
  final boolean isRecordingStats;
76
  final Executor executor;
77
  final boolean isAsync;
78

79
  @Nullable Set<K> keySet;
80
  @Nullable Collection<V> values;
81
  @Nullable Set<Entry<K, V>> entrySet;
82
  volatile @Nullable ConcurrentMap<Object, CompletableFuture<?>> refreshes;
83

84
  UnboundedLocalCache(Caffeine<? super K, ? super V> builder, boolean isAsync) {
1✔
85
    this.data = new ConcurrentHashMap<>(builder.getInitialCapacity());
1✔
86
    this.statsCounter = builder.getStatsCounterSupplier().get();
1✔
87
    this.removalListener = builder.getRemovalListener(isAsync);
1✔
88
    this.isRecordingStats = builder.isRecordingStats();
1✔
89
    this.executor = builder.getExecutor();
1✔
90
    this.isAsync = isAsync;
1✔
91
  }
1✔
92

93
  @Override
94
  public boolean isAsync() {
95
    return isAsync;
1✔
96
  }
97

98
  @Override
99
  public @Nullable RemovalListener<K, V> removalListener() {
100
    return removalListener;
1✔
101
  }
102

103
  @Override
104
  public @Nullable Expiry<K, V> expiry() {
105
    return null;
1✔
106
  }
107

108
  @Override
109
  public boolean collectKeys() {
110
    return false;
1✔
111
  }
112

113
  @Override
114
  @CanIgnoreReturnValue
115
  public Object referenceKey(K key) {
116
    return key;
1✔
117
  }
118

119
  @Override
120
  public boolean isPendingEviction(K key) {
121
    return false;
1✔
122
  }
123

124
  /* --------------- Cache --------------- */
125

126
  @Override
127
  @SuppressWarnings("SuspiciousMethodCalls")
128
  public @Nullable V getIfPresent(Object key, boolean recordStats) {
129
    V value = data.get(key);
1✔
130

131
    if (recordStats) {
1✔
132
      if (value == null) {
1!
UNCOV
133
        statsCounter.recordMisses(1);
×
134
      } else {
135
        statsCounter.recordHits(1);
1✔
136
      }
137
    }
138
    return value;
1✔
139
  }
140

141
  @Override
142
  @SuppressWarnings("SuspiciousMethodCalls")
143
  public @Nullable V getIfPresentQuietly(Object key) {
144
    return data.get(key);
1✔
145
  }
146

147
  @Override
148
  public long estimatedSize() {
149
    return data.mappingCount();
1✔
150
  }
151

152
  @Override
153
  public Map<K, V> getAllPresent(Iterable<? extends K> keys) {
154
    var result = new LinkedHashMap<K, @Nullable V>(calculateHashMapCapacity(keys));
1✔
155
    for (K key : keys) {
1✔
156
      result.put(key, null);
1✔
157
    }
1✔
158

159
    int uniqueKeys = result.size();
1✔
160
    for (var iter = result.entrySet().iterator(); iter.hasNext();) {
1✔
161
      Map.Entry<K, @Nullable V> entry = iter.next();
1✔
162
      V value = data.get(entry.getKey());
1✔
163
      if (value == null) {
1✔
164
        iter.remove();
1✔
165
      } else {
166
        entry.setValue(value);
1✔
167
      }
168
    }
1✔
169
    statsCounter.recordHits(result.size());
1✔
170
    statsCounter.recordMisses(uniqueKeys - result.size());
1✔
171

172
    @SuppressWarnings("NullableProblems")
173
    Map<K, V> unmodifiable = Collections.unmodifiableMap(result);
1✔
174
    return unmodifiable;
1✔
175
  }
176

177
  @Override
178
  public void cleanUp() {}
1✔
179

180
  @Override
181
  public StatsCounter statsCounter() {
182
    return statsCounter;
1✔
183
  }
184

185
  @Override
186
  public void notifyRemoval(@Nullable K key, @Nullable V value, RemovalCause cause) {
187
    if (removalListener == null) {
1✔
188
      return;
1✔
189
    }
190
    Runnable task = () -> {
1✔
191
      try {
192
        removalListener.onRemoval(key, value, cause);
1✔
193
      } catch (Throwable t) {
1✔
194
        logger.log(Level.WARNING, "Exception thrown by removal listener", t);
1✔
195
      }
1✔
196
    };
1✔
197
    try {
198
      executor.execute(task);
1✔
199
    } catch (Throwable t) {
1✔
200
      logger.log(Level.ERROR, "Exception thrown when submitting removal listener", t);
1✔
201
      task.run();
1✔
202
    }
1✔
203
  }
1✔
204

205
  @Override
206
  public boolean isRecordingStats() {
207
    return isRecordingStats;
1✔
208
  }
209

210
  @Override
211
  public Executor executor() {
212
    return executor;
1✔
213
  }
214

215
  @Override
216
  public ConcurrentMap<Object, CompletableFuture<?>> refreshes() {
217
    @Var var pending = refreshes;
1✔
218
    if (pending == null) {
1✔
219
      pending = new ConcurrentHashMap<>();
1✔
220
      if (!REFRESHES.compareAndSet(this, null, pending)) {
1!
UNCOV
221
        pending = requireNonNull(refreshes);
×
222
      }
223
    }
224
    return pending;
1✔
225
  }
226

227
  /** Invalidate the in-flight refresh. */
228
  void discardRefresh(Object keyReference) {
229
    var pending = refreshes;
1✔
230
    if (pending != null) {
1✔
231
      pending.remove(keyReference);
1✔
232
    }
233
  }
1✔
234

235
  @Override
236
  public Ticker statsTicker() {
237
    return isRecordingStats ? Ticker.systemTicker() : Ticker.disabledTicker();
1✔
238
  }
239

240
  /* --------------- JDK8+ Map extensions --------------- */
241

242
  @Override
243
  public void forEach(BiConsumer<? super K, ? super V> action) {
244
    data.forEach(action);
1✔
245
  }
1✔
246

247
  @Override
248
  @SuppressWarnings("ResultOfMethodCallIgnored")
249
  public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
250
    requireNonNull(function);
1✔
251

252
    BiFunction<K, @Nullable V, @Nullable V> remappingFunction = (key, oldValue) ->
1✔
253
        (oldValue == null) ? null : requireNonNull(function.apply(key, oldValue));
1!
254
    for (K key : data.keySet()) {
1✔
255
      remap(key, remappingFunction, /* hints= */ null);
1✔
256
    }
1✔
257
  }
1✔
258

259
  @Override
260
  public @Nullable V computeIfAbsent(K key,
261
      Function<? super K, ? extends @Nullable V> mappingFunction,
262
      boolean recordStats, boolean recordLoad) {
263
    requireNonNull(mappingFunction);
1✔
264

265
    // An optimistic fast path to avoid unnecessary locking
266
    @Var V value = data.get(key);
1✔
267
    if (value != null) {
1✔
268
      if (recordStats) {
1!
269
        statsCounter.recordHits(1);
1✔
270
      }
271
      return value;
1✔
272
    }
273

274
    boolean[] missed = new boolean[1];
1✔
275
    value = data.computeIfAbsent(key, k -> {
1✔
276
      missed[0] = true;
1✔
277
      V computed = recordStats
1✔
278
          ? statsAware(mappingFunction, recordLoad).apply(k)
1✔
279
          : mappingFunction.apply(k);
1✔
280
      discardRefresh(k);
1✔
281
      return computed;
1✔
282
    });
283
    if (!missed[0] && recordStats) {
1!
284
      statsCounter.recordHits(1);
1✔
285
    }
286
    return value;
1✔
287
  }
288

289
  @Override
290
  public @Nullable V computeIfPresent(K key,
291
      BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
292
    requireNonNull(remappingFunction);
1✔
293

294
    // An optimistic fast path to avoid unnecessary locking
295
    if (!data.containsKey(key)) {
1✔
296
      return null;
1✔
297
    }
298

299
    // ensures that the removal notification is processed after the removal has completed
300
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
301
    @Nullable V[] oldValue = (V[]) new Object[1];
1✔
302
    boolean[] replaced = new boolean[1];
1✔
303
    V nv = data.computeIfPresent(key, (K k, V value) -> {
1✔
304
      BiFunction<? super K, ? super V, ? extends @Nullable V> function = statsAware(
1✔
305
          remappingFunction, /* recordLoad= */ true, /* recordLoadFailure= */ true);
306
      V newValue = function.apply(k, value);
1✔
307

308
      replaced[0] = (newValue != null);
1!
309
      if (newValue != value) {
1!
310
        oldValue[0] = value;
1✔
311
      }
312

313
      discardRefresh(k);
1✔
314
      return newValue;
1✔
315
    });
316
    if (replaced[0]) {
1!
317
      notifyOnReplace(key, oldValue[0], nv);
1✔
UNCOV
318
    } else if (oldValue[0] != null) {
×
UNCOV
319
      notifyRemoval(key, oldValue[0], RemovalCause.EXPLICIT);
×
320
    }
321
    return nv;
1✔
322
  }
323

324
  @Override
325
  public @Nullable V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction,
326
      @Nullable Expiry<? super K, ? super V> expiry, boolean recordLoad,
327
      boolean recordLoadFailure, @Nullable RemapHints hints) {
328
    requireNonNull(remappingFunction);
1✔
329
    return remap(key, statsAware(remappingFunction, recordLoad, recordLoadFailure), hints);
1✔
330
  }
331

332
  @Override
333
  public @Nullable V merge(K key, V value,
334
      BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
335
    requireNonNull(remappingFunction);
1✔
336
    requireNonNull(value);
1✔
337

338
    return remap(key, (K k, @Nullable V oldValue) ->
1✔
339
      (oldValue == null) ? value : statsAware(remappingFunction).apply(oldValue, value),
1!
340
      /* hints= */ null);
341
  }
342

343
  /**
344
   * A {@link Map#compute(Object, BiFunction)} that does not directly record any cache statistics.
345
   *
346
   * @param key key with which the specified value is to be associated
347
   * @param remappingFunction the function to compute a value
348
   * @param hints flags from a query-style caller, or {@code null} if not present
349
   * @return the new value associated with the specified key, or null if none
350
   */
351
  @Nullable V remap(K key,
352
      BiFunction<? super K, ? super @Nullable V, ? extends @Nullable V> remappingFunction,
353
      @Nullable RemapHints hints) {
354
    // ensures that the removal notification is processed after the removal has completed
355
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
356
    @Nullable V[] oldValue = (@Nullable V[]) new Object[1];
1✔
357
    boolean[] replaced = new boolean[1];
1✔
358
    V nv = data.compute(key, (K k, V value) -> {
1✔
359
      V newValue = remappingFunction.apply(k, value);
1✔
360
      if ((value == null) && (newValue == null)) {
1✔
361
        discardRefresh(k);
1✔
362
        return null;
1✔
363
      }
364

365
      replaced[0] = (newValue != null);
1✔
366
      if (newValue != value) {
1✔
367
        oldValue[0] = value;
1✔
368
      }
369

370
      // A query-style caller may flag a same-instance return as a no-op that must leave any
371
      // in-flight refresh intact whereas a real mutation discards a racing refresh so that a
372
      // stale reload cannot overwrite the new mapping.
373
      boolean preserveRefresh = (hints != null) && hints.preserveRefresh && (newValue == value);
1!
374
      if (!preserveRefresh) {
1✔
375
        discardRefresh(k);
1✔
376
      }
377
      return newValue;
1✔
378
    });
379
    if (replaced[0]) {
1✔
380
      notifyOnReplace(key, oldValue[0], nv);
1✔
381
    } else if (oldValue[0] != null) {
1✔
382
      notifyRemoval(key, oldValue[0], RemovalCause.EXPLICIT);
1✔
383
    }
384
    return nv;
1✔
385
  }
386

387
  /* --------------- Concurrent Map --------------- */
388

389
  @Override
390
  public boolean isEmpty() {
391
    return data.isEmpty();
1✔
392
  }
393

394
  @Override
395
  public int size() {
396
    return data.size();
1✔
397
  }
398

399
  @Override
400
  @SuppressWarnings("ResultOfMethodCallIgnored")
401
  public void clear() {
402
    var keys = (removalListener == null) ? data.keySet() : List.copyOf(data.keySet());
1✔
403
    for (K key : keys) {
1✔
404
      remove(key);
1✔
405
    }
1✔
406
  }
1✔
407

408
  @Override
409
  public boolean containsKey(Object key) {
410
    return data.containsKey(key);
1✔
411
  }
412

413
  @Override
414
  public boolean containsValue(Object value) {
415
    return data.containsValue(value);
1✔
416
  }
417

418
  @Override
419
  public @Nullable V get(Object key) {
420
    return getIfPresent(key, /* recordStats= */ false);
1✔
421
  }
422

423
  @Override
424
  public @Nullable V put(K key, V value) {
425
    requireNonNull(value);
1✔
426

427
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
428
    @Nullable V[] oldValue = (@Nullable V[]) new Object[1];
1✔
429
    data.compute(key, (K k, V v) -> {
1✔
430
      discardRefresh(k);
1✔
431
      oldValue[0] = v;
1✔
432
      return value;
1✔
433
    });
434
    if (oldValue[0] != null) {
1✔
435
      notifyOnReplace(key, oldValue[0], value);
1✔
436
    }
437
    return oldValue[0];
1✔
438
  }
439

440
  @Override
441
  public @Nullable V putIfAbsent(K key, V value) {
442
    requireNonNull(value);
1✔
443

444
    // An optimistic fast path to avoid unnecessary locking
445
    var v = data.get(key);
1✔
446
    if (v != null) {
1✔
447
      return v;
1✔
448
    }
449

450
    var added = new boolean[1];
1✔
451
    var val = data.computeIfAbsent(key, k -> {
1✔
452
      discardRefresh(k);
1✔
453
      added[0] = true;
1✔
454
      return value;
1✔
455
    });
456
    return added[0] ? null : val;
1✔
457
  }
458

459
  @Override
460
  @SuppressWarnings("ResultOfMethodCallIgnored")
461
  public void putAll(Map<? extends K, ? extends V> map) {
462
    map.forEach(this::put);
1✔
463
  }
1✔
464

465
  @Override
466
  public @Nullable V remove(Object key) {
467
    @SuppressWarnings("unchecked")
468
    var castKey = (K) key;
1✔
469
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
470
    @Nullable V[] oldValue = (V[]) new Object[1];
1✔
471
    data.computeIfPresent(castKey, (k, v) -> {
1✔
472
      discardRefresh(k);
1✔
473
      oldValue[0] = v;
1✔
474
      return null;
1✔
475
    });
476

477
    if (oldValue[0] != null) {
1✔
478
      notifyRemoval(castKey, oldValue[0], RemovalCause.EXPLICIT);
1✔
479
    }
480

481
    return oldValue[0];
1✔
482
  }
483

484
  @Override
485
  public boolean remove(Object key, @Nullable Object value) {
486
    if (value == null) {
1✔
487
      requireNonNull(key);
1✔
488
      return false;
1✔
489
    }
490

491
    @SuppressWarnings("unchecked")
492
    var castKey = (K) key;
1✔
493
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
494
    @Nullable V[] oldValue = (V[]) new Object[1];
1✔
495

496
    data.computeIfPresent(castKey, (k, v) -> {
1✔
497
      if (Objects.equals(value, v)) {
1✔
498
        discardRefresh(k);
1✔
499
        oldValue[0] = v;
1✔
500
        return null;
1✔
501
      }
502
      return v;
1✔
503
    });
504

505
    if (oldValue[0] != null) {
1✔
506
      notifyRemoval(castKey, oldValue[0], RemovalCause.EXPLICIT);
1✔
507
      return true;
1✔
508
    }
509
    return false;
1✔
510
  }
511

512
  @Override
513
  public @Nullable V replace(K key, V value) {
514
    requireNonNull(value);
1✔
515

516
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
517
    @Nullable V[] oldValue = (@Nullable V[]) new Object[1];
1✔
518
    data.computeIfPresent(key, (k, v) -> {
1✔
519
      discardRefresh(k);
1✔
520
      oldValue[0] = v;
1✔
521
      return value;
1✔
522
    });
523

524
    if ((oldValue[0] != null) && (oldValue[0] != value)) {
1✔
525
      notifyOnReplace(key, oldValue[0], value);
1✔
526
    }
527
    return oldValue[0];
1✔
528
  }
529

530
  @Override
531
  public boolean replace(K key, V oldValue, V newValue) {
532
    return replace(key, oldValue, newValue, /* shouldDiscardRefresh= */ true);
1✔
533
  }
534

535
  @Override
536
  public boolean replace(K key, V oldValue, V newValue, boolean shouldDiscardRefresh) {
537
    requireNonNull(oldValue);
1✔
538
    requireNonNull(newValue);
1✔
539

540
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
541
    @Nullable V[] prev = (V[]) new Object[1];
1✔
542
    data.computeIfPresent(key, (k, v) -> {
1✔
543
      if (Objects.equals(oldValue, v)) {
1✔
544
        if (shouldDiscardRefresh) {
1✔
545
          discardRefresh(k);
1✔
546
        }
547
        prev[0] = v;
1✔
548
        return newValue;
1✔
549
      }
550
      return v;
1✔
551
    });
552

553
    boolean replaced = (prev[0] != null);
1✔
554
    if (replaced && (prev[0] != newValue)) {
1✔
555
      notifyOnReplace(key, prev[0], newValue);
1✔
556
    }
557
    return replaced;
1✔
558
  }
559

560
  @Override
561
  @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
562
  public boolean equals(@Nullable Object o) {
563
    if (o == this) {
1!
UNCOV
564
      return true;
×
565
    }
566
    try {
567
      return data.equals(o);
1✔
568
    } catch (ClassCastException | NullPointerException ignored) {
1✔
569
      return false;
1✔
570
    }
571
  }
572

573
  @Override
574
  public int hashCode() {
575
    return data.hashCode();
1✔
576
  }
577

578
  @Override
579
  public String toString() {
580
    var result = new StringBuilder(50).append('{');
1✔
581
    data.forEach((key, value) -> {
1✔
582
      if (result.length() != 1) {
1✔
583
        result.append(", ");
1✔
584
      }
585
      result.append((key == this) ? "(this Map)" : key)
1✔
586
          .append('=')
1✔
587
          .append((value == this) ? "(this Map)" : value);
1✔
588
    });
1✔
589
    return result.append('}').toString();
1✔
590
  }
591

592
  @Override
593
  public Set<K> keySet() {
594
    Set<K> ks = keySet;
1✔
595
    return (ks == null) ? (keySet = new KeySetView<>(this)) : ks;
1✔
596
  }
597

598
  @Override
599
  public Collection<V> values() {
600
    Collection<V> vs = values;
1✔
601
    return (vs == null) ? (values = new ValuesView<>(this)) : vs;
1✔
602
  }
603

604
  @Override
605
  public Set<Entry<K, V>> entrySet() {
606
    Set<Entry<K, V>> es = entrySet;
1✔
607
    return (es == null) ? (entrySet = new EntrySetView<>(this)) : es;
1✔
608
  }
609

610
  /** An adapter to safely externalize the keys. */
611
  static final class KeySetView<K> extends AbstractSet<K> {
612
    final UnboundedLocalCache<K, ?> cache;
613

614
    KeySetView(UnboundedLocalCache<K, ?> cache) {
1✔
615
      this.cache = requireNonNull(cache);
1✔
616
    }
1✔
617

618
    @Override
619
    public boolean isEmpty() {
620
      return cache.isEmpty();
1✔
621
    }
622

623
    @Override
624
    public int size() {
625
      return cache.size();
1✔
626
    }
627

628
    @Override
629
    public void clear() {
630
      cache.clear();
1✔
631
    }
1✔
632

633
    @Override
634
    @SuppressWarnings("SuspiciousMethodCalls")
635
    public boolean contains(Object o) {
636
      return cache.containsKey(o);
1✔
637
    }
638

639
    @Override
640
    public boolean containsAll(Collection<?> collection) {
641
      requireNonNull(collection);
1✔
642
      if (collection != this) {
1!
643
        for (Object o : collection) {
1✔
644
          if ((o == null) || !contains(o)) {
1✔
645
            return false;
1✔
646
          }
647
        }
1✔
648
      }
649
      return true;
1✔
650
    }
651

652
    @Override
653
    public boolean removeAll(Collection<?> collection) {
654
      requireNonNull(collection);
1✔
655
      @Var boolean modified = false;
1✔
656
      if ((collection instanceof Set<?>) && (collection.size() > size())) {
1✔
657
        for (K key : this) {
1✔
658
          if (collection.contains(key)) {
1✔
659
            modified |= remove(key);
1✔
660
          }
661
        }
1✔
662
      } else {
663
        for (var o : collection) {
1✔
664
          modified |= (o != null) && remove(o);
1✔
665
        }
1✔
666
      }
667
      return modified;
1✔
668
    }
669

670
    @Override
671
    public boolean remove(Object o) {
672
      return (cache.remove(o) != null);
1✔
673
    }
674

675
    @Override
676
    public boolean removeIf(Predicate<? super K> filter) {
677
      requireNonNull(filter);
1✔
678
      @Var boolean modified = false;
1✔
679
      for (K key : this) {
1✔
680
        if (filter.test(key) && remove(key)) {
1!
UNCOV
681
          modified = true;
×
682
        }
683
      }
1✔
684
      return modified;
1✔
685
    }
686

687
    @Override
688
    public boolean retainAll(Collection<?> collection) {
689
      requireNonNull(collection);
1✔
690
      @Var boolean modified = false;
1✔
691
      for (K key : this) {
1✔
692
        if (!collection.contains(key) && remove(key)) {
1✔
693
          modified = true;
1✔
694
        }
695
      }
1✔
696
      return modified;
1✔
697
    }
698

699
    @Override
700
    public void forEach(Consumer<? super K> action) {
701
      cache.data.keySet().forEach(action);
1✔
702
    }
1✔
703

704
    @Override
705
    public Iterator<K> iterator() {
706
      return new KeyIterator<>(cache);
1✔
707
    }
708

709
    @Override
710
    public Spliterator<K> spliterator() {
711
      return new KeySpliterator<>(cache);
1✔
712
    }
713

714
    @Override
715
    public Object[] toArray() {
716
      return cache.data.keySet().toArray();
1✔
717
    }
718

719
    @Override
720
    public <T> T[] toArray(T[] array) {
721
      return cache.data.keySet().toArray(array);
1✔
722
    }
723
  }
724

725
  /** An adapter to safely externalize the key iterator. */
726
  static final class KeyIterator<K> implements Iterator<K> {
727
    final UnboundedLocalCache<K, ?> cache;
728
    final Iterator<K> iterator;
729
    @Nullable K current;
730

731
    KeyIterator(UnboundedLocalCache<K, ?> cache) {
1✔
732
      this.iterator = cache.data.keySet().iterator();
1✔
733
      this.cache = cache;
1✔
734
    }
1✔
735

736
    @Override
737
    public boolean hasNext() {
738
      return iterator.hasNext();
1✔
739
    }
740

741
    @Override
742
    public K next() {
743
      current = iterator.next();
1✔
744
      return current;
1✔
745
    }
746

747
    @Override
748
    @SuppressWarnings("ResultOfMethodCallIgnored")
749
    public void remove() {
750
      if (current == null) {
1✔
751
        throw new IllegalStateException();
1✔
752
      }
753
      cache.remove(current);
1✔
754
      current = null;
1✔
755
    }
1✔
756
  }
757

758
  /** An adapter to safely externalize the key spliterator. */
759
  static final class KeySpliterator<K, V> implements Spliterator<K> {
760
    final Spliterator<K> spliterator;
761

762
    KeySpliterator(UnboundedLocalCache<K, V> cache) {
763
      this(cache.data.keySet().spliterator());
1✔
764
    }
1✔
765

766
    KeySpliterator(Spliterator<K> spliterator) {
1✔
767
      this.spliterator = requireNonNull(spliterator);
1✔
768
    }
1✔
769

770
    @Override
771
    public void forEachRemaining(Consumer<? super K> action) {
772
      requireNonNull(action);
1✔
773
      spliterator.forEachRemaining(action);
1✔
774
    }
1✔
775

776
    @Override
777
    public boolean tryAdvance(Consumer<? super K> action) {
UNCOV
778
      requireNonNull(action);
×
UNCOV
779
      return spliterator.tryAdvance(action);
×
780
    }
781

782
    @Override
783
    public @Nullable KeySpliterator<K, V> trySplit() {
784
      Spliterator<K> split = spliterator.trySplit();
1✔
785
      return (split == null) ? null : new KeySpliterator<>(split);
1✔
786
    }
787

788
    @Override
789
    public long estimateSize() {
790
      return spliterator.estimateSize();
1✔
791
    }
792

793
    @Override
794
    public int characteristics() {
795
      return DISTINCT | CONCURRENT | NONNULL;
1✔
796
    }
797
  }
798

799
  /** An adapter to safely externalize the values. */
800
  static final class ValuesView<K, V> extends AbstractCollection<V> {
801
    final UnboundedLocalCache<K, V> cache;
802

803
    ValuesView(UnboundedLocalCache<K, V> cache) {
1✔
804
      this.cache = requireNonNull(cache);
1✔
805
    }
1✔
806

807
    @Override
808
    public boolean isEmpty() {
809
      return cache.isEmpty();
1✔
810
    }
811

812
    @Override
813
    public int size() {
814
      return cache.size();
1✔
815
    }
816

817
    @Override
818
    public void clear() {
819
      cache.clear();
1✔
820
    }
1✔
821

822
    @Override
823
    @SuppressWarnings("SuspiciousMethodCalls")
824
    public boolean contains(Object o) {
825
      return cache.containsValue(o);
1✔
826
    }
827

828
    @Override
829
    public boolean containsAll(Collection<?> collection) {
830
      requireNonNull(collection);
1✔
831
      if (collection != this) {
1!
832
        for (Object o : collection) {
1✔
833
          if ((o == null) || !contains(o)) {
1✔
834
            return false;
1✔
835
          }
836
        }
1✔
837
      }
838
      return true;
1✔
839
    }
840

841
    @Override
842
    public boolean removeAll(Collection<?> collection) {
843
      requireNonNull(collection);
1✔
844
      @Var boolean modified = false;
1✔
845
      for (var entry : cache.data.entrySet()) {
1✔
846
        if (collection.contains(entry.getValue())
1✔
847
            && cache.remove(entry.getKey(), entry.getValue())) {
1!
848
          modified = true;
1✔
849
        }
850
      }
1✔
851
      return modified;
1✔
852
    }
853

854
    @Override
855
    public boolean remove(@Nullable Object o) {
856
      if (o == null) {
1✔
857
        return false;
1✔
858
      }
859
      for (var entry : cache.data.entrySet()) {
1✔
860
        if (o.equals(entry.getValue()) && cache.remove(entry.getKey(), entry.getValue())) {
1!
861
          return true;
1✔
862
        }
863
      }
1✔
864
      return false;
1✔
865
    }
866

867
    @Override
868
    public boolean removeIf(Predicate<? super V> filter) {
869
      requireNonNull(filter);
1✔
870
      @Var boolean removed = false;
1✔
871
      for (var entry : cache.data.entrySet()) {
1✔
872
        if (filter.test(entry.getValue())) {
1✔
873
          removed |= cache.remove(entry.getKey(), entry.getValue());
1✔
874
        }
875
      }
1✔
876
      return removed;
1✔
877
    }
878

879
    @Override
880
    public boolean retainAll(Collection<?> collection) {
881
      requireNonNull(collection);
1✔
882
      @Var boolean modified = false;
1✔
883
      for (var entry : cache.data.entrySet()) {
1✔
884
        if (!collection.contains(entry.getValue())
1✔
885
            && cache.remove(entry.getKey(), entry.getValue())) {
1!
886
          modified = true;
1✔
887
        }
888
      }
1✔
889
      return modified;
1✔
890
    }
891

892
    @Override
893
    public void forEach(Consumer<? super V> action) {
894
      cache.data.values().forEach(action);
1✔
895
    }
1✔
896

897
    @Override
898
    public Iterator<V> iterator() {
899
      return new ValueIterator<>(cache);
1✔
900
    }
901

902
    @Override
903
    public Spliterator<V> spliterator() {
904
      return new ValueSpliterator<>(cache);
1✔
905
    }
906

907
    @Override
908
    public Object[] toArray() {
909
      return cache.data.values().toArray();
1✔
910
    }
911

912
    @Override
913
    public <T> T[] toArray(T[] array) {
914
      return cache.data.values().toArray(array);
1✔
915
    }
916
  }
917

918
  /** An adapter to safely externalize the value iterator. */
919
  static final class ValueIterator<K, V> implements Iterator<V> {
920
    final UnboundedLocalCache<K, V> cache;
921
    final Iterator<Entry<K, V>> iterator;
922
    @Nullable Entry<K, V> entry;
923

924
    ValueIterator(UnboundedLocalCache<K, V> cache) {
1✔
925
      this.iterator = cache.data.entrySet().iterator();
1✔
926
      this.cache = cache;
1✔
927
    }
1✔
928

929
    @Override
930
    public boolean hasNext() {
931
      return iterator.hasNext();
1✔
932
    }
933

934
    @Override
935
    public V next() {
936
      entry = iterator.next();
1✔
937
      return entry.getValue();
1✔
938
    }
939

940
    @Override
941
    @SuppressWarnings("ResultOfMethodCallIgnored")
942
    public void remove() {
943
      if (entry == null) {
1!
UNCOV
944
        throw new IllegalStateException();
×
945
      }
946
      cache.remove(entry.getKey());
1✔
947
      entry = null;
1✔
948
    }
1✔
949
  }
950

951
  /** An adapter to safely externalize the value spliterator. */
952
  static final class ValueSpliterator<K, V> implements Spliterator<V> {
953
    final Spliterator<V> spliterator;
954

955
    ValueSpliterator(UnboundedLocalCache<K, V> cache) {
956
      this(cache.data.values().spliterator());
1✔
957
    }
1✔
958

959
    ValueSpliterator(Spliterator<V> spliterator) {
1✔
960
      this.spliterator = requireNonNull(spliterator);
1✔
961
    }
1✔
962

963
    @Override
964
    public void forEachRemaining(Consumer<? super V> action) {
965
      requireNonNull(action);
1✔
966
      spliterator.forEachRemaining(action);
1✔
967
    }
1✔
968

969
    @Override
970
    public boolean tryAdvance(Consumer<? super V> action) {
971
      requireNonNull(action);
1✔
972
      return spliterator.tryAdvance(action);
1✔
973
    }
974

975
    @Override
976
    public @Nullable ValueSpliterator<K, V> trySplit() {
977
      Spliterator<V> split = spliterator.trySplit();
1✔
978
      return (split == null) ? null : new ValueSpliterator<>(split);
1✔
979
    }
980

981
    @Override
982
    public long estimateSize() {
983
      return spliterator.estimateSize();
1✔
984
    }
985

986
    @Override
987
    public int characteristics() {
988
      return CONCURRENT | NONNULL;
1✔
989
    }
990
  }
991

992
  /** An adapter to safely externalize the entries. */
993
  static final class EntrySetView<K, V> extends AbstractSet<Entry<K, V>> {
994
    final UnboundedLocalCache<K, V> cache;
995

996
    EntrySetView(UnboundedLocalCache<K, V> cache) {
1✔
997
      this.cache = requireNonNull(cache);
1✔
998
    }
1✔
999

1000
    @Override
1001
    public boolean isEmpty() {
1002
      return cache.isEmpty();
1✔
1003
    }
1004

1005
    @Override
1006
    public int size() {
1007
      return cache.size();
1✔
1008
    }
1009

1010
    @Override
1011
    public void clear() {
1012
      cache.clear();
1✔
1013
    }
1✔
1014

1015
    @Override
1016
    @SuppressWarnings("SuspiciousMethodCalls")
1017
    public boolean contains(Object o) {
1018
      if (!(o instanceof Entry<?, ?>)) {
1✔
1019
        return false;
1✔
1020
      }
1021
      var entry = (Entry<?, ?>) o;
1✔
1022
      var key = entry.getKey();
1✔
1023
      var value = entry.getValue();
1✔
1024
      if ((key == null) || (value == null)) {
1✔
1025
        return false;
1✔
1026
      }
1027
      V cachedValue = cache.get(key);
1✔
1028
      return value.equals(cachedValue);
1✔
1029
    }
1030

1031
    @Override
1032
    public boolean removeAll(Collection<?> collection) {
1033
      requireNonNull(collection);
1✔
1034
      @Var boolean modified = false;
1✔
1035
      if ((collection instanceof Set<?>) && (collection.size() > size())) {
1✔
1036
        for (var entry : this) {
1✔
1037
          if (collection.contains(entry)) {
1!
1038
            modified |= remove(entry);
1✔
1039
          }
1040
        }
1✔
1041
      } else {
1042
        for (var o : collection) {
1✔
1043
          modified |= remove(o);
1✔
1044
        }
1✔
1045
      }
1046
      return modified;
1✔
1047
    }
1048

1049
    @Override
1050
    @SuppressWarnings("SuspiciousMethodCalls")
1051
    public boolean remove(Object o) {
1052
      if (!(o instanceof Entry<?, ?>)) {
1✔
1053
        return false;
1✔
1054
      }
1055
      var entry = (Entry<?, ?>) o;
1✔
1056
      var key = entry.getKey();
1✔
1057
      return (key != null) && cache.remove(key, entry.getValue());
1✔
1058
    }
1059

1060
    @Override
1061
    public boolean removeIf(Predicate<? super Entry<K, V>> filter) {
1062
      requireNonNull(filter);
1✔
1063
      @Var boolean removed = false;
1✔
1064
      for (var entry : cache.data.entrySet()) {
1✔
1065
        var key = entry.getKey();
1✔
1066
        var value = entry.getValue();
1✔
1067
        if (filter.test(Map.entry(key, value))) {
1✔
1068
          removed |= cache.remove(key, value);
1✔
1069
        }
1070
      }
1✔
1071
      return removed;
1✔
1072
    }
1073

1074
    @Override
1075
    public boolean retainAll(Collection<?> collection) {
1076
      requireNonNull(collection);
1✔
1077
      @Var boolean modified = false;
1✔
1078
      for (var entry : this) {
1✔
1079
        if (!collection.contains(entry) && remove(entry)) {
1!
1080
          modified = true;
1✔
1081
        }
1082
      }
1✔
1083
      return modified;
1✔
1084
    }
1085

1086
    @Override
1087
    public Iterator<Entry<K, V>> iterator() {
1088
      return new EntryIterator<>(cache);
1✔
1089
    }
1090

1091
    @Override
1092
    public Spliterator<Entry<K, V>> spliterator() {
1093
      return new EntrySpliterator<>(cache);
1✔
1094
    }
1095
  }
1096

1097
  /** An adapter to safely externalize the entry iterator. */
1098
  static final class EntryIterator<K, V> implements Iterator<Entry<K, V>> {
1099
    final UnboundedLocalCache<K, V> cache;
1100
    final Iterator<Entry<K, V>> iterator;
1101
    @Nullable Entry<K, V> entry;
1102

1103
    EntryIterator(UnboundedLocalCache<K, V> cache) {
1✔
1104
      this.iterator = cache.data.entrySet().iterator();
1✔
1105
      this.cache = cache;
1✔
1106
    }
1✔
1107

1108
    @Override
1109
    public boolean hasNext() {
1110
      return iterator.hasNext();
1✔
1111
    }
1112

1113
    @Override
1114
    public Entry<K, V> next() {
1115
      entry = iterator.next();
1✔
1116
      return new WriteThroughEntry<>(cache, entry.getKey(), entry.getValue());
1✔
1117
    }
1118

1119
    @Override
1120
    @SuppressWarnings("ResultOfMethodCallIgnored")
1121
    public void remove() {
1122
      if (entry == null) {
1✔
1123
        throw new IllegalStateException();
1✔
1124
      }
1125
      cache.remove(entry.getKey());
1✔
1126
      entry = null;
1✔
1127
    }
1✔
1128
  }
1129

1130
  /** An adapter to safely externalize the entry spliterator. */
1131
  static final class EntrySpliterator<K, V> implements Spliterator<Entry<K, V>> {
1132
    final Spliterator<Entry<K, V>> spliterator;
1133
    final UnboundedLocalCache<K, V> cache;
1134

1135
    EntrySpliterator(UnboundedLocalCache<K, V> cache) {
1136
      this(cache, cache.data.entrySet().spliterator());
1✔
1137
    }
1✔
1138

1139
    EntrySpliterator(UnboundedLocalCache<K, V> cache, Spliterator<Entry<K, V>> spliterator) {
1✔
1140
      this.spliterator = requireNonNull(spliterator);
1✔
1141
      this.cache = requireNonNull(cache);
1✔
1142
    }
1✔
1143

1144
    @Override
1145
    public void forEachRemaining(Consumer<? super Entry<K, V>> action) {
1146
      requireNonNull(action);
1✔
1147
      spliterator.forEachRemaining(entry -> {
1✔
1148
        var e = new WriteThroughEntry<>(cache, entry.getKey(), entry.getValue());
1✔
1149
        action.accept(e);
1✔
1150
      });
1✔
1151
    }
1✔
1152

1153
    @Override
1154
    public boolean tryAdvance(Consumer<? super Entry<K, V>> action) {
1155
      requireNonNull(action);
1✔
1156
      return spliterator.tryAdvance(entry -> {
1✔
1157
        var e = new WriteThroughEntry<>(cache, entry.getKey(), entry.getValue());
1✔
1158
        action.accept(e);
1✔
1159
      });
1✔
1160
    }
1161

1162
    @Override
1163
    public @Nullable EntrySpliterator<K, V> trySplit() {
1164
      Spliterator<Entry<K, V>> split = spliterator.trySplit();
1✔
1165
      return (split == null) ? null : new EntrySpliterator<>(cache, split);
1✔
1166
    }
1167

1168
    @Override
1169
    public long estimateSize() {
1170
      return spliterator.estimateSize();
1✔
1171
    }
1172

1173
    @Override
1174
    public int characteristics() {
1175
      return DISTINCT | CONCURRENT | NONNULL;
1✔
1176
    }
1177
  }
1178

1179
  /* --------------- Manual Cache --------------- */
1180

1181
  static class UnboundedLocalManualCache<K, V> implements LocalManualCache<K, V>, Serializable {
1182
    private static final long serialVersionUID = 1;
1183

1184
    final UnboundedLocalCache<K, V> cache;
1185
    @Nullable Policy<K, V> policy;
1186

1187
    UnboundedLocalManualCache(Caffeine<K, V> builder) {
1✔
1188
      cache = new UnboundedLocalCache<>(builder, /* isAsync= */ false);
1✔
1189
    }
1✔
1190

1191
    @Override
1192
    public final UnboundedLocalCache<K, V> cache() {
1193
      return cache;
1✔
1194
    }
1195

1196
    @Override
1197
    public final Policy<K, V> policy() {
1198
      if (policy == null) {
1✔
1199
        Function<@Nullable V, @Nullable V> identity = v -> v;
1✔
1200
        policy = new UnboundedPolicy<>(cache, identity);
1✔
1201
      }
1202
      return policy;
1✔
1203
    }
1204

1205
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
UNCOV
1206
      throw new InvalidObjectException("Proxy required");
×
1207
    }
1208

1209
    Object writeReplace() {
1210
      var proxy = new SerializationProxy<K, V>();
1✔
1211
      proxy.isRecordingStats = cache.isRecordingStats;
1✔
1212
      proxy.removalListener = cache.removalListener;
1✔
1213
      return proxy;
1✔
1214
    }
1215
  }
1216

1217
  /** An eviction policy that supports no bounding. */
1218
  static final class UnboundedPolicy<K, V> implements Policy<K, V> {
1219
    final Function<@Nullable V, @Nullable V> transformer;
1220
    final UnboundedLocalCache<K, V> cache;
1221

1222
    UnboundedPolicy(UnboundedLocalCache<K, V> cache,
1223
        Function<@Nullable V, @Nullable V> transformer) {
1✔
1224
      this.transformer = transformer;
1✔
1225
      this.cache = cache;
1✔
1226
    }
1✔
1227
    @Override public boolean isRecordingStats() {
1228
      return cache.isRecordingStats;
1✔
1229
    }
1230
    @Override public @Nullable V getIfPresentQuietly(K key) {
UNCOV
1231
      return transformer.apply(cache.data.get(key));
×
1232
    }
1233
    @Override public @Nullable CacheEntry<K, V> getEntryIfPresentQuietly(K key) {
1234
      V value = transformer.apply(cache.data.get(key));
1✔
1235
      return (value == null) ? null : SnapshotEntry.forEntry(key, value);
1!
1236
    }
1237
    @SuppressWarnings("Java9CollectionFactory")
1238
    @Override public Map<K, CompletableFuture<V>> refreshes() {
1239
      var refreshes = cache.refreshes;
1✔
1240
      if ((refreshes == null) || refreshes.isEmpty()) {
1✔
1241
        @SuppressWarnings({"ImmutableMapOf", "RedundantUnmodifiable"})
1242
        Map<K, CompletableFuture<V>> emptyMap = Collections.unmodifiableMap(Collections.emptyMap());
1✔
1243
        return emptyMap;
1✔
1244
      }
1245
      @SuppressWarnings("unchecked")
1246
      var castedRefreshes = (Map<K, CompletableFuture<V>>) (Object) refreshes;
1✔
1247
      return Collections.unmodifiableMap(new HashMap<>(castedRefreshes));
1✔
1248
    }
1249
    @Override public Optional<Eviction<K, V>> eviction() {
1250
      return Optional.empty();
1✔
1251
    }
1252
    @Override public Optional<FixedExpiration<K, V>> expireAfterAccess() {
1253
      return Optional.empty();
1✔
1254
    }
1255
    @Override public Optional<FixedExpiration<K, V>> expireAfterWrite() {
1256
      return Optional.empty();
1✔
1257
    }
1258
    @Override public Optional<VarExpiration<K, V>> expireVariably() {
1259
      return Optional.empty();
1✔
1260
    }
1261
    @Override public Optional<FixedRefresh<K, V>> refreshAfterWrite() {
1262
      return Optional.empty();
1✔
1263
    }
1264
  }
1265

1266
  /* --------------- Loading Cache --------------- */
1267

1268
  static final class UnboundedLocalLoadingCache<K, V> extends UnboundedLocalManualCache<K, V>
1269
      implements LocalLoadingCache<K, V> {
1270
    private static final long serialVersionUID = 1;
1271

1272
    final Function<K, @Nullable V> mappingFunction;
1273
    final CacheLoader<? super K, V> cacheLoader;
1274
    final @Nullable Function<Set<? extends K>, Map<K, V>> bulkMappingFunction;
1275

1276
    UnboundedLocalLoadingCache(Caffeine<K, V> builder, CacheLoader<? super K, V> cacheLoader) {
1277
      super(builder);
1✔
1278
      this.cacheLoader = cacheLoader;
1✔
1279
      this.mappingFunction = newMappingFunction(cacheLoader);
1✔
1280
      this.bulkMappingFunction = newBulkMappingFunction(cacheLoader);
1✔
1281
    }
1✔
1282

1283
    @Override
1284
    public AsyncCacheLoader<? super K, V> cacheLoader() {
1285
      return cacheLoader;
1✔
1286
    }
1287

1288
    @Override
1289
    public Function<K, @Nullable V> mappingFunction() {
1290
      return mappingFunction;
1✔
1291
    }
1292

1293
    @Override
1294
    public @Nullable Function<Set<? extends K>, Map<K, V>>  bulkMappingFunction() {
1295
      return bulkMappingFunction;
1✔
1296
    }
1297

1298
    @Override
1299
    Object writeReplace() {
1300
      @SuppressWarnings("unchecked")
1301
      var proxy = (SerializationProxy<K, V>) super.writeReplace();
1✔
1302
      proxy.cacheLoader = cacheLoader;
1✔
1303
      return proxy;
1✔
1304
    }
1305

1306
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
UNCOV
1307
      throw new InvalidObjectException("Proxy required");
×
1308
    }
1309
  }
1310

1311
  /* --------------- Async Cache --------------- */
1312

1313
  static final class UnboundedLocalAsyncCache<K, V> implements LocalAsyncCache<K, V>, Serializable {
1314
    private static final long serialVersionUID = 1;
1315

1316
    final UnboundedLocalCache<K, CompletableFuture<V>> cache;
1317

1318
    @Nullable ConcurrentMap<K, CompletableFuture<V>> mapView;
1319
    @Nullable CacheView<K, V> cacheView;
1320
    @Nullable Policy<K, V> policy;
1321

1322
    @SuppressWarnings("unchecked")
1323
    UnboundedLocalAsyncCache(Caffeine<K, V> builder) {
1✔
1324
      cache = new UnboundedLocalCache<>(
1✔
1325
          (Caffeine<K, CompletableFuture<V>>) builder, /* isAsync= */ true);
1326
    }
1✔
1327

1328
    @Override
1329
    public UnboundedLocalCache<K, CompletableFuture<V>> cache() {
1330
      return cache;
1✔
1331
    }
1332

1333
    @Override
1334
    public ConcurrentMap<K, CompletableFuture<V>> asMap() {
1335
      return (mapView == null) ? (mapView = new AsyncAsMapView<>(this)) : mapView;
1✔
1336
    }
1337

1338
    @Override
1339
    public Cache<K, V> synchronous() {
1340
      return (cacheView == null) ? (cacheView = new CacheView<>(this)) : cacheView;
1✔
1341
    }
1342

1343
    @Override
1344
    public Policy<K, V> policy() {
1345
      @SuppressWarnings("unchecked")
1346
      var castCache = (UnboundedLocalCache<K, V>) cache;
1✔
1347
      Function<CompletableFuture<V>, @Nullable V> transformer = Async::getIfReady;
1✔
1348
      @SuppressWarnings("unchecked")
1349
      var castTransformer = (Function<@Nullable V, @Nullable V>) transformer;
1✔
1350
      return (policy == null)
1✔
1351
          ? (policy = new UnboundedPolicy<>(castCache, castTransformer))
1✔
1352
          : policy;
1✔
1353
    }
1354

1355
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
UNCOV
1356
      throw new InvalidObjectException("Proxy required");
×
1357
    }
1358

1359
    Object writeReplace() {
1360
      var proxy = new SerializationProxy<K, V>();
1✔
1361
      proxy.isRecordingStats = cache.isRecordingStats;
1✔
1362
      proxy.removalListener = cache.removalListener;
1✔
1363
      proxy.async = true;
1✔
1364
      return proxy;
1✔
1365
    }
1366
  }
1367

1368
  /* --------------- Async Loading Cache --------------- */
1369

1370
  static final class UnboundedLocalAsyncLoadingCache<K, V>
1371
      extends LocalAsyncLoadingCache<K, V> implements Serializable {
1372
    private static final long serialVersionUID = 1;
1373

1374
    final UnboundedLocalCache<K, CompletableFuture<V>> cache;
1375

1376
    @Nullable ConcurrentMap<K, CompletableFuture<V>> mapView;
1377
    @Nullable Policy<K, V> policy;
1378

1379
    @SuppressWarnings("unchecked")
1380
    UnboundedLocalAsyncLoadingCache(Caffeine<K, V> builder, AsyncCacheLoader<? super K, V> loader) {
1381
      super(loader);
1✔
1382
      cache = new UnboundedLocalCache<>(
1✔
1383
          (Caffeine<K, CompletableFuture<V>>) builder, /* isAsync= */ true);
1384
    }
1✔
1385

1386
    @Override
1387
    public LocalCache<K, CompletableFuture<V>> cache() {
1388
      return cache;
1✔
1389
    }
1390

1391
    @Override
1392
    public ConcurrentMap<K, CompletableFuture<V>> asMap() {
1393
      return (mapView == null) ? (mapView = new AsyncAsMapView<>(this)) : mapView;
1✔
1394
    }
1395

1396
    @Override
1397
    public Policy<K, V> policy() {
1398
      @SuppressWarnings("unchecked")
1399
      var castCache = (UnboundedLocalCache<K, V>) cache;
1✔
1400
      Function<CompletableFuture<V>, @Nullable V> transformer = Async::getIfReady;
1✔
1401
      @SuppressWarnings("unchecked")
1402
      var castTransformer = (Function<@Nullable V, @Nullable V>) transformer;
1✔
1403
      return (policy == null)
1✔
1404
          ? (policy = new UnboundedPolicy<>(castCache, castTransformer))
1✔
1405
          : policy;
1✔
1406
    }
1407

1408
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
UNCOV
1409
      throw new InvalidObjectException("Proxy required");
×
1410
    }
1411

1412
    Object writeReplace() {
1413
      var proxy = new SerializationProxy<K, V>();
1✔
1414
      proxy.isRecordingStats = cache.isRecordingStats();
1✔
1415
      proxy.removalListener = cache.removalListener;
1✔
1416
      proxy.cacheLoader = cacheLoader;
1✔
1417
      proxy.async = true;
1✔
1418
      return proxy;
1✔
1419
    }
1420
  }
1421
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc