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

ben-manes / caffeine / #5732

18 Aug 2026 04:57AM UTC coverage: 99.867% (+0.001%) from 99.866%
#5732

push

github

ben-manes
fix issues found by the audit sweep

- Scope a bulk load's null-result marker to its operation
- Copy the indexers when building an indexed cache
- Pin that a bulk load matches requested keys by equality

4578 of 4594 branches covered (99.65%)

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

1 existing line in 1 file now uncovered.

8984 of 8996 relevant lines covered (99.87%)

1.0 hits per line

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

99.82
/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✔
133
        statsCounter.recordMisses(1);
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
    var removalListener = removalListener();
1✔
188
    if (removalListener == null) {
1✔
189
      return;
1✔
190
    }
191
    Runnable task = () -> {
1✔
192
      try {
193
        removalListener.onRemoval(key, value, cause);
1✔
194
      } catch (Throwable t) {
1✔
195
        logger.log(Level.WARNING, "Exception thrown by removal listener", t);
1✔
196
      }
1✔
197
    };
1✔
198
    try {
199
      executor.execute(task);
1✔
200
    } catch (Throwable t) {
1✔
201
      logger.log(Level.ERROR, "Exception thrown when submitting removal listener", t);
1✔
202
      task.run();
1✔
203
    }
1✔
204
  }
1✔
205

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

314
        discardRefresh(k);
1✔
315
        return newValue;
1✔
316
      } catch (Throwable t) {
1✔
317
        discardRefresh(k);
1✔
318
        throw t;
1✔
319
      }
320
    });
321
    if (replaced[0]) {
1✔
322
      notifyOnReplace(key, oldValue[0], nv);
1✔
323
    } else if (oldValue[0] != null) {
1✔
324
      notifyRemoval(key, oldValue[0], RemovalCause.EXPLICIT);
1✔
325
    }
326
    return nv;
1✔
327
  }
328

329
  @Override
330
  public @Nullable V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction,
331
      @Nullable Expiry<? super K, ? super V> expiry, boolean recordLoad,
332
      boolean recordLoadFailure, @Nullable RemapHints hints) {
333
    requireNonNull(remappingFunction);
1✔
334
    return remap(key, statsAware(remappingFunction, recordLoad, recordLoadFailure),
1✔
335
        hints, /* computeIfAbsent= */ true);
336
  }
337

338
  @Override
339
  public @Nullable V merge(K key, V value,
340
      BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
341
    requireNonNull(remappingFunction);
1✔
342
    requireNonNull(value);
1✔
343

344
    return remap(key, (K k, @Nullable V oldValue) ->
1✔
345
      (oldValue == null) ? value : statsAware(remappingFunction).apply(oldValue, value),
1✔
346
      /* hints= */ null, /* computeIfAbsent= */ true);
347
  }
348

349
  /**
350
   * A {@link Map#compute(Object, BiFunction)} that does not directly record any cache statistics.
351
   *
352
   * @param key key with which the specified value is to be associated
353
   * @param remappingFunction the function to compute a value
354
   * @param hints flags from a query-style caller, or {@code null} if not present
355
   * @param computeIfAbsent whether the caller may create an entry when the key is absent
356
   * @return the new value associated with the specified key, or null if none
357
   */
358
  @Nullable V remap(K key,
359
      BiFunction<? super K, ? super @Nullable V, ? extends @Nullable V> remappingFunction,
360
      @Nullable RemapHints hints, boolean computeIfAbsent) {
361
    // ensures that the removal notification is processed after the removal has completed
362
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
363
    @Nullable V[] oldValue = (@Nullable V[]) new Object[1];
1✔
364
    boolean[] replaced = new boolean[1];
1✔
365
    V nv = data.compute(key, (K k, V value) -> {
1✔
366
      if ((value == null) && !computeIfAbsent) {
1✔
367
        return null;
1✔
368
      }
369
      try {
370
        V newValue = remappingFunction.apply(k, value);
1✔
371
        if ((value == null) && (newValue == null)) {
1✔
372
          // A stale refresh whose entry is absent leaves a successor's registration intact
373
          if ((hints == null) || !hints.preserveRefresh) {
1✔
374
            discardRefresh(k);
1✔
375
          }
376
          return null;
1✔
377
        }
378

379
        replaced[0] = (newValue != null);
1✔
380
        if (newValue != value) {
1✔
381
          oldValue[0] = value;
1✔
382
        }
383

384
        // A query-style caller may flag a same-instance return as a no-op that must leave any
385
        // in-flight refresh intact whereas a real mutation discards a racing refresh so that a
386
        // stale reload cannot overwrite the new mapping.
387
        boolean preserveRefresh = (hints != null) && hints.preserveRefresh && (newValue == value);
1✔
388
        if (!preserveRefresh) {
1✔
389
          discardRefresh(k);
1✔
390
        }
391
        return newValue;
1✔
392
      } catch (Throwable t) {
1✔
393
        if (value != null) {
1✔
394
          discardRefresh(k);
1✔
395
        }
396
        throw t;
1✔
397
      }
398
    });
399
    if (replaced[0]) {
1✔
400
      notifyOnReplace(key, oldValue[0], nv);
1✔
401
    } else if (oldValue[0] != null) {
1✔
402
      notifyRemoval(key, oldValue[0], RemovalCause.EXPLICIT);
1✔
403
    }
404
    return nv;
1✔
405
  }
406

407
  /* --------------- Concurrent Map --------------- */
408

409
  @Override
