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

ben-manes / caffeine / #5651

18 Jul 2026 03:26AM UTC coverage: 66.303% (-33.7%) from 100.0%
#5651

push

github

ben-manes
dependency updates

2809 of 4164 branches covered (67.46%)

5584 of 8422 relevant lines covered (66.3%)

0.66 hits per line

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

89.62
/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
    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) {
×
200
      logger.log(Level.ERROR, "Exception thrown when submitting removal listener", t);
×
201
      task.run();
×
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✔
221
        pending = requireNonNull(refreshes);
1✔
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
        requireNonNull(function.apply(key, requireNonNull(oldValue)));
1✔
254
    for (K key : data.keySet()) {
1✔
255
      remap(key, remappingFunction, /* hints= */ null, /* computeIfAbsent= */ false);
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);
×
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
      try {
307
        V newValue = function.apply(k, value);
1✔
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
      } catch (Throwable t) {
1✔
316
        discardRefresh(k);
1✔
317
        throw t;
1✔
318
      }
319
    });
320
    if (replaced[0]) {
1!
321
      notifyOnReplace(key, oldValue[0], nv);
1✔
322
    } else if (oldValue[0] != null) {
×
323
      notifyRemoval(key, oldValue[0], RemovalCause.EXPLICIT);
×
324
    }
325
    return nv;
1✔
326
  }
327

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

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

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

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

375
        replaced[0] = (newValue != null);
1✔
376
        if (newValue != value) {
1✔
377
          oldValue[0] = value;
1✔
378
        }
379

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

401
  /* --------------- Concurrent Map --------------- */
402

403
  @Override
404
  public boolean isEmpty() {
405
    return data.isEmpty();
1✔
406
  }
407

408
  @Override
409
  public int size() {
410
    return data.size();
1✔
411
  }
412

413
  @Override
414
  @SuppressWarnings("ResultOfMethodCallIgnored")
415
  public void clear() {
416
    var keys = (removalListener == null) ? data.keySet() : List.copyOf(data.keySet());
1✔
417
    for (K key : keys) {
1✔
418
      remove(key);
1✔
419
    }
1✔
420
  }
1✔
421

422
  @Override
423
  public boolean containsKey(Object key) {
424
    return data.containsKey(key);
1✔
425
  }
426

427
  @Override
428
  public boolean containsValue(Object value) {
429
    return data.containsValue(value);
1✔
430
  }
431

432
  @Override
433
  public @Nullable V get(Object key) {
434
    return getIfPresent(key, /* recordStats= */ false);
1✔
435
  }
436

437
  @Override
438
  public @Nullable V put(K key, V value) {
439
    requireNonNull(value);
1✔
440

441
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
442
    @Nullable V[] oldValue = (@Nullable V[]) new Object[1];
1✔
443
    data.compute(key, (K k, V v) -> {
1✔
444
      discardRefresh(k);
1✔
445
      oldValue[0] = v;
1✔
446
      return value;
1✔
447
    });
448
    if (oldValue[0] != null) {
1✔
449
      notifyOnReplace(key, oldValue[0], value);
1✔
450
    }
451
    return oldValue[0];
1✔
452
  }
453

454
  @Override
455
  public @Nullable V putIfAbsent(K key, V value) {
456
    requireNonNull(value);
1✔
457

458
    // An optimistic fast path to avoid unnecessary locking
459
    var v = data.get(key);
1✔
460
    if (v != null) {
1✔
461
      return v;
1✔
462
    }
463

464
    var added = new boolean[1];
1✔
465
    var val = data.computeIfAbsent(key, k -> {
1✔
466
      discardRefresh(k);
1✔
467
      added[0] = true;
1✔
468
      return value;
1✔
469
    });
470
    return added[0] ? null : val;
1✔
471
  }
472

473
  @Override
474
  @SuppressWarnings("ResultOfMethodCallIgnored")
475
  public void putAll(Map<? extends K, ? extends V> map) {
476
    map.forEach(this::put);
1✔
477
  }
1✔
478

479
  @Override
480
  public @Nullable V remove(Object key) {
481
    @SuppressWarnings("unchecked")
482
    var castKey = (K) key;
1✔
483
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
484
    @Nullable V[] oldValue = (V[]) new Object[1];
1✔
485
    data.computeIfPresent(castKey, (k, v) -> {
1✔
486
      discardRefresh(k);
1✔
487
      oldValue[0] = v;
1✔
488
      return null;
1✔
489
    });
490

491
    if (oldValue[0] != null) {
1✔
492
      notifyRemoval(castKey, oldValue[0], RemovalCause.EXPLICIT);
1✔
493
    }
494

495
    return oldValue[0];
1✔
496
  }
