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

ben-manes / caffeine / #5707

02 Aug 2026 12:45AM UTC coverage: 0.106% (-99.9%) from 100.0%
#5707

push

github

ben-manes
fix wikibench trace reader after overly strict audit fixes

0 of 4235 branches covered (0.0%)

9 of 8528 relevant lines covered (0.11%)

0.0 hits per line

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

0.0
/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());
×
69
  static final VarHandle REFRESHES = fieldVarHandle(MethodHandles.lookup(),
×
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) {
×
85
    this.data = new ConcurrentHashMap<>(builder.getInitialCapacity());
×
86
    this.statsCounter = builder.getStatsCounterSupplier().get();
×
87
    this.removalListener = builder.getRemovalListener(isAsync);
×
88
    this.isRecordingStats = builder.isRecordingStats();
×
89
    this.executor = builder.getExecutor();
×
90
    this.isAsync = isAsync;
×
91
  }
×
92

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

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

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

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

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

119
  @Override
120
  public boolean isPendingEviction(K key) {
121
    return false;
×
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);
×
130

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

252
    BiFunction<K, @Nullable V, @Nullable V> remappingFunction = (key, oldValue) ->
×
253
        requireNonNull(function.apply(key, requireNonNull(oldValue)));
×
254
    for (K key : data.keySet()) {
×
255
      remap(key, remappingFunction, /* hints= */ null, /* computeIfAbsent= */ false);
×
256
    }
×
257
  }
×
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);
×
264

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

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

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

294
    // An optimistic fast path to avoid unnecessary locking
295
    if (!data.containsKey(key)) {
×
296
      return null;
×
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];
×
302
    boolean[] replaced = new boolean[1];
×
303
    V nv = data.computeIfPresent(key, (K k, V value) -> {
×
304
      BiFunction<? super K, ? super V, ? extends @Nullable V> function = statsAware(
×
305
          remappingFunction, /* recordLoad= */ true, /* recordLoadFailure= */ true);
306
      try {
307
        V newValue = function.apply(k, value);
×
308
        replaced[0] = (newValue != null);
×
309
        if (newValue != value) {
×
310
          oldValue[0] = value;
×
311
        }
312

313
        discardRefresh(k);
×
314
        return newValue;
×
315
      } catch (Throwable t) {
×
316
        discardRefresh(k);
×
317
        throw t;
×
318
      }
319
    });
320
    if (replaced[0]) {
×
321
      notifyOnReplace(key, oldValue[0], nv);
×
322
    } else if (oldValue[0] != null) {
×
323
      notifyRemoval(key, oldValue[0], RemovalCause.EXPLICIT);
×
324
    }
325
    return nv;
×
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);
×
333
    return remap(key, statsAware(remappingFunction, recordLoad, recordLoadFailure),
×
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);
×
341
    requireNonNull(value);
×
342

343
    return remap(key, (K k, @Nullable V oldValue) ->
×
344
      (oldValue == null) ? value : statsAware(remappingFunction).apply(oldValue, value),
×
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];
×
363
    boolean[] replaced = new boolean[1];