410
  public boolean isEmpty() {
411
    return data.isEmpty();
1✔
412
  }
413

414
  @Override
415
  public int size() {
416
    return data.size();
1✔
417
  }
418

419
  @Override
420
  @SuppressWarnings("ResultOfMethodCallIgnored")
421
  public void clear() {
422
    var pending = refreshes;
1✔
423
    if (pending != null) {
1✔
424
      pending.clear();
1✔
425
    }
426

427
    var keys = (removalListener == null) ? data.keySet() : List.copyOf(data.keySet());
1✔
428
    for (K key : keys) {
1✔
429
      remove(key);
1✔
430
    }
1✔
431
  }
1✔
432

433
  @Override
434
  public boolean containsKey(Object key) {
435
    return data.containsKey(key);
1✔
436
  }
437

438
  @Override
439
  public boolean containsValue(Object value) {
440
    return data.containsValue(value);
1✔
441
  }
442

443
  @Override
444
  public @Nullable V get(Object key) {
445
    return getIfPresent(key, /* recordStats= */ false);
1✔
446
  }
447

448
  @Override
449
  public @Nullable V put(K key, V value) {
450
    requireNonNull(value);
1✔
451

452
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
453
    @Nullable V[] oldValue = (@Nullable V[]) new Object[1];
1✔
454
    data.compute(key, (K k, V v) -> {
1✔
455
      discardRefresh(k);
1✔
456
      oldValue[0] = v;
1✔
457
      return value;
1✔
458
    });
459
    if (oldValue[0] != null) {
1✔
460
      notifyOnReplace(key, oldValue[0], value);
1✔
461
    }
462
    return oldValue[0];
1✔
463
  }
464

465
  @Override
466
  public @Nullable V putIfAbsent(K key, V value) {
467
    requireNonNull(value);
1✔
468

469
    // An optimistic fast path to avoid unnecessary locking
470
    var v = data.get(key);
1✔
471
    if (v != null) {
1✔
472
      return v;
1✔
473
    }
474

475
    var added = new boolean[1];
1✔
476
    var val = data.computeIfAbsent(key, k -> {
1✔
477
      discardRefresh(k);
1✔
478
      added[0] = true;
1✔
479
      return value;
1✔
480
    });
481
    return added[0] ? null : val;
1✔
482
  }
483

484
  @Override
485
  @SuppressWarnings("ResultOfMethodCallIgnored")
486
  public void putAll(Map<? extends K, ? extends V> map) {
487
    map.forEach(this::put);
1✔
488
  }
1✔
489

490
  @Override
491
  public @Nullable V remove(Object key) {
492
    @SuppressWarnings("unchecked")
493
    var castKey = (K) key;
1✔
494
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
495
    @Nullable V[] oldValue = (V[]) new Object[1];
1✔
496
    data.compute(castKey, (k, v) -> {
1✔
497
      discardRefresh(k);
1✔
498
      oldValue[0] = v;
1✔
499
      return null;
1✔
500
    });
501

502
    if (oldValue[0] != null) {
1✔
503
      notifyRemoval(castKey, oldValue[0], RemovalCause.EXPLICIT);
1✔
504
    }
505

506
    return oldValue[0];
1✔
507
  }
508

509
  @Override
510
  public boolean remove(Object key, @Nullable Object value) {
511
    if (value == null) {
1✔
512
      requireNonNull(key);
1✔
513
      return false;
1✔
514
    }
515

516
    @SuppressWarnings("unchecked")
517
    var castKey = (K) key;
1✔
518
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
519
    @Nullable V[] oldValue = (V[]) new Object[1];
1✔
520

521
    data.computeIfPresent(castKey, (k, v) -> {
1✔
522
      if (Objects.equals(value, v)) {
1✔
523
        discardRefresh(k);
1✔
524
        oldValue[0] = v;
1✔
525
        return null;
1✔
526
      }
527
      return v;
1✔
528
    });
529

530
    if (oldValue[0] != null) {
1✔
531
      notifyRemoval(castKey, oldValue[0], RemovalCause.EXPLICIT);
1✔
532
      return true;
1✔
533
    }
534
    return false;
1✔
535
  }
536

537
  @Override
538
  public @Nullable V replace(K key, V value) {
539
    requireNonNull(value);
1✔
540

541
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
542
    @Nullable V[] oldValue = (@Nullable V[]) new Object[1];
1✔
543
    data.computeIfPresent(key, (k, v) -> {
1✔
544
      discardRefresh(k);
1✔
545
      oldValue[0] = v;
1✔
546
      return value;
1✔
547
    });
548

549
    if ((oldValue[0] != null) && (oldValue[0] != value)) {
1✔
550
      notifyOnReplace(key, oldValue[0], value);
1✔
551
    }
552
    return oldValue[0];
1✔
553
  }
554

555
  @Override
556
  public boolean replace(K key, V oldValue, V newValue,
557
      boolean shouldDiscardRefresh, boolean quietly) {
558
    requireNonNull(oldValue);
1✔
559
    requireNonNull(newValue);
1✔
560

561
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
562
    @Nullable V[] prev = (V[]) new Object[1];
1✔
563
    data.computeIfPresent(key, (k, v) -> {
1✔
564
      if (Objects.equals(oldValue, v)) {
1✔
565
        if (shouldDiscardRefresh) {
1✔
566
          discardRefresh(k);
1✔
567
        }
568
        prev[0] = v;
1✔
569
        return newValue;
1✔
570
      }
571
      return v;
1✔
572
    });
573

574
    boolean replaced = (prev[0] != null);
1✔
575
    if (replaced && (prev[0] != newValue)) {
1✔
576
      notifyOnReplace(key, prev[0], newValue);
1✔
577
    }
578
    return replaced;
1✔
579
  }