497

498
  @Override
499
  public boolean remove(Object key, @Nullable Object value) {
500
    if (value == null) {
1✔
501
      requireNonNull(key);
1✔
502
      return false;
1✔
503
    }
504

505
    @SuppressWarnings("unchecked")
506
    var castKey = (K) key;
1✔
507
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
508
    @Nullable V[] oldValue = (V[]) new Object[1];
1✔
509

510
    data.computeIfPresent(castKey, (k, v) -> {
1✔
511
      if (Objects.equals(value, v)) {
1✔
512
        discardRefresh(k);
1✔
513
        oldValue[0] = v;
1✔
514
        return null;
1✔
515
      }
516
      return v;
1✔
517
    });
518

519
    if (oldValue[0] != null) {
1✔
520
      notifyRemoval(castKey, oldValue[0], RemovalCause.EXPLICIT);
1✔
521
      return true;
1✔
522
    }
523
    return false;
1✔
524
  }
525

526
  @Override
527
  public @Nullable V replace(K key, V value) {
528
    requireNonNull(value);
1✔
529

530
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
531
    @Nullable V[] oldValue = (@Nullable V[]) new Object[1];
1✔
532
    data.computeIfPresent(key, (k, v) -> {
1✔
533
      discardRefresh(k);
1✔
534
      oldValue[0] = v;
1✔
535
      return value;
1✔
536
    });
537

538
    if ((oldValue[0] != null) && (oldValue[0] != value)) {
1✔
539
      notifyOnReplace(key, oldValue[0], value);
1✔
540
    }
541
    return oldValue[0];
1✔
542
  }
543

544
  @Override
545
  public boolean replace(K key, V oldValue, V newValue) {
546
    return replace(key, oldValue, newValue, /* shouldDiscardRefresh= */ true);
1✔
547
  }
548

549
  @Override
550
  public boolean replace(K key, V oldValue, V newValue, boolean shouldDiscardRefresh) {
551
    requireNonNull(oldValue);
1✔
552
    requireNonNull(newValue);
1✔
553

554
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
555
    @Nullable V[] prev = (V[]) new Object[1];
1✔
556
    data.computeIfPresent(key, (k, v) -> {
1✔
557
      if (Objects.equals(oldValue, v)) {
1!
558
        if (shouldDiscardRefresh) {
1✔
559
          discardRefresh(k);
1✔
560
        }
561
        prev[0] = v;
1✔
562
        return newValue;
1✔
563
      }
564
      return v;
×
565
    });
566

567
    boolean replaced = (prev[0] != null);
1✔
568
    if (replaced && (prev[0] != newValue)) {
1✔
569
      notifyOnReplace(key, prev[0], newValue);
1✔
570
    }
571
    return replaced;
1✔
572
  }
573

574
  @Override
575
  @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
576
  public boolean equals(@Nullable Object o) {
577
    if (o == this) {
1!
578
      return true;
×
579
    }
580
    try {
581
      return data.equals(o);
1✔
582
    } catch (ClassCastException | NullPointerException ignored) {
×
583
      return false;
×
584
    }
585
  }
586

587
  @Override
588
  public int hashCode() {
589
    return data.hashCode();
1✔
590
  }
591

592
  @Override
593
  public String toString() {
594
    var result = new StringBuilder(50).append('{');
1✔
595
    data.forEach((key, value) -> {
1✔
596
      if (result.length() != 1) {
1✔
597
        result.append(", ");
1✔
598
      }
599
      result.append((key == this) ? "(this Map)" : key)
1!
600
          .append('=')
1✔
601
          .append((value == this) ? "(this Map)" : value);
1!
602
    });
1✔
603
    return result.append('}').toString();
1✔
604
  }
605

606
  @Override
607
  public Set<K> keySet() {
608
    Set<K> ks = keySet;
1✔
609
    return (ks == null) ? (keySet = new KeySetView<>(this)) : ks;
1✔
610
  }
611

612
  @Override
613
  public Collection<V> values() {
614
    Collection<V> vs = values;
1✔
615
    return (vs == null) ? (values = new ValuesView<>(this)) : vs;
1✔
616
  }
617

618
  @Override
619
  public Set<Entry<K, V>> entrySet() {
620
    Set<Entry<K, V>> es = entrySet;
1✔
621
    return (es == null) ? (entrySet = new EntrySetView<>(this)) : es;
1✔
622
  }
623

624
  /** An adapter to safely externalize the keys. */