×
364
    V nv = data.compute(key, (K k, V value) -> {
×
365
      if ((value == null) && !computeIfAbsent) {
×
366
        return null;
×
367
      }
368
      try {
369
        V newValue = remappingFunction.apply(k, value);
×
370
        if ((value == null) && (newValue == null)) {
×
371
          // A stale refresh whose entry is absent leaves a successor's registration intact
372
          if ((hints == null) || !hints.preserveRefresh) {
×
373
            discardRefresh(k);
×
374
          }
375
          return null;
×
376
        }
377

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

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

406
  /* --------------- Concurrent Map --------------- */
407

408
  @Override
409
  public boolean isEmpty() {
410
    return data.isEmpty();
×
411
  }
412

413
  @Override
414
  public int size() {
415
    return data.size();
×
416
  }
417

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

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

432
  @Override
433
  public boolean containsKey(Object key) {
434
    return data.containsKey(key);
×
435
  }
436

437
  @Override
438
  public boolean containsValue(Object value) {
439
    return data.containsValue(value);
×
440
  }
441

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

447
  @Override
448
  public @Nullable V put(K key, V value) {
449
    requireNonNull(value);
×
450

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

464
  @Override
465
  public @Nullable V putIfAbsent(K key, V value) {
466
    requireNonNull(value);
×
467

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

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

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

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

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

505
    return oldValue[0];
×
506
  }
507

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

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

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

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

536
  @Override
537
  public @Nullable V replace(K key, V value) {
538
    requireNonNull(value);
×
539

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

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

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

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

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

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

593
  @Override
594
  public int hashCode() {
595
    return data.hashCode();
×
596
  }
597

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

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

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

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

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

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

638
    @Override
639
    public boolean isEmpty() {
640
      return cache.isEmpty();
×
641
    }
642

643
    @Override
644
    public int size() {
645
      return cache.size();
×
646
    }
647

648
    @Override
649
    public void clear() {
650
      cache.clear();
×
651
    }
×
652

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

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

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

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

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

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

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

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

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

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

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

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

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

756
    @Override
757
    public boolean hasNext() {
758
      return iterator.hasNext();
×
759
    }
760

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

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

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

782
    KeySpliterator(UnboundedLocalCache<K, V> cache) {
783
      this(cache.data.keySet().spliterator());
×
784
    }
×
785

786
    KeySpliterator(Spliterator<K> spliterator) {
×
787
      this.spliterator = requireNonNull(spliterator);
×
788
    }
×
789

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

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

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

808
    @Override
809
    public long estimateSize() {
810
      return spliterator.estimateSize();
×
811
    }
812

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

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

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

827
    @Override
828
    public boolean isEmpty() {
829
      return cache.isEmpty();
×
830
    }
831

832
    @Override
833
    public int size() {
834
      return cache.size();
×
835
    }
836

837
    @Override
838
    public void clear() {
839
      cache.clear();
×
840
    }
×
841

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

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

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

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

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

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

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

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

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

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

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

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

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

949
    @Override
950
    public boolean hasNext() {
951
      return iterator.hasNext();
×
952
    }
953

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

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

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

975
    ValueSpliterator(UnboundedLocalCache<K, V> cache) {
976
      this(cache.data.values().spliterator());
×
977
    }
×
978

979
    ValueSpliterator(Spliterator<V> spliterator) {
×
980
      this.spliterator = requireNonNull(spliterator);
×
981
    }
×
982

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

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

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

1001
    @Override
1002
    public long estimateSize() {
1003
      return spliterator.estimateSize();
×
1004
    }
1005

1006
    @Override
1007
    public int characteristics() {
1008
      return CONCURRENT | NONNULL;
×
1009
    }
1010
  }
1011

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

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

1020
    @Override
1021
    public boolean isEmpty() {
1022
      return cache.isEmpty();
×
1023
    }
1024

1025
    @Override
1026
    public int size() {
1027
      return cache.size();
×
1028
    }
1029

1030
    @Override
1031
    public void clear() {
1032
      cache.clear();
×
1033
    }
×
1034

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

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

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

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

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

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

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

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

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

1128
    @Override
1129
    public boolean hasNext() {
1130
      return iterator.hasNext();
×
1131
    }
1132

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

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

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

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

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

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

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

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

1188
    @Override
1189
    public long estimateSize() {
1190
      return spliterator.estimateSize();
×
1191
    }
1192

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

1199
  /* --------------- Manual Cache --------------- */
1200

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

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

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

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

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

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

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

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

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

1286
  /* --------------- Loading Cache --------------- */
1287

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

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

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

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

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

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

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

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

1331
  /* --------------- Async Cache --------------- */
1332

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

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

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

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

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

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

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

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

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

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

1388
  /* --------------- Async Loading Cache --------------- */
1389

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

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

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

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

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

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

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

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

1432
    Object writeReplace() {
1433
      var proxy = new SerializationProxy<K, V>();
×
1434
      proxy.isRecordingStats = cache.isRecordingStats();
×
1435
      proxy.removalListener = cache.removalListener;
×
1436
      proxy.cacheLoader = cacheLoader;
×
1437
      proxy.async = true;
×
1438
      return proxy;
×
1439
    }
1440
  }
1441
}
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