580

581
  @Override
582
  @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
583
  public boolean equals(@Nullable Object o) {
584
    if (o == this) {
1✔
585
      return true;
1✔
586
    }
587
    try {
588
      return data.equals(o);
1✔
589
    } catch (ClassCastException | NullPointerException ignored) {
1✔
590
      return false;
1✔
591
    }
592
  }
593

594
  @Override
595
  public int hashCode() {
596
    return data.hashCode();
1✔
597
  }
598

599
  @Override
600
  public String toString() {
601
    var result = new StringBuilder(50).append('{');
1✔
602
    data.forEach((key, value) -> {
1✔
603
      if (result.length() != 1) {
1✔
604
        result.append(", ");
1✔
605
      }
606
      result.append((key == this) ? "(this Map)" : key)
1✔
607
          .append('=')
1✔
608
          .append((value == this) ? "(this Map)" : value);
1✔
609
    });
1✔
610
    return result.append('}').toString();
1✔
611
  }
612

613
  @Override
614
  public Set<K> keySet() {
615
    Set<K> ks = keySet;
1✔
616
    return (ks == null) ? (keySet = new KeySetView<>(this)) : ks;
1✔
617
  }
618

619
  @Override
620
  public Collection<V> values() {
621
    Collection<V> vs = values;
1✔
622
    return (vs == null) ? (values = new ValuesView<>(this)) : vs;
1✔
623
  }
624

625
  @Override
626
  public Set<Entry<K, V>> entrySet() {
627
    Set<Entry<K, V>> es = entrySet;
1✔
628
    return (es == null) ? (entrySet = new EntrySetView<>(this)) : es;
1✔
629
  }
630

631
  /** An adapter to safely externalize the keys. */
632
  static final class KeySetView<K> extends AbstractSet<K> {
633
    final UnboundedLocalCache<K, ?> cache;
634

635
    KeySetView(UnboundedLocalCache<K, ?> cache) {
1✔
636
      this.cache = requireNonNull(cache);
1✔
637
    }
1✔
638

639
    @Override
640
    public boolean isEmpty() {
641
      return cache.isEmpty();
1✔
642
    }
643

644
    @Override
645
    public int size() {
646
      return cache.size();
1✔
647
    }
648

649
    @Override
650
    public void clear() {
651
      cache.clear();
1✔
652
    }
1✔
653

654
    @Override
655
    @SuppressWarnings("SuspiciousMethodCalls")
656
    public boolean contains(Object o) {
657
      return cache.containsKey(o);
1✔
658
    }
659

660
    @Override
661
    public boolean containsAll(Collection<?> collection) {
662
      requireNonNull(collection);
1✔
663
      if (collection != this) {
1✔
664
        for (Object o : collection) {
1✔
665
          if ((o == null) || !contains(o)) {
1✔
666
            return false;
1✔
667
          }
668
        }
1✔
669
      }
670
      return true;
1✔
671
    }
672

673
    @Override
674
    public boolean removeAll(Collection<?> collection) {
675
      requireNonNull(collection);
1✔
676
      @Var boolean modified = false;
1✔
677
      if ((collection instanceof Set<?>) && (collection.size() > size())) {
1✔
678
        for (K key : this) {
1✔
679
          if (collection.contains(key)) {
1✔
680
            modified |= remove(key);
1✔
681
          }
682
        }
1✔
683
      } else {
684
        for (var o : collection) {
1✔
685
          modified |= (o != null) && remove(o);
1✔
686
        }
1✔
687
      }
688
      return modified;
1✔
689
    }
690

691
    @Override
692
    public boolean remove(Object o) {
693
      return (cache.remove(o) != null);
1✔
694
    }
695

696
    @Override
697
    public boolean removeIf(Predicate<? super K> filter) {
698
      requireNonNull(filter);
1✔
699
      @Var boolean modified = false;
1✔
700
      for (K key : this) {
1✔
701
        if (filter.test(key) && remove(key)) {
1✔
702
          modified = true;
1✔
703
        }
704
      }
1✔
705
      return modified;
1✔
706
    }
707

708
    @Override
709
    public boolean retainAll(Collection<?> collection) {
710
      requireNonNull(collection);
1✔
711
      @Var boolean modified = false;
1✔
712
      for (K key : this) {
1✔
713
        if (!collection.contains(key) && remove(key)) {
1✔
714
          modified = true;
1✔
715
        }
716
      }
1✔
717
      return modified;
1✔
718
    }
719

720
    @Override
721
    public void forEach(Consumer<? super K> action) {
722
      cache.data.keySet().forEach(action);
1✔
723
    }
1✔
724

725
    @Override
726
    public Iterator<K> iterator() {
727
      return new KeyIterator<>(cache);
1✔
728
    }
729

730
    @Override
731
    public Spliterator<K> spliterator() {
732
      return new KeySpliterator<>(cache);
1✔
733
    }
734

735
    @Override
736
    public Object[] toArray() {
737
      return cache.data.keySet().toArray();
1✔
738
    }
739

740
    @Override
741
    public <T> T[] toArray(T[] array) {
742
      return cache.data.keySet().toArray(array);
1✔
743
    }
744
  }
745

746
  /** An adapter to safely externalize the key iterator. */