625
  static final class KeySetView<K> extends AbstractSet<K> {
626
    final UnboundedLocalCache<K, ?> cache;
627

628
    KeySetView(UnboundedLocalCache<K, ?> cache) {
1✔
629
      this.cache = requireNonNull(cache);
1✔
630
    }
1✔
631

632
    @Override
633
    public boolean isEmpty() {
634
      return cache.isEmpty();
1✔
635
    }
636

637
    @Override
638
    public int size() {
639
      return cache.size();
1✔
640
    }
641

642
    @Override
643
    public void clear() {
644
      cache.clear();
1✔
645
    }
1✔
646

647
    @Override
648
    @SuppressWarnings("SuspiciousMethodCalls")
649
    public boolean contains(Object o) {
650
      return cache.containsKey(o);
1✔
651
    }
652

653
    @Override
654
    public boolean containsAll(Collection<?> collection) {
655
      requireNonNull(collection);
1✔
656
      if (collection != this) {
1!
657
        for (Object o : collection) {
1!
658
          if ((o == null) || !contains(o)) {
×
659
            return false;
×
660
          }
661
        }
×
662
      }
663
      return true;
1✔
664
    }
665

666
    @Override
667
    public boolean removeAll(Collection<?> collection) {
668
      requireNonNull(collection);
1✔
669
      @Var boolean modified = false;
1✔
670
      if ((collection instanceof Set<?>) && (collection.size() > size())) {
1✔
671
        for (K key : this) {
1✔
672
          if (collection.contains(key)) {
1✔
673
            modified |= remove(key);
1✔
674
          }
675
        }
1✔
676
      } else {
677
        for (var o : collection) {
1✔
678
          modified |= (o != null) && remove(o);
1!
679
        }
1✔
680
      }
681
      return modified;
1✔
682
    }
683

684
    @Override
685
    public boolean remove(Object o) {
686
      return (cache.remove(o) != null);
1✔
687
    }
688

689
    @Override
690
    public boolean removeIf(Predicate<? super K> filter) {
691
      requireNonNull(filter);
1✔
692
      @Var boolean modified = false;
1✔
693
      for (K key : this) {
1✔
694
        if (filter.test(key) && remove(key)) {
1✔
695
          modified = true;
1✔
696
        }
697
      }
1✔
698
      return modified;
1✔
699
    }
700

701
    @Override
702
    public boolean retainAll(Collection<?> collection) {
703
      requireNonNull(collection);
1✔
704
      @Var boolean modified = false;
1✔
705
      for (K key : this) {
1✔
706
        if (!collection.contains(key) && remove(key)) {
1✔
707
          modified = true;
1✔
708
        }
709
      }
1✔
710
      return modified;
1✔
711
    }
712

713
    @Override
714
    public void forEach(Consumer<? super K> action) {
715
      cache.data.keySet().forEach(action);
×
716
    }
×
717

718
    @Override
719
    public Iterator<K> iterator() {
720
      return new KeyIterator<>(cache);
1✔
721
    }
722

723
    @Override
724
    public Spliterator<K> spliterator() {
725
      return new KeySpliterator<>(cache);
1✔
726
    }
727

728
    @Override
729
    public Object[] toArray() {
730
      return cache.data.keySet().toArray();
1✔
731
    }
732

733
    @Override
734
    public <T> T[] toArray(T[] array) {
735
      return cache.data.keySet().toArray(array);
1✔
736
    }
737
  }
738

739
  /** An adapter to safely externalize the key iterator. */
740
  static final class KeyIterator<K> implements Iterator<K> {
741
    final UnboundedLocalCache<K, ?> cache;
742
    final Iterator<K> iterator;
743
    @Nullable K current;
744

745
    KeyIterator(UnboundedLocalCache<K, ?> cache) {
1✔
746
      this.iterator = cache.data.keySet().iterator();
1✔
747
      this.cache = cache;
1✔
748
    }
1✔
749

750
    @Override
751
    public boolean hasNext() {
752
      return iterator.hasNext();
1✔
753
    }
754

755
    @Override
756
    public K next() {
757
      current = iterator.next();
1✔
758
      return current;
1✔
759
    }
760

761
    @Override
762
    @SuppressWarnings("ResultOfMethodCallIgnored")
763
    public void remove() {
764
      if (current == null) {
×
765
        throw new IllegalStateException();
×
766
      }
767
      cache.remove(current);
×
768
      current = null;
×
769
    }
×
770
  }
771

772
  /** An adapter to safely externalize the key spliterator. */