747
  static final class KeyIterator<K> implements Iterator<K> {
748
    final UnboundedLocalCache<K, ?> cache;
749
    final Iterator<K> iterator;
750
    @Nullable K current;
751

752
    KeyIterator(UnboundedLocalCache<K, ?> cache) {
1✔
753
      this.iterator = cache.data.keySet().iterator();
1✔
754
      this.cache = cache;
1✔
755
    }
1✔
756

757
    @Override
758
    public boolean hasNext() {
759
      return iterator.hasNext();
1✔
760
    }
761

762
    @Override
763
    public K next() {
764
      current = iterator.next();
1✔
765
      return current;
1✔
766
    }
767

768
    @Override
769
    @SuppressWarnings("ResultOfMethodCallIgnored")
770
    public void remove() {
771
      if (current == null) {
1✔
772
        throw new IllegalStateException();
1✔
773
      }
774
      cache.remove(current);
1✔
775
      current = null;
1✔
776
    }
1✔
777
  }
778

779
  /** An adapter to safely externalize the key spliterator. */
780
  static final class KeySpliterator<K> implements Spliterator<K> {
781
    final Spliterator<K> spliterator;
782

783
    KeySpliterator(UnboundedLocalCache<K, ?> cache) {
784
      this(cache.data.keySet().spliterator());
1✔
785
    }
1✔
786

787
    KeySpliterator(Spliterator<K> spliterator) {
1✔
788
      this.spliterator = requireNonNull(spliterator);
1✔
789
    }
1✔
790

791
    @Override
792
    public void forEachRemaining(Consumer<? super K> action) {
793
      requireNonNull(action);
1✔
794
      spliterator.forEachRemaining(action);
1✔
795
    }
1✔
796

797
    @Override
798
    public boolean tryAdvance(Consumer<? super K> action) {
799
      requireNonNull(action);
1✔
800
      return spliterator.tryAdvance(action);
1✔
801
    }
802

803
    @Override
804
    public @Nullable KeySpliterator<K> trySplit() {
805
      Spliterator<K> split = spliterator.trySplit();
1✔
806
      return (split == null) ? null : new KeySpliterator<>(split);
1✔
807
    }
808

809
    @Override
810
    public long estimateSize() {
811
      return spliterator.estimateSize();
1✔
812
    }
813

814
    @Override
815
    public int characteristics() {
816
      return DISTINCT | CONCURRENT | NONNULL;
1✔
817
    }
818
  }
819

820
  /** An adapter to safely externalize the values. */
821
  static final class ValuesView<K, V> extends AbstractCollection<V> {
822
    final UnboundedLocalCache<K, V> cache;
823

824
    ValuesView(UnboundedLocalCache<K, V> cache) {
1✔
825
      this.cache = requireNonNull(cache);
1✔
826
    }
1✔
827

828
    @Override
829
    public boolean isEmpty() {
830
      return cache.isEmpty();
1✔
831
    }
832

833
    @Override
834
    public int size() {
835
      return cache.size();
1✔
836
    }
837

838
    @Override
839
    public void clear() {
840
      cache.clear();
1✔
841
    }
1✔
842

843
    @Override
844
    @SuppressWarnings("SuspiciousMethodCalls")
845
    public boolean contains(Object o) {
846
      return cache.containsValue(o);
1✔
847
    }
848

849
    @Override
850
    public boolean containsAll(Collection<?> collection) {
851
      requireNonNull(collection);
1✔
852
      if (collection != this) {
1✔
853
        for (Object o : collection) {
1✔
854
          if ((o == null) || !contains(o)) {
1✔
855
            return false;
1✔
856
          }
857
        }
1✔
858
      }
859
      return true;
1✔
860
    }
861

862
    @Override
863
    public boolean removeAll(Collection<?> collection) {
864
      requireNonNull(collection);
1✔
865
      @Var boolean modified = false;
1✔
866
      for (var entry : cache.data.entrySet()) {
1✔
867
        if (collection.contains(entry.getValue())
1✔
868
            && cache.remove(entry.getKey(), entry.getValue())) {
1✔
869
          modified = true;
1✔
870
        }
871
      }
1✔
872
      return modified;
1✔
873
    }
874

875
    @Override
876
    public boolean remove(@Nullable Object o) {
877
      if (o == null) {
1✔
878
        return false;
1✔
879
      }
880
      for (var entry : cache.data.entrySet()) {
1✔
881
        if (o.equals(entry.getValue()) && cache.remove(entry.getKey(), entry.getValue())) {
1✔
882
          return true;
1✔
883
        }
884
      }
1✔
885
      return false;
1✔
886
    }
887

888
    @Override
889
    public boolean removeIf(Predicate<? super V> filter) {
890
      requireNonNull(filter);
1✔
891
      @Var boolean removed = false;
1✔
892
      for (var entry : cache.data.entrySet()) {
1✔
893
        if (filter.test(entry.getValue())) {
1✔
894
          removed |= cache.remove(entry.getKey(), entry.getValue());
1✔
895
        }
896
      }
1✔
897
      return removed;
1✔
898
    }
899

900
    @Override
901
    public boolean retainAll(Collection<?> collection) {
902
      requireNonNull(collection);
1✔
903
      @Var boolean modified = false;
1✔
904
      for (var entry : cache.data.entrySet()) {
1✔
905
        if (!collection.contains(entry.getValue())
1✔
906
            && cache.remove(entry.getKey(), entry.getValue())) {
1✔
907
          modified = true;
1✔
908
        }
909
      }
1✔
910
      return modified;
1✔
911
    }
912

913
    @Override
914
    public void forEach(Consumer<? super V> action) {
915
      cache.data.values().forEach(action);
1✔
916
    }
1✔
917

918
    @Override
919
    public Iterator<V> iterator() {
920
      return new ValueIterator<>(cache);
1✔
921
    }
922

923
    @Override
924
    public Spliterator<V> spliterator() {
925
      return new ValueSpliterator<>(cache);
1✔
926
    }
927

928
    @Override
929
    public Object[] toArray() {
930
      return cache.data.values().toArray();
1✔
931
    }
932

933
    @Override
934
    public <T> T[] toArray(T[] array) {
935
      return cache.data.values().toArray(array);
1✔
936
    }
937
  }