773
  static final class KeySpliterator<K, V> implements Spliterator<K> {
774
    final Spliterator<K> spliterator;
775

776
    KeySpliterator(UnboundedLocalCache<K, V> cache) {
777
      this(cache.data.keySet().spliterator());
1✔
778
    }
1✔
779

780
    KeySpliterator(Spliterator<K> spliterator) {
1✔
781
      this.spliterator = requireNonNull(spliterator);
1✔
782
    }
1✔
783

784
    @Override
785
    public void forEachRemaining(Consumer<? super K> action) {
786
      requireNonNull(action);
1✔
787
      spliterator.forEachRemaining(action);
1✔
788
    }
1✔
789

790
    @Override
791
    public boolean tryAdvance(Consumer<? super K> action) {
792
      requireNonNull(action);
1✔
793
      return spliterator.tryAdvance(action);
1✔
794
    }
795

796
    @Override
797
    public @Nullable KeySpliterator<K, V> trySplit() {
798
      Spliterator<K> split = spliterator.trySplit();
1✔
799
      return (split == null) ? null : new KeySpliterator<>(split);
1✔
800
    }
801

802
    @Override
803
    public long estimateSize() {
804
      return spliterator.estimateSize();
1✔
805
    }
806

807
    @Override
808
    public int characteristics() {
809
      return DISTINCT | CONCURRENT | NONNULL;
1✔
810
    }
811
  }
812

813
  /** An adapter to safely externalize the values. */
814
  static final class ValuesView<K, V> extends AbstractCollection<V> {
815
    final UnboundedLocalCache<K, V> cache;
816

817
    ValuesView(UnboundedLocalCache<K, V> cache) {
1✔
818
      this.cache = requireNonNull(cache);
1✔
819
    }
1✔
820

821
    @Override
822
    public boolean isEmpty() {
823
      return cache.isEmpty();
1✔
824
    }
825

826
    @Override
827
    public int size() {
828
      return cache.size();
1✔
829
    }
830

831
    @Override
832
    public void clear() {
833
      cache.clear();
×
834
    }
×
835

836
    @Override
837
    @SuppressWarnings("SuspiciousMethodCalls")
838
    public boolean contains(Object o) {
839
      return cache.containsValue(o);
1✔
840
    }
841

842
    @Override
843
    public boolean containsAll(Collection<?> collection) {
844
      requireNonNull(collection);
1✔
845
      if (collection != this) {
1!
846
        for (Object o : collection) {
1!
847
          if ((o == null) || !contains(o)) {
1!
848
            return false;
1✔
849
          }
850
        }
1✔
851
      }
852
      return true;
×
853
    }
854

855
    @Override
856
    public boolean removeAll(Collection<?> collection) {
857
      requireNonNull(collection);
1✔
858
      @Var boolean modified = false;
1✔
859
      for (var entry : cache.data.entrySet()) {
1✔
860
        if (collection.contains(entry.getValue())
1✔
861
            && cache.remove(entry.getKey(), entry.getValue())) {
1!
862
          modified = true;
1✔
863
        }
864
      }
1✔
865
      return modified;
1✔
866
    }
867

868
    @Override
869
    public boolean remove(@Nullable Object o) {
870
      if (o == null) {
1✔
871
        return false;
1✔
872
      }
873
      for (var entry : cache.data.entrySet()) {
1!
874
        if (o.equals(entry.getValue()) && cache.remove(entry.getKey(), entry.getValue())) {
1!
875
          return true;
1✔
876
        }
877
      }
×
878
      return false;
×
879
    }
880

881
    @Override
882
    public boolean removeIf(Predicate<? super V> filter) {
883
      requireNonNull(filter);
1✔
884
      @Var boolean removed = false;
1✔
885
      for (var entry : cache.data.entrySet()) {
1✔
886
        if (filter.test(entry.getValue())) {
1✔
887
          removed |= cache.remove(entry.getKey(), entry.getValue());
1✔
888
        }
889
      }
1✔
890
      return removed;
1✔
891
    }
892

893
    @Override
894
    public boolean retainAll(Collection<?> collection) {
895
      requireNonNull(collection);
1✔
896
      @Var boolean modified = false;
1✔
897
      for (var entry : cache.data.entrySet()) {
1✔
898
        if (!collection.contains(entry.getValue())
1✔
899
            && cache.remove(entry.getKey(), entry.getValue())) {
1!
900
          modified = true;
1✔
901
        }
902
      }
1✔
903
      return modified;
1✔
904
    }
905

906
    @Override
907
    public void forEach(Consumer<? super V> action) {
908
      cache.data.values().forEach(action);
×
909
    }
×
910

911
    @Override
912
    public Iterator<V> iterator() {
913
      return new ValueIterator<>(cache);
1✔
914
    }
915

916
    @Override
917
    public Spliterator<V> spliterator() {
918
      return new ValueSpliterator<>(cache);
1✔
919
    }
920

921
    @Override
922
    public Object[] toArray() {
923
      return cache.data.values().toArray();
1✔
924
    }
925

926
    @Override
927
    public <T> T[] toArray(T[] array) {
928
      return cache.data.values().toArray(array);
1✔
929
    }
930
  }
931

932
  /** An adapter to safely externalize the value iterator. */
933
  static final class ValueIterator<K, V> implements Iterator<V> {
934
    final UnboundedLocalCache<K, V> cache;
935
    final Iterator<Entry<K, V>> iterator;
936
    @Nullable Entry<K, V> entry;
937

938
    ValueIterator(UnboundedLocalCache<K, V> cache) {
1✔
939
      this.iterator = cache.data.entrySet().iterator();
1✔
940
      this.cache = cache;
1✔
941
    }
1✔
942

943
    @Override
944
    public boolean hasNext() {
945
      return iterator.hasNext();
1✔
946
    }
947

948
    @Override
949
    public V next() {
950
      entry = iterator.next();
1✔
951
      return entry.getValue();
1✔
952
    }
953

954
    @Override
955
    @SuppressWarnings("ResultOfMethodCallIgnored")
956
    public void remove() {
957
      if (entry == null) {
×
958
        throw new IllegalStateException();
×
959
      }
960
      cache.remove(entry.getKey());
×
961
      entry = null;
×
962
    }
×
963
  }
964

965
  /** An adapter to safely externalize the value spliterator. */
966
  static final class ValueSpliterator<K, V> implements Spliterator<V> {
967
    final Spliterator<V> spliterator;
968

969
    ValueSpliterator(UnboundedLocalCache<K, V> cache) {
970
      this(cache.data.values().spliterator());
1✔
971
    }
1✔
972

973
    ValueSpliterator(Spliterator<V> spliterator) {
1✔
974
      this.spliterator = requireNonNull(spliterator);
1✔
975
    }
1✔
976

977
    @Override
978
    public void forEachRemaining(Consumer<? super V> action) {
979
      requireNonNull(action);
1✔
980
      spliterator.forEachRemaining(action);
1✔
981
    }
1✔
982

983
    @Override
984
    public boolean tryAdvance(Consumer<? super V> action) {
985
      requireNonNull(action);
1✔
986
      return spliterator.tryAdvance(action);
1✔
987
    }
988

989
    @Override
990
    public @Nullable ValueSpliterator<K, V> trySplit() {
991
      Spliterator<V> split = spliterator.trySplit();
1✔
992
      return (split == null) ? null : new ValueSpliterator<>(split);
1✔
993
    }
994

995
    @Override
996
    public long estimateSize() {
997
      return spliterator.estimateSize();
×
998
    }
999

1000
    @Override
1001
    public int characteristics() {
1002
      return CONCURRENT | NONNULL;
1✔
1003
    }
1004
  }
1005

1006
  /** An adapter to safely externalize the entries. */