938

939
  /** An adapter to safely externalize the value iterator. */
940
  static final class ValueIterator<K, V> implements Iterator<V> {
941
    final UnboundedLocalCache<K, V> cache;
942
    final Iterator<Entry<K, V>> iterator;
943
    @Nullable Entry<K, V> entry;
944

945
    ValueIterator(UnboundedLocalCache<K, V> cache) {
1✔
946
      this.iterator = cache.data.entrySet().iterator();
1✔
947
      this.cache = cache;
1✔
948
    }
1✔
949

950
    @Override
951
    public boolean hasNext() {
952
      return iterator.hasNext();
1✔
953
    }
954

955
    @Override
956
    public V next() {
957
      entry = iterator.next();
1✔
958
      return entry.getValue();
1✔
959
    }
960

961
    @Override
962
    @SuppressWarnings("ResultOfMethodCallIgnored")
963
    public void remove() {
964
      if (entry == null) {
1✔
965
        throw new IllegalStateException();
1✔
966
      }
967
      cache.remove(entry.getKey());
1✔
968
      entry = null;
1✔
969
    }
1✔
970
  }
971

972
  /** An adapter to safely externalize the value spliterator. */
973
  static final class ValueSpliterator<V> implements Spliterator<V> {
974
    final Spliterator<V> spliterator;
975

976
    ValueSpliterator(UnboundedLocalCache<?, V> cache) {
977
      this(cache.data.values().spliterator());
1✔
978
    }
1✔
979

980
    ValueSpliterator(Spliterator<V> spliterator) {
1✔
981
      this.spliterator = requireNonNull(spliterator);
1✔
982
    }
1✔
983

984
    @Override
985
    public void forEachRemaining(Consumer<? super V> action) {
986
      requireNonNull(action);
1✔
987
      spliterator.forEachRemaining(action);
1✔
988
    }
1✔
989

990
    @Override
991
    public boolean tryAdvance(Consumer<? super V> action) {
992
      requireNonNull(action);
1✔
993
      return spliterator.tryAdvance(action);
1✔
994
    }
995

996
    @Override
997
    public @Nullable ValueSpliterator<V> trySplit() {
998
      Spliterator<V> split = spliterator.trySplit();
1✔
999
      return (split == null) ? null : new ValueSpliterator<>(split);
1✔
1000
    }
1001

1002
    @Override
1003
    public long estimateSize() {
1004
      return spliterator.estimateSize();
1✔
1005
    }
1006

1007
    @Override
1008
    public int characteristics() {
1009
      return CONCURRENT | NONNULL;
1✔
1010
    }
1011
  }
1012

1013
  /** An adapter to safely externalize the entries. */
1014
  static final class EntrySetView<K, V> extends AbstractSet<Entry<K, V>> {
1015
    final UnboundedLocalCache<K, V> cache;
1016

1017
    EntrySetView(UnboundedLocalCache<K, V> cache) {
1✔
1018
      this.cache = requireNonNull(cache);
1✔
1019
    }
1✔
1020

1021
    @Override
1022
    public boolean isEmpty() {
1023
      return cache.isEmpty();
1✔
1024
    }
1025

1026
    @Override
1027
    public int size() {
1028
      return cache.size();
1✔
1029
    }
1030

1031
    @Override
1032
    public void clear() {
1033
      cache.clear();
1✔
1034
    }
1✔
1035

1036
    @Override
1037
    @SuppressWarnings("SuspiciousMethodCalls")
1038
    public boolean contains(Object o) {
1039
      if (!(o instanceof Entry<?, ?>)) {
1✔
1040
        return false;
1✔
1041
      }
1042
      var entry = (Entry<?, ?>) o;
1✔
1043
      var key = entry.getKey();
1✔
1044
      var value = entry.getValue();
1✔
1045
      if ((key == null) || (value == null)) {
1✔
1046
        return false;
1✔
1047
      }
1048
      V cachedValue = cache.get(key);
1✔
1049
      return value.equals(cachedValue);
1✔
1050
    }
1051

1052
    @Override
1053
    public boolean removeAll(Collection<?> collection) {
1054
      requireNonNull(collection);
1✔
1055
      @Var boolean modified = false;
1✔
1056
      if ((collection instanceof Set<?>) && (collection.size() > size())) {
1✔
1057
        for (var entry : this) {
1✔
1058
          if (collection.contains(entry)) {
1✔
1059
            modified |= remove(entry);
1✔
1060
          }
1061
        }
1✔
1062
      } else {
1063
        for (var o : collection) {
1✔
1064
          modified |= remove(o);
1✔
1065
        }
1✔
1066
      }
1067
      return modified;
1✔
1068
    }
1069

1070
    @Override
1071
    @SuppressWarnings("SuspiciousMethodCalls")
1072
    public boolean remove(Object o) {
1073
      if (!(o instanceof Entry<?, ?>)) {
1✔
1074
        return false;
1✔
1075
      }
1076
      var entry = (Entry<?, ?>) o;
1✔
1077
      var key = entry.getKey();
1✔
1078
      return (key != null) && cache.remove(key, entry.getValue());
1✔
1079
    }
1080

1081
    @Override
1082
    public boolean removeIf(Predicate<? super Entry<K, V>> filter) {
1083
      requireNonNull(filter);
1✔
1084
      @Var boolean removed = false;
1✔
1085
      for (var entry : cache.data.entrySet()) {
1✔
1086
        var key = entry.getKey();
1✔
1087
        var value = entry.getValue();
1✔
1088
        if (filter.test(Map.entry(key, value))) {
1✔
1089
          removed |= cache.remove(key, value);
1✔
1090
        }
1091
      }
1✔
1092
      return removed;
1✔
1093
    }
1094

1095
    @Override
1096
    public boolean retainAll(Collection<?> collection) {
1097
      requireNonNull(collection);
1✔
1098
      @Var boolean modified = false;
1✔
1099
      for (var entry : this) {
1✔
1100
        if (!collection.contains(entry) && remove(entry)) {
1✔
1101
          modified = true;
1✔
1102
        }
1103
      }
1✔
1104
      return modified;
1✔
1105
    }
1106

1107
    @Override
1108
    public Iterator<Entry<K, V>> iterator() {
1109
      return new EntryIterator<>(cache);
1✔
1110
    }
1111

1112
    @Override
1113
    public Spliterator<Entry<K, V>> spliterator() {
1114
      return new EntrySpliterator<>(cache);
1✔
1115
    }
1116
  }