1007
  static final class EntrySetView<K, V> extends AbstractSet<Entry<K, V>> {
1008
    final UnboundedLocalCache<K, V> cache;
1009

1010
    EntrySetView(UnboundedLocalCache<K, V> cache) {
1✔
1011
      this.cache = requireNonNull(cache);
1✔
1012
    }
1✔
1013

1014
    @Override
1015
    public boolean isEmpty() {
1016
      return cache.isEmpty();
1✔
1017
    }
1018

1019
    @Override
1020
    public int size() {
1021
      return cache.size();
1✔
1022
    }
1023

1024
    @Override
1025
    public void clear() {
1026
      cache.clear();
1✔
1027
    }
1✔
1028

1029
    @Override
1030
    @SuppressWarnings("SuspiciousMethodCalls")
1031
    public boolean contains(Object o) {
1032
      if (!(o instanceof Entry<?, ?>)) {
1✔
1033
        return false;
1✔
1034
      }
1035
      var entry = (Entry<?, ?>) o;
1✔
1036
      var key = entry.getKey();
1✔
1037
      var value = entry.getValue();
1✔
1038
      if ((key == null) || (value == null)) {
1✔
1039
        return false;
1✔
1040
      }
1041
      V cachedValue = cache.get(key);
1✔
1042
      return value.equals(cachedValue);
1✔
1043
    }
1044

1045
    @Override
1046
    public boolean removeAll(Collection<?> collection) {
1047
      requireNonNull(collection);
1✔
1048
      @Var boolean modified = false;
1✔
1049
      if ((collection instanceof Set<?>) && (collection.size() > size())) {
1✔
1050
        for (var entry : this) {
1✔
1051
          if (collection.contains(entry)) {
1!
1052
            modified |= remove(entry);
1✔
1053
          }
1054
        }
1✔
1055
      } else {
1056
        for (var o : collection) {
1✔
1057
          modified |= remove(o);
1✔
1058
        }
1✔
1059
      }
1060
      return modified;
1✔
1061
    }
1062

1063
    @Override
1064
    @SuppressWarnings("SuspiciousMethodCalls")
1065
    public boolean remove(Object o) {
1066
      if (!(o instanceof Entry<?, ?>)) {
1✔
1067
        return false;
1✔
1068
      }
1069
      var entry = (Entry<?, ?>) o;
1✔
1070
      var key = entry.getKey();
1✔
1071
      return (key != null) && cache.remove(key, entry.getValue());
1✔
1072
    }
1073

1074
    @Override
1075
    public boolean removeIf(Predicate<? super Entry<K, V>> filter) {
1076
      requireNonNull(filter);
1✔
1077
      @Var boolean removed = false;
1✔
1078
      for (var entry : cache.data.entrySet()) {
1✔
1079
        var key = entry.getKey();
1✔
1080
        var value = entry.getValue();
1✔
1081
        if (filter.test(Map.entry(key, value))) {
1✔
1082
          removed |= cache.remove(key, value);
1✔
1083
        }
1084
      }
1✔
1085
      return removed;
1✔
1086
    }
1087

1088
    @Override
1089
    public boolean retainAll(Collection<?> collection) {
1090
      requireNonNull(collection);
1✔
1091
      @Var boolean modified = false;
1✔
1092
      for (var entry : this) {
1✔
1093
        if (!collection.contains(entry) && remove(entry)) {
1✔
1094
          modified = true;
1✔
1095
        }
1096
      }
1✔
1097
      return modified;
1✔
1098
    }
1099

1100
    @Override
1101
    public Iterator<Entry<K, V>> iterator() {
1102
      return new EntryIterator<>(cache);
1✔
1103
    }
1104

1105
    @Override
1106
    public Spliterator<Entry<K, V>> spliterator() {
1107
      return new EntrySpliterator<>(cache);
1✔
1108
    }
1109
  }
1110

1111
  /** An adapter to safely externalize the entry iterator. */
1112
  static final class EntryIterator<K, V> implements Iterator<Entry<K, V>> {
1113
    final UnboundedLocalCache<K, V> cache;
1114
    final Iterator<Entry<K, V>> iterator;
1115
    @Nullable Entry<K, V> entry;
1116

1117
    EntryIterator(UnboundedLocalCache<K, V> cache) {
1✔
1118
      this.iterator = cache.data.entrySet().iterator();
1✔
1119
      this.cache = cache;
1✔
1120
    }
1✔
1121

1122
    @Override
1123
    public boolean hasNext() {
1124
      return iterator.hasNext();
1✔
1125
    }
1126

1127
    @Override
1128
    public Entry<K, V> next() {
1129
      entry = iterator.next();
1✔
1130
      return new WriteThroughEntry<>(cache, entry.getKey(), entry.getValue());
1✔
1131
    }
1132

1133
    @Override
1134
    @SuppressWarnings("ResultOfMethodCallIgnored")
1135
    public void remove() {
1136
      if (entry == null) {
1✔
1137
        throw new IllegalStateException();
1✔
1138
      }
1139
      cache.remove(entry.getKey());
1✔
1140
      entry = null;
1✔
1141
    }
1✔
1142
  }
1143

1144
  /** An adapter to safely externalize the entry spliterator. */
1145
  static final class EntrySpliterator<K, V> implements Spliterator<Entry<K, V>> {
1146
    final Spliterator<Entry<K, V>> spliterator;
1147
    final UnboundedLocalCache<K, V> cache;
1148

1149
    EntrySpliterator(UnboundedLocalCache<K, V> cache) {
1150
      this(cache, cache.data.entrySet().spliterator());
1✔
1151
    }
1✔
1152

1153
    EntrySpliterator(UnboundedLocalCache<K, V> cache, Spliterator<Entry<K, V>> spliterator) {
1✔
1154
      this.spliterator = requireNonNull(spliterator);
1✔
1155
      this.cache = requireNonNull(cache);
1✔
1156
    }
1✔
1157

1158
    @Override
1159
    public void forEachRemaining(Consumer<? super Entry<K, V>> action) {
1160
      requireNonNull(action);
1✔
1161
      spliterator.forEachRemaining(entry -> {
1✔
1162
        var e = new WriteThroughEntry<>(cache, entry.getKey(), entry.getValue());
1✔
1163
        action.accept(e);
1✔
1164
      });
1✔
1165
    }
1✔
1166

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

1176
    @Override
1177
    public @Nullable EntrySpliterator<K, V> trySplit() {
1178
      Spliterator<Entry<K, V>> split = spliterator.trySplit();
1✔
1179
      return (split == null) ? null : new EntrySpliterator<>(cache, split);
1✔
1180
    }
1181

1182
    @Override
1183
    public long estimateSize() {
1184
      return spliterator.estimateSize();
1✔
1185
    }
1186

1187
    @Override
1188
    public int characteristics() {
1189
      return DISTINCT | CONCURRENT | NONNULL;
1✔
1190
    }
1191
  }
1192

1193
  /* --------------- Manual Cache --------------- */
1194

1195
  static class UnboundedLocalManualCache<K, V> implements LocalManualCache<K, V>, Serializable {
1196
    private static final long serialVersionUID = 1;
1197

1198
    final UnboundedLocalCache<K, V> cache;
1199
    @Nullable Policy<K, V> policy;
1200

1201
    UnboundedLocalManualCache(Caffeine<K, V> builder) {
1✔
1202
      cache = new UnboundedLocalCache<>(builder, /* isAsync= */ false);
1✔
1203
    }
1✔
1204

1205
    @Override
1206
    public final UnboundedLocalCache<K, V> cache() {
1207
      return cache;
1✔
1208
    }
1209

1210
    @Override
1211
    public final Policy<K, V> policy() {
1212
      if (policy == null) {
1✔
1213
        Function<@Nullable V, @Nullable V> identity = v -> v;
1✔
1214
        policy = new UnboundedPolicy<>(cache, identity);
1✔
1215
      }
1216
      return policy;
1✔
1217
    }
1218

1219
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1220
      throw new InvalidObjectException("Proxy required");
×
1221
    }
1222

1223
    Object writeReplace() {
1224
      var proxy = new SerializationProxy<K, V>();
×
1225
      proxy.isRecordingStats = cache.isRecordingStats;
×
1226
      proxy.removalListener = cache.removalListener;
×
1227
      return proxy;
×
1228
    }
1229
  }
1230

1231
  /** An eviction policy that supports no bounding. */
1232
  static final class UnboundedPolicy<K, V> implements Policy<K, V> {
1233
    final Function<@Nullable V, @Nullable V> transformer;
1234
    final UnboundedLocalCache<K, V> cache;
1235

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

1280
  /* --------------- Loading Cache --------------- */
1281

1282
  static final class UnboundedLocalLoadingCache<K, V> extends UnboundedLocalManualCache<K, V>
1283
      implements LocalLoadingCache<K, V> {
1284
    private static final long serialVersionUID = 1;
1285

1286
    final Function<K, @Nullable V> mappingFunction;
1287
    final CacheLoader<? super K, V> cacheLoader;
1288
    final @Nullable Function<Set<? extends K>, Map<K, V>> bulkMappingFunction;
1289

1290
    UnboundedLocalLoadingCache(Caffeine<K, V> builder, CacheLoader<? super K, V> cacheLoader) {
1291
      super(builder);
1✔
1292
      this.cacheLoader = cacheLoader;
1✔
1293
      this.mappingFunction = newMappingFunction(cacheLoader);
1✔
1294
      this.bulkMappingFunction = newBulkMappingFunction(cacheLoader);
1✔
1295
    }
1✔
1296

1297
    @Override
1298
    public AsyncCacheLoader<? super K, V> cacheLoader() {
1299
      return cacheLoader;
1✔
1300
    }
1301

1302
    @Override
1303
    public Function<K, @Nullable V> mappingFunction() {
1304
      return mappingFunction;
1✔
1305
    }
1306

1307
    @Override
1308
    public @Nullable Function<Set<? extends K>, Map<K, V>>  bulkMappingFunction() {
1309
      return bulkMappingFunction;
1✔
1310
    }
1311

1312
    @Override
1313
    Object writeReplace() {
1314
      @SuppressWarnings("unchecked")
1315
      var proxy = (SerializationProxy<K, V>) super.writeReplace();
×
1316
      proxy.cacheLoader = cacheLoader;
×
1317
      return proxy;
×
1318
    }
1319

1320
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1321
      throw new InvalidObjectException("Proxy required");
×
1322
    }
1323
  }
1324

1325
  /* --------------- Async Cache --------------- */
1326