1117

1118
  /** An adapter to safely externalize the entry iterator. */
1119
  static final class EntryIterator<K, V> implements Iterator<Entry<K, V>> {
1120
    final UnboundedLocalCache<K, V> cache;
1121
    final Iterator<Entry<K, V>> iterator;
1122
    @Nullable Entry<K, V> entry;
1123

1124
    EntryIterator(UnboundedLocalCache<K, V> cache) {
1✔
1125
      this.iterator = cache.data.entrySet().iterator();
1✔
1126
      this.cache = cache;
1✔
1127
    }
1✔
1128

1129
    @Override
1130
    public boolean hasNext() {
1131
      return iterator.hasNext();
1✔
1132
    }
1133

1134
    @Override
1135
    public Entry<K, V> next() {
1136
      entry = iterator.next();
1✔
1137
      return new WriteThroughEntry<>(cache, entry.getKey(), entry.getValue());
1✔
1138
    }
1139

1140
    @Override
1141
    @SuppressWarnings("ResultOfMethodCallIgnored")
1142
    public void remove() {
1143
      if (entry == null) {
1✔
1144
        throw new IllegalStateException();
1✔
1145
      }
1146
      cache.remove(entry.getKey());
1✔
1147
      entry = null;
1✔
1148
    }
1✔
1149
  }
1150

1151
  /** An adapter to safely externalize the entry spliterator. */
1152
  static final class EntrySpliterator<K, V> implements Spliterator<Entry<K, V>> {
1153
    final Spliterator<Entry<K, V>> spliterator;
1154
    final UnboundedLocalCache<K, V> cache;
1155

1156
    EntrySpliterator(UnboundedLocalCache<K, V> cache) {
1157
      this(cache, cache.data.entrySet().spliterator());
1✔
1158
    }
1✔
1159

1160
    EntrySpliterator(UnboundedLocalCache<K, V> cache, Spliterator<Entry<K, V>> spliterator) {
1✔
1161
      this.spliterator = requireNonNull(spliterator);
1✔
1162
      this.cache = requireNonNull(cache);
1✔
1163
    }
1✔
1164

1165
    @Override
1166
    public void forEachRemaining(Consumer<? super Entry<K, V>> action) {
1167
      requireNonNull(action);
1✔
1168
      spliterator.forEachRemaining(entry -> {
1✔
1169
        var e = new WriteThroughEntry<>(cache, entry.getKey(), entry.getValue());
1✔
1170
        action.accept(e);
1✔
1171
      });
1✔
1172
    }
1✔
1173

1174
    @Override
1175
    public boolean tryAdvance(Consumer<? super Entry<K, V>> action) {
1176
      requireNonNull(action);
1✔
1177
      return spliterator.tryAdvance(entry -> {
1✔
1178
        var e = new WriteThroughEntry<>(cache, entry.getKey(), entry.getValue());
1✔
1179
        action.accept(e);
1✔
1180
      });
1✔
1181
    }
1182

1183
    @Override
1184
    public @Nullable EntrySpliterator<K, V> trySplit() {
1185
      Spliterator<Entry<K, V>> split = spliterator.trySplit();
1✔
1186
      return (split == null) ? null : new EntrySpliterator<>(cache, split);
1✔
1187
    }
1188

1189
    @Override
1190
    public long estimateSize() {
1191
      return spliterator.estimateSize();
1✔
1192
    }
1193

1194
    @Override
1195
    public int characteristics() {
1196
      return DISTINCT | CONCURRENT | NONNULL;
1✔
1197
    }
1198
  }
1199

1200
  /* --------------- Manual Cache --------------- */
1201

1202
  static class UnboundedLocalManualCache<K, V> implements LocalManualCache<K, V>, Serializable {
1203
    private static final long serialVersionUID = 1;
1204

1205
    final UnboundedLocalCache<K, V> cache;
1206
    @Nullable Policy<K, V> policy;
1207

1208
    UnboundedLocalManualCache(Caffeine<K, V> builder) {
1✔
1209
      cache = new UnboundedLocalCache<>(builder, /* isAsync= */ false);
1✔
1210
    }
1✔
1211

1212
    @Override
1213
    public final UnboundedLocalCache<K, V> cache() {
1214
      return cache;
1✔
1215
    }
1216

1217
    @Override
1218
    public final Policy<K, V> policy() {
1219
      if (policy == null) {
1✔
1220
        Function<@Nullable V, @Nullable V> identity = v -> v;
1✔
1221
        policy = new UnboundedPolicy<>(cache, identity);
1✔
1222
      }
1223
      return policy;
1✔
1224
    }
1225

1226
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1227
      throw new InvalidObjectException("Proxy required");
1✔
1228
    }
1229

1230
    Object writeReplace() {
1231
      var proxy = new SerializationProxy<K, V>();
1✔
1232
      proxy.isRecordingStats = cache.isRecordingStats;
1✔
1233
      proxy.removalListener = cache.removalListener;
1✔
1234
      return proxy;
1✔
1235
    }
1236
  }
1237

1238
  /** An eviction policy that supports no bounding. */
1239
  static final class UnboundedPolicy<K, V> implements Policy<K, V> {
1240
    final Function<@Nullable V, @Nullable V> transformer;
1241
    final UnboundedLocalCache<K, V> cache;
1242

1243
    UnboundedPolicy(UnboundedLocalCache<K, V> cache,
1244
        Function<@Nullable V, @Nullable V> transformer) {
1✔
1245
      this.transformer = transformer;
1✔
1246
      this.cache = cache;
1✔
1247
    }
1✔
1248
    @Override public boolean isRecordingStats() {
1249
      return cache.isRecordingStats;
1✔
1250
    }
1251
    @Override public @Nullable V getIfPresentQuietly(K key) {
1252
      return transformer.apply(cache.data.get(key));
1✔
1253
    }
1254
    @Override public @Nullable CacheEntry<K, V> getEntryIfPresentQuietly(K key) {
1255
      V value = transformer.apply(cache.data.get(key));
1✔
1256
      return (value == null) ? null : SnapshotEntry.forEntry(key, value);
1✔
1257
    }
1258
    @SuppressWarnings("Java9CollectionFactory")
1259
    @Override public Map<K, CompletableFuture<V>> refreshes() {
1260
      var refreshes = cache.refreshes;
1✔
1261
      if ((refreshes == null) || refreshes.isEmpty()) {
1✔
1262
        @SuppressWarnings({"ImmutableMapOf", "RedundantUnmodifiable"})
1263
        Map<K, CompletableFuture<V>> emptyMap = Collections.unmodifiableMap(Collections.emptyMap());
1✔
1264
        return emptyMap;
1✔
1265
      }
1266
      @SuppressWarnings("unchecked")
1267
      var castedRefreshes = (Map<K, CompletableFuture<V>>) (Object) refreshes;
1✔
1268
      return Collections.unmodifiableMap(new HashMap<>(castedRefreshes));
1✔
1269
    }
1270
    @Override public Optional<Eviction<K, V>> eviction() {
1271
      return Optional.empty();
1✔
1272
    }
1273
    @Override public Optional<FixedExpiration<K, V>> expireAfterAccess() {
1274
      return Optional.empty();
1✔
1275
    }
1276
    @Override public Optional<FixedExpiration<K, V>> expireAfterWrite() {
1277
      return Optional.empty();
1✔
1278
    }
1279
    @Override public Optional<VarExpiration<K, V>> expireVariably() {
1280
      return Optional.empty();
1✔
1281
    }
1282
    @Override public Optional<FixedRefresh<K, V>> refreshAfterWrite() {
1283
      return Optional.empty();
1✔
1284
    }
1285
  }
1286

1287
  /* --------------- Loading Cache --------------- */
1288

1289
  static final class UnboundedLocalLoadingCache<K, V> extends UnboundedLocalManualCache<K, V>
1290
      implements LocalLoadingCache<K, V> {
1291
    private static final long serialVersionUID = 1;
1292

1293
    final Function<K, @Nullable V> mappingFunction;
1294
    final CacheLoader<? super K, V> cacheLoader;
1295
    final @Nullable Function<Set<? extends K>, Map<K, V>> bulkMappingFunction;
1296

1297
    UnboundedLocalLoadingCache(Caffeine<K, V> builder, CacheLoader<? super K, V> cacheLoader) {
1298
      super(builder);
1✔
1299
      this.cacheLoader = cacheLoader;
1✔
1300
      this.mappingFunction = newMappingFunction(cacheLoader);
1✔
1301
      this.bulkMappingFunction = newBulkMappingFunction(cacheLoader);
1✔
1302
    }
1✔
1303

1304
    @Override
1305
    public AsyncCacheLoader<? super K, V> cacheLoader() {
1306
      return cacheLoader;
1✔
1307
    }
1308

1309
    @Override
1310
    public Function<K, @Nullable V> mappingFunction() {
1311
      return mappingFunction;
1✔
1312
    }
1313

1314
    @Override
1315
    public @Nullable Function<Set<? extends K>, Map<K, V>>  bulkMappingFunction() {
1316
      return bulkMappingFunction;
1✔
1317
    }
1318

1319
    @Override
1320
    Object writeReplace() {
1321
      @SuppressWarnings("unchecked")
1322
      var proxy = (SerializationProxy<K, V>) super.writeReplace();
1✔
1323
      proxy.cacheLoader = cacheLoader;
1✔
1324
      return proxy;
1✔
1325
    }
1326

1327
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1328
      throw new InvalidObjectException("Proxy required");
1✔
1329
    }