1327
  static final class UnboundedLocalAsyncCache<K, V> implements LocalAsyncCache<K, V>, Serializable {
1328
    private static final long serialVersionUID = 1;
1329

1330
    final UnboundedLocalCache<K, CompletableFuture<V>> cache;
1331

1332
    @Nullable ConcurrentMap<K, CompletableFuture<V>> mapView;
1333
    @Nullable CacheView<K, V> cacheView;
1334
    @Nullable Policy<K, V> policy;
1335

1336
    @SuppressWarnings("unchecked")
1337
    UnboundedLocalAsyncCache(Caffeine<K, V> builder) {
1✔
1338
      cache = new UnboundedLocalCache<>(
1✔
1339
          (Caffeine<K, CompletableFuture<V>>) builder, /* isAsync= */ true);
1340
    }
1✔
1341

1342
    @Override
1343
    public UnboundedLocalCache<K, CompletableFuture<V>> cache() {
1344
      return cache;
1✔
1345
    }
1346

1347
    @Override
1348
    public ConcurrentMap<K, CompletableFuture<V>> asMap() {
1349
      return (mapView == null) ? (mapView = new AsyncAsMapView<>(this)) : mapView;
1✔
1350
    }
1351

1352
    @Override
1353
    public Cache<K, V> synchronous() {
1354
      return (cacheView == null) ? (cacheView = new CacheView<>(this)) : cacheView;
1✔
1355
    }
1356

1357
    @Override
1358
    public Policy<K, V> policy() {
1359
      @SuppressWarnings("unchecked")
1360
      var castCache = (UnboundedLocalCache<K, V>) cache;
1✔
1361
      Function<CompletableFuture<V>, @Nullable V> transformer = Async::getIfReady;
1✔
1362
      @SuppressWarnings("unchecked")
1363
      var castTransformer = (Function<@Nullable V, @Nullable V>) transformer;
1✔
1364
      return (policy == null)
1✔
1365
          ? (policy = new UnboundedPolicy<>(castCache, castTransformer))
1✔
1366
          : policy;
1✔
1367
    }
1368

1369
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1370
      throw new InvalidObjectException("Proxy required");
×
1371
    }
1372

1373
    Object writeReplace() {
1374
      var proxy = new SerializationProxy<K, V>();
×
1375
      proxy.isRecordingStats = cache.isRecordingStats;
×
1376
      proxy.removalListener = cache.removalListener;
×
1377
      proxy.async = true;
×
1378
      return proxy;
×
1379
    }
1380
  }
1381

1382
  /* --------------- Async Loading Cache --------------- */
1383

1384
  static final class UnboundedLocalAsyncLoadingCache<K, V>
1385
      extends LocalAsyncLoadingCache<K, V> implements Serializable {
1386
    private static final long serialVersionUID = 1;
1387

1388
    final UnboundedLocalCache<K, CompletableFuture<V>> cache;
1389

1390
    @Nullable ConcurrentMap<K, CompletableFuture<V>> mapView;
1391
    @Nullable Policy<K, V> policy;
1392

1393
    @SuppressWarnings("unchecked")
1394
    UnboundedLocalAsyncLoadingCache(Caffeine<K, V> builder, AsyncCacheLoader<? super K, V> loader) {
1395
      super(loader);
1✔
1396
      cache = new UnboundedLocalCache<>(
1✔
1397
          (Caffeine<K, CompletableFuture<V>>) builder, /* isAsync= */ true);
1398
    }
1✔
1399

1400
    @Override
1401
    public LocalCache<K, CompletableFuture<V>> cache() {
1402
      return cache;
1✔
1403
    }
1404

1405
    @Override
1406
    public ConcurrentMap<K, CompletableFuture<V>> asMap() {
1407
      return (mapView == null) ? (mapView = new AsyncAsMapView<>(this)) : mapView;
1✔
1408
    }
1409

1410
    @Override
1411
    public Policy<K, V> policy() {
1412
      @SuppressWarnings("unchecked")
1413
      var castCache = (UnboundedLocalCache<K, V>) cache;
1✔
1414
      Function<CompletableFuture<V>, @Nullable V> transformer = Async::getIfReady;
1✔
1415
      @SuppressWarnings("unchecked")
1416
      var castTransformer = (Function<@Nullable V, @Nullable V>) transformer;
1✔
1417
      return (policy == null)
1✔
1418
          ? (policy = new UnboundedPolicy<>(castCache, castTransformer))
1✔
1419
          : policy;
1✔
1420
    }
1421

1422
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1423
      throw new InvalidObjectException("Proxy required");
×
1424
    }
1425

1426
    Object writeReplace() {
1427
      var proxy = new SerializationProxy<K, V>();
×
1428
      proxy.isRecordingStats = cache.isRecordingStats();
×
1429
      proxy.removalListener = cache.removalListener;
×
1430
      proxy.cacheLoader = cacheLoader;
×
1431
      proxy.async = true;
×
1432
      return proxy;
×
1433
    }
1434
  }
1435
}
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