1330
  }
1331

1332
  /* --------------- Async Cache --------------- */
1333

1334
  static final class UnboundedLocalAsyncCache<K, V> implements LocalAsyncCache<K, V>, Serializable {
1335
    private static final long serialVersionUID = 1;
1336

1337
    final UnboundedLocalCache<K, CompletableFuture<V>> cache;
1338

1339
    @Nullable ConcurrentMap<K, CompletableFuture<V>> mapView;
1340
    @Nullable CacheView<K, V> cacheView;
1341
    @Nullable Policy<K, V> policy;
1342

1343
    @SuppressWarnings("unchecked")
1344
    UnboundedLocalAsyncCache(Caffeine<K, V> builder) {
1✔
1345
      cache = new UnboundedLocalCache<>(
1✔
1346
          (Caffeine<K, CompletableFuture<V>>) builder, /* isAsync= */ true);
1347
    }
1✔
1348

1349
    @Override
1350
    public UnboundedLocalCache<K, CompletableFuture<V>> cache() {
1351
      return cache;
1✔
1352
    }
1353

1354
    @Override
1355
    public ConcurrentMap<K, CompletableFuture<V>> asMap() {
1356
      return (mapView == null) ? (mapView = new AsyncAsMapView<>(this)) : mapView;
1✔
1357
    }
1358

1359
    @Override
1360
    public Cache<K, V> synchronous() {
1361
      return (cacheView == null) ? (cacheView = new CacheView<>(this)) : cacheView;
1✔
1362
    }
1363

1364
    @Override
1365
    public Policy<K, V> policy() {
1366
      @SuppressWarnings("unchecked")
1367
      var castCache = (UnboundedLocalCache<K, V>) cache;
1✔
1368
      Function<CompletableFuture<V>, @Nullable V> transformer = Async::getIfReady;
1✔
1369
      @SuppressWarnings("unchecked")
1370
      var castTransformer = (Function<@Nullable V, @Nullable V>) transformer;
1✔
1371
      return (policy == null)
1✔
1372
          ? (policy = new UnboundedPolicy<>(castCache, castTransformer))
1✔
1373
          : policy;
1✔
1374
    }
1375

1376
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1377
      throw new InvalidObjectException("Proxy required");
1✔
1378
    }
1379

1380
    Object writeReplace() {
1381
      var proxy = new SerializationProxy<K, V>();
1✔
1382
      proxy.isRecordingStats = cache.isRecordingStats;
1✔
1383
      proxy.removalListener = cache.removalListener;
1✔
1384
      proxy.async = true;
1✔
1385
      return proxy;
1✔
1386
    }
1387
  }
1388

1389
  /* --------------- Async Loading Cache --------------- */
1390

1391
  static final class UnboundedLocalAsyncLoadingCache<K, V>
1392
      extends LocalAsyncLoadingCache<K, V> implements Serializable {
1393
    private static final long serialVersionUID = 1;
1394

1395
    final UnboundedLocalCache<K, CompletableFuture<V>> cache;
1396

1397
    @Nullable ConcurrentMap<K, CompletableFuture<V>> mapView;
1398
    @Nullable Policy<K, V> policy;
1399

1400
    @SuppressWarnings("unchecked")
1401
    UnboundedLocalAsyncLoadingCache(Caffeine<K, V> builder, AsyncCacheLoader<? super K, V> loader) {
1402
      super(loader);
1✔
1403
      cache = new UnboundedLocalCache<>(
1✔
1404
          (Caffeine<K, CompletableFuture<V>>) builder, /* isAsync= */ true);
1405
    }
1✔
1406

1407
    @Override
1408
    public LocalCache<K, CompletableFuture<V>> cache() {
1409
      return cache;
1✔
1410
    }
1411

1412
    @Override
1413
    public ConcurrentMap<K, CompletableFuture<V>> asMap() {
1414
      return (mapView == null) ? (mapView = new AsyncAsMapView<>(this)) : mapView;
1✔
1415
    }
1416

1417
    @Override
1418
    public Policy<K, V> policy() {
1419
      @SuppressWarnings("unchecked")
1420
      var castCache = (UnboundedLocalCache<K, V>) cache;
1✔
1421
      Function<CompletableFuture<V>, @Nullable V> transformer = Async::getIfReady;
1✔
1422
      @SuppressWarnings("unchecked")
1423
      var castTransformer = (Function<@Nullable V, @Nullable V>) transformer;
1✔
1424
      return (policy == null)
1✔
1425
          ? (policy = new UnboundedPolicy<>(castCache, castTransformer))
1✔
1426
          : policy;
1✔
1427
    }
1428

1429
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1430
      throw new InvalidObjectException("Proxy required");
1✔
1431
    }
1432

1433
    Object writeReplace() {
1434
      var proxy = new SerializationProxy<K, V>();
1✔
1435
      proxy.isRecordingStats = cache.isRecordingStats();
1✔
1436
      proxy.removalListener = cache.removalListener;
1✔
1437
      proxy.cacheLoader = cacheLoader;
1✔
1438
      proxy.async = true;
1✔
1439
      return proxy;
1✔
1440
    }
1441
  }
1442
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc