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

ben-manes / caffeine / #5247

22 Jan 2026 01:50AM UTC coverage: 99.987% (-0.01%) from 100.0%
#5247

push

github

ben-manes
use test sharding to more evenly distribute the work across jobs

3833 of 3838 branches covered (99.87%)

7869 of 7870 relevant lines covered (99.99%)

1.0 hits per line

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

99.81
/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.util.Objects.requireNonNull;
22

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

53
import org.jspecify.annotations.Nullable;
54

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

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

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

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

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

92
  static VarHandle findVarHandle(Class<?> recv, String name, Class<?> type) {
93
    try {
94
      return MethodHandles.lookup().findVarHandle(recv, name, type);
1✔
95
    } catch (ReflectiveOperationException e) {
1✔
96
      throw new ExceptionInInitializerError(e);
1✔
97
    }
98
  }
99

100
  @Override
101
  public boolean isAsync() {
102
    return isAsync;
1✔
103
  }
104

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

110
  @Override
111
  @CanIgnoreReturnValue
112
  public Object referenceKey(K key) {
113
    return key;
1✔
114
  }
115

116
  @Override
117
  public boolean isPendingEviction(K key) {
118
    return false;
1✔
119
  }
120

121
  /* --------------- Cache --------------- */
122

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

128
    if (recordStats) {
1✔
129
      if (value == null) {
1✔
130
        statsCounter.recordMisses(1);
1✔
131
      } else {
132
        statsCounter.recordHits(1);
1✔
133
      }
134
    }
135
    return value;
1✔
136
  }
137

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

144
  @Override
145
  public long estimatedSize() {
146
    return data.mappingCount();
1✔
147
  }
148

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

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

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

174
  @Override
175
  public void cleanUp() {}
1✔
176

177
  @Override
178
  public StatsCounter statsCounter() {
179
    return statsCounter;
1✔
180
  }
181

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

202
  @Override
203
  public boolean isRecordingStats() {
204
    return isRecordingStats;
1✔
205
  }
206

207
  @Override
208
  public Executor executor() {
209
    return executor;
1✔
210
  }
211

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

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

232
  @Override
233
  public Ticker statsTicker() {
234
    return isRecordingStats ? Ticker.systemTicker() : Ticker.disabledTicker();
1✔
235
  }
236

237
  /* --------------- JDK8+ Map extensions --------------- */
238

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

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

249
    BiFunction<K, @Nullable V, @Nullable V> remappingFunction = (key, oldValue) ->
1✔
250
        (oldValue == null) ? null : requireNonNull(function.apply(key, oldValue));
1✔
251
    for (K key : data.keySet()) {
1✔
252
      remap(key, remappingFunction);
1✔
253
    }
1✔
254
  }
1✔
255

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

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

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

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

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

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

305
      replaced[0] = (newValue != null);
1✔
306
      if (newValue != value) {
1✔
307
        oldValue[0] = value;
1✔
308
      }
309

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

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

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

335
    return remap(key, (K k, @Nullable V oldValue) ->
1✔
336
      (oldValue == null) ? value : statsAware(remappingFunction).apply(oldValue, value));
1✔
337
  }
338

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

358
      replaced[0] = (newValue != null);
1✔
359
      if (newValue != value) {
1✔
360
        oldValue[0] = value;
1✔
361
      }
362

363
      discardRefresh(k);
1✔
364
      return newValue;
1✔
365
    });
366
    if (replaced[0]) {
1✔
367
      notifyOnReplace(key, oldValue[0], nv);
1✔
368
    } else if (oldValue[0] != null) {
1✔
369
      notifyRemoval(key, oldValue[0], RemovalCause.EXPLICIT);
1✔
370
    }
371
    return nv;
1✔
372
  }
373

374
  /* --------------- Concurrent Map --------------- */
375

376
  @Override
377
  public boolean isEmpty() {
378
    return data.isEmpty();
1✔
379
  }
380

381
  @Override
382
  public int size() {
383
    return data.size();
1✔
384
  }
385

386
  @Override
387
  @SuppressWarnings("ResultOfMethodCallIgnored")
388
  public void clear() {
389
    var keys = (removalListener == null) ? data.keySet() : List.copyOf(data.keySet());
1✔
390
    for (K key : keys) {
1✔
391
      remove(key);
1✔
392
    }
1✔
393
  }
1✔
394

395
  @Override
396
  public boolean containsKey(Object key) {
397
    return data.containsKey(key);
1✔
398
  }
399

400
  @Override
401
  public boolean containsValue(Object value) {
402
    return data.containsValue(value);
1✔
403
  }
404

405
  @Override
406
  public @Nullable V get(Object key) {
407
    return getIfPresent(key, /* recordStats= */ false);
1✔
408
  }
409

410
  @Override
411
  public @Nullable V put(K key, V value) {
412
    requireNonNull(value);
1✔
413

414
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
415
    @Nullable V[] oldValue = (@Nullable V[]) new Object[1];
1✔
416
    data.compute(key, (K k, V v) -> {
1✔
417
      discardRefresh(k);
1✔
418
      oldValue[0] = v;
1✔
419
      return value;
1✔
420
    });
421
    if (oldValue[0] != null) {
1✔
422
      notifyOnReplace(key, oldValue[0], value);
1✔
423
    }
424
    return oldValue[0];
1✔
425
  }
426

427
  @Override
428
  public @Nullable V putIfAbsent(K key, V value) {
429
    requireNonNull(value);
1✔
430

431
    // An optimistic fast path to avoid unnecessary locking
432
    var v = data.get(key);
1✔
433
    if (v != null) {
1✔
434
      return v;
1✔
435
    }
436

437
    var added = new boolean[1];
1✔
438
    var val = data.computeIfAbsent(key, k -> {
1✔
439
      discardRefresh(k);
1✔
440
      added[0] = true;
1✔
441
      return value;
1✔
442
    });
443
    return added[0] ? null : val;
1✔
444
  }
445

446
  @Override
447
  @SuppressWarnings("ResultOfMethodCallIgnored")
448
  public void putAll(Map<? extends K, ? extends V> map) {
449
    map.forEach(this::put);
1✔
450
  }
1✔
451

452
  @Override
453
  public @Nullable V remove(Object key) {
454
    @SuppressWarnings("unchecked")
455
    var castKey = (K) key;
1✔
456
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
457
    @Nullable V[] oldValue = (V[]) new Object[1];
1✔
458
    data.computeIfPresent(castKey, (k, v) -> {
1✔
459
      discardRefresh(k);
1✔
460
      oldValue[0] = v;
1✔
461
      return null;
1✔
462
    });
463

464
    if (oldValue[0] != null) {
1✔
465
      notifyRemoval(castKey, oldValue[0], RemovalCause.EXPLICIT);
1✔
466
    }
467

468
    return oldValue[0];
1✔
469
  }
470

471
  @Override
472
  public boolean remove(Object key, @Nullable Object value) {
473
    if (value == null) {
1✔
474
      requireNonNull(key);
1✔
475
      return false;
1✔
476
    }
477

478
    @SuppressWarnings("unchecked")
479
    var castKey = (K) key;
1✔
480
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
481
    @Nullable V[] oldValue = (V[]) new Object[1];
1✔
482

483
    data.computeIfPresent(castKey, (k, v) -> {
1✔
484
      if (Objects.equals(v, value)) {
1✔
485
        discardRefresh(k);
1✔
486
        oldValue[0] = v;
1✔
487
        return null;
1✔
488
      }
489
      return v;
1✔
490
    });
491

492
    if (oldValue[0] != null) {
1✔
493
      notifyRemoval(castKey, oldValue[0], RemovalCause.EXPLICIT);
1✔
494
      return true;
1✔
495
    }
496
    return false;
1✔
497
  }
498

499
  @Override
500
  public @Nullable V replace(K key, V value) {
501
    requireNonNull(value);
1✔
502

503
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
504
    @Nullable V[] oldValue = (@Nullable V[]) new Object[1];
1✔
505
    data.computeIfPresent(key, (k, v) -> {
1✔
506
      discardRefresh(k);
1✔
507
      oldValue[0] = v;
1✔
508
      return value;
1✔
509
    });
510

511
    if ((oldValue[0] != null) && (oldValue[0] != value)) {
1✔
512
      notifyRemoval(key, oldValue[0], RemovalCause.REPLACED);
1✔
513
    }
514
    return oldValue[0];
1✔
515
  }
516

517
  @Override
518
  public boolean replace(K key, V oldValue, V newValue) {
519
    return replace(key, oldValue, newValue, /* shouldDiscardRefresh= */ true);
1✔
520
  }
521

522
  @Override
523
  public boolean replace(K key, V oldValue, V newValue, boolean shouldDiscardRefresh) {
524
    requireNonNull(oldValue);
1✔
525
    requireNonNull(newValue);
1✔
526

527
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
528
    @Nullable V[] prev = (V[]) new Object[1];
1✔
529
    data.computeIfPresent(key, (k, v) -> {
1✔
530
      if (Objects.equals(v, oldValue)) {
1✔
531
        if (shouldDiscardRefresh) {
1✔
532
          discardRefresh(k);
1✔
533
        }
534
        prev[0] = v;
1✔
535
        return newValue;
1✔
536
      }
537
      return v;
1✔
538
    });
539

540
    boolean replaced = (prev[0] != null);
1✔
541
    if (replaced && (prev[0] != newValue)) {
1✔
542
      notifyRemoval(key, prev[0], RemovalCause.REPLACED);
1✔
543
    }
544
    return replaced;
1✔
545
  }
546

547
  @Override
548
  @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
549
  public boolean equals(@Nullable Object o) {
550
    return (o == this) || data.equals(o);
1✔
551
  }
552

553
  @Override
554
  public int hashCode() {
555
    return data.hashCode();
1✔
556
  }
557

558
  @Override
559
  public String toString() {
560
    var result = new StringBuilder(50).append('{');
1✔
561
    data.forEach((key, value) -> {
1✔
562
      if (result.length() != 1) {
1✔
563
        result.append(", ");
1✔
564
      }
565
      result.append((key == this) ? "(this Map)" : key)
1✔
566
          .append('=')
1✔
567
          .append((value == this) ? "(this Map)" : value);
1✔
568
    });
1✔
569
    return result.append('}').toString();
1✔
570
  }
571

572
  @Override
573
  public Set<K> keySet() {
574
    Set<K> ks = keySet;
1✔
575
    return (ks == null) ? (keySet = new KeySetView<>(this)) : ks;
1✔
576
  }
577

578
  @Override
579
  public Collection<V> values() {
580
    Collection<V> vs = values;
1✔
581
    return (vs == null) ? (values = new ValuesView<>(this)) : vs;
1✔
582
  }
583

584
  @Override
585
  public Set<Entry<K, V>> entrySet() {
586
    Set<Entry<K, V>> es = entrySet;
1✔
587
    return (es == null) ? (entrySet = new EntrySetView<>(this)) : es;
1✔
588
  }
589

590
  /** An adapter to safely externalize the keys. */
591
  static final class KeySetView<K> extends AbstractSet<K> {
592
    final UnboundedLocalCache<K, ?> cache;
593

594
    KeySetView(UnboundedLocalCache<K, ?> cache) {
1✔
595
      this.cache = requireNonNull(cache);
1✔
596
    }
1✔
597

598
    @Override
599
    public boolean isEmpty() {
600
      return cache.isEmpty();
1✔
601
    }
602

603
    @Override
604
    public int size() {
605
      return cache.size();
1✔
606
    }
607

608
    @Override
609
    public void clear() {
610
      cache.clear();
1✔
611
    }
1✔
612

613
    @Override
614
    @SuppressWarnings("SuspiciousMethodCalls")
615
    public boolean contains(Object o) {
616
      return cache.containsKey(o);
1✔
617
    }
618

619
    @Override
620
    public boolean removeAll(Collection<?> collection) {
621
      requireNonNull(collection);
1✔
622
      @Var boolean modified = false;
1✔
623
      if ((collection instanceof Set<?>) && (collection.size() > size())) {
1✔
624
        for (K key : this) {
1✔
625
          if (collection.contains(key)) {
1✔
626
            modified |= remove(key);
1✔
627
          }
628
        }
1✔
629
      } else {
630
        for (var o : collection) {
1✔
631
          modified |= (o != null) && remove(o);
1✔
632
        }
1✔
633
      }
634
      return modified;
1✔
635
    }
636

637
    @Override
638
    public boolean remove(Object o) {
639
      return (cache.remove(o) != null);
1✔
640
    }
641

642
    @Override
643
    public boolean removeIf(Predicate<? super K> filter) {
644
      requireNonNull(filter);
1✔
645
      @Var boolean modified = false;
1✔
646
      for (K key : this) {
1✔
647
        if (filter.test(key) && remove(key)) {
1✔
648
          modified = true;
1✔
649
        }
650
      }
1✔
651
      return modified;
1✔
652
    }
653

654
    @Override
655
    public boolean retainAll(Collection<?> collection) {
656
      requireNonNull(collection);
1✔
657
      @Var boolean modified = false;
1✔
658
      for (K key : this) {
1✔
659
        if (!collection.contains(key) && remove(key)) {
1✔
660
          modified = true;
1✔
661
        }
662
      }
1✔
663
      return modified;
1✔
664
    }
665

666
    @Override
667
    public void forEach(Consumer<? super K> action) {
668
      cache.data.keySet().forEach(action);
1✔
669
    }
1✔
670

671
    @Override
672
    public Iterator<K> iterator() {
673
      return new KeyIterator<>(cache);
1✔
674
    }
675

676
    @Override
677
    public Spliterator<K> spliterator() {
678
      return new KeySpliterator<>(cache);
1✔
679
    }
680

681
    @Override
682
    public Object[] toArray() {
683
      return cache.data.keySet().toArray();
1✔
684
    }
685

686
    @Override
687
    public <T> T[] toArray(T[] array) {
688
      return cache.data.keySet().toArray(array);
1✔
689
    }
690
  }
691

692
  /** An adapter to safely externalize the key iterator. */
693
  static final class KeyIterator<K> implements Iterator<K> {
694
    final UnboundedLocalCache<K, ?> cache;
695
    final Iterator<K> iterator;
696
    @Nullable K current;
697

698
    KeyIterator(UnboundedLocalCache<K, ?> cache) {
1✔
699
      this.iterator = cache.data.keySet().iterator();
1✔
700
      this.cache = cache;
1✔
701
    }
1✔
702

703
    @Override
704
    public boolean hasNext() {
705
      return iterator.hasNext();
1✔
706
    }
707

708
    @Override
709
    public K next() {
710
      current = iterator.next();
1✔
711
      return current;
1✔
712
    }
713

714
    @Override
715
    @SuppressWarnings("ResultOfMethodCallIgnored")
716
    public void remove() {
717
      if (current == null) {
1✔
718
        throw new IllegalStateException();
1✔
719
      }
720
      cache.remove(current);
1✔
721
      current = null;
1✔
722
    }
1✔
723
  }
724

725
  /** An adapter to safely externalize the key spliterator. */
726
  static final class KeySpliterator<K, V> implements Spliterator<K> {
727
    final Spliterator<K> spliterator;
728

729
    KeySpliterator(UnboundedLocalCache<K, V> cache) {
730
      this(cache.data.keySet().spliterator());
1✔
731
    }
1✔
732

733
    KeySpliterator(Spliterator<K> spliterator) {
1✔
734
      this.spliterator = requireNonNull(spliterator);
1✔
735
    }
1✔
736

737
    @Override
738
    public void forEachRemaining(Consumer<? super K> action) {
739
      requireNonNull(action);
1✔
740
      spliterator.forEachRemaining(action);
1✔
741
    }
1✔
742

743
    @Override
744
    public boolean tryAdvance(Consumer<? super K> action) {
745
      requireNonNull(action);
1✔
746
      return spliterator.tryAdvance(action);
1✔
747
    }
748

749
    @Override
750
    public @Nullable KeySpliterator<K, V> trySplit() {
751
      Spliterator<K> split = spliterator.trySplit();
1✔
752
      return (split == null) ? null : new KeySpliterator<>(split);
1✔
753
    }
754

755
    @Override
756
    public long estimateSize() {
757
      return spliterator.estimateSize();
1✔
758
    }
759

760
    @Override
761
    public int characteristics() {
762
      return DISTINCT | CONCURRENT | NONNULL;
1✔
763
    }
764
  }
765

766
  /** An adapter to safely externalize the values. */
767
  static final class ValuesView<K, V> extends AbstractCollection<V> {
768
    final UnboundedLocalCache<K, V> cache;
769

770
    ValuesView(UnboundedLocalCache<K, V> cache) {
1✔
771
      this.cache = requireNonNull(cache);
1✔
772
    }
1✔
773

774
    @Override
775
    public boolean isEmpty() {
776
      return cache.isEmpty();
1✔
777
    }
778

779
    @Override
780
    public int size() {
781
      return cache.size();
1✔
782
    }
783

784
    @Override
785
    public void clear() {
786
      cache.clear();
1✔
787
    }
1✔
788

789
    @Override
790
    @SuppressWarnings("SuspiciousMethodCalls")
791
    public boolean contains(Object o) {
792
      return cache.containsValue(o);
1✔
793
    }
794

795
    @Override
796
    public boolean removeAll(Collection<?> collection) {
797
      requireNonNull(collection);
1✔
798
      @Var boolean modified = false;
1✔
799
      for (var entry : cache.data.entrySet()) {
1✔
800
        if (collection.contains(entry.getValue())
1✔
801
            && cache.remove(entry.getKey(), entry.getValue())) {
1✔
802
          modified = true;
1✔
803
        }
804
      }
1✔
805
      return modified;
1✔
806
    }
807

808
    @Override
809
    public boolean remove(@Nullable Object o) {
810
      if (o == null) {
1✔
811
        return false;
1✔
812
      }
813
      for (var entry : cache.data.entrySet()) {
1✔
814
        if (o.equals(entry.getValue()) && cache.remove(entry.getKey(), entry.getValue())) {
1✔
815
          return true;
1✔
816
        }
817
      }
1✔
818
      return false;
1✔
819
    }
820

821
    @Override
822
    public boolean removeIf(Predicate<? super V> filter) {
823
      requireNonNull(filter);
1✔
824
      @Var boolean removed = false;
1✔
825
      for (var entry : cache.data.entrySet()) {
1✔
826
        if (filter.test(entry.getValue())) {
1✔
827
          removed |= cache.remove(entry.getKey(), entry.getValue());
1✔
828
        }
829
      }
1✔
830
      return removed;
1✔
831
    }
832

833
    @Override
834
    public boolean retainAll(Collection<?> collection) {
835
      requireNonNull(collection);
1✔
836
      @Var boolean modified = false;
1✔
837
      for (var entry : cache.data.entrySet()) {
1✔
838
        if (!collection.contains(entry.getValue())
1✔
839
            && cache.remove(entry.getKey(), entry.getValue())) {
1✔
840
          modified = true;
1✔
841
        }
842
      }
1✔
843
      return modified;
1✔
844
    }
845

846
    @Override
847
    public void forEach(Consumer<? super V> action) {
848
      cache.data.values().forEach(action);
1✔
849
    }
1✔
850

851
    @Override
852
    public Iterator<V> iterator() {
853
      return new ValueIterator<>(cache);
1✔
854
    }
855

856
    @Override
857
    public Spliterator<V> spliterator() {
858
      return new ValueSpliterator<>(cache);
1✔
859
    }
860

861
    @Override
862
    public Object[] toArray() {
863
      return cache.data.values().toArray();
1✔
864
    }
865

866
    @Override
867
    public <T> T[] toArray(T[] array) {
868
      return cache.data.values().toArray(array);
1✔
869
    }
870
  }
871

872
  /** An adapter to safely externalize the value iterator. */
873
  static final class ValueIterator<K, V> implements Iterator<V> {
874
    final UnboundedLocalCache<K, V> cache;
875
    final Iterator<Entry<K, V>> iterator;
876
    @Nullable Entry<K, V> entry;
877

878
    ValueIterator(UnboundedLocalCache<K, V> cache) {
1✔
879
      this.iterator = cache.data.entrySet().iterator();
1✔
880
      this.cache = cache;
1✔
881
    }
1✔
882

883
    @Override
884
    public boolean hasNext() {
885
      return iterator.hasNext();
1✔
886
    }
887

888
    @Override
889
    public V next() {
890
      entry = iterator.next();
1✔
891
      return entry.getValue();
1✔
892
    }
893

894
    @Override
895
    @SuppressWarnings("ResultOfMethodCallIgnored")
896
    public void remove() {
897
      if (entry == null) {
1✔
898
        throw new IllegalStateException();
1✔
899
      }
900
      cache.remove(entry.getKey());
1✔
901
      entry = null;
1✔
902
    }
1✔
903
  }
904

905
  /** An adapter to safely externalize the value spliterator. */
906
  static final class ValueSpliterator<K, V> implements Spliterator<V> {
907
    final Spliterator<V> spliterator;
908

909
    ValueSpliterator(UnboundedLocalCache<K, V> cache) {
910
      this(cache.data.values().spliterator());
1✔
911
    }
1✔
912

913
    ValueSpliterator(Spliterator<V> spliterator) {
1✔
914
      this.spliterator = requireNonNull(spliterator);
1✔
915
    }
1✔
916

917
    @Override
918
    public void forEachRemaining(Consumer<? super V> action) {
919
      requireNonNull(action);
1✔
920
      spliterator.forEachRemaining(action);
1✔
921
    }
1✔
922

923
    @Override
924
    public boolean tryAdvance(Consumer<? super V> action) {
925
      requireNonNull(action);
1✔
926
      return spliterator.tryAdvance(action);
1✔
927
    }
928

929
    @Override
930
    public @Nullable ValueSpliterator<K, V> trySplit() {
931
      Spliterator<V> split = spliterator.trySplit();
1✔
932
      return (split == null) ? null : new ValueSpliterator<>(split);
1✔
933
    }
934

935
    @Override
936
    public long estimateSize() {
937
      return spliterator.estimateSize();
1✔
938
    }
939

940
    @Override
941
    public int characteristics() {
942
      return CONCURRENT | NONNULL;
1✔
943
    }
944
  }
945

946
  /** An adapter to safely externalize the entries. */
947
  static final class EntrySetView<K, V> extends AbstractSet<Entry<K, V>> {
948
    final UnboundedLocalCache<K, V> cache;
949

950
    EntrySetView(UnboundedLocalCache<K, V> cache) {
1✔
951
      this.cache = requireNonNull(cache);
1✔
952
    }
1✔
953

954
    @Override
955
    public boolean isEmpty() {
956
      return cache.isEmpty();
1✔
957
    }
958

959
    @Override
960
    public int size() {
961
      return cache.size();
1✔
962
    }
963

964
    @Override
965
    public void clear() {
966
      cache.clear();
1✔
967
    }
1✔
968

969
    @Override
970
    @SuppressWarnings("SuspiciousMethodCalls")
971
    public boolean contains(Object o) {
972
      if (!(o instanceof Entry<?, ?>)) {
1✔
973
        return false;
1✔
974
      }
975
      var entry = (Entry<?, ?>) o;
1✔
976
      var key = entry.getKey();
1✔
977
      var value = entry.getValue();
1✔
978
      if ((key == null) || (value == null)) {
1✔
979
        return false;
1✔
980
      }
981
      V cachedValue = cache.get(key);
1✔
982
      return (cachedValue != null) && cachedValue.equals(value);
1✔
983
    }
984

985
    @Override
986
    public boolean removeAll(Collection<?> collection) {
987
      requireNonNull(collection);
1✔
988
      @Var boolean modified = false;
1✔
989
      if ((collection instanceof Set<?>) && (collection.size() > size())) {
1✔
990
        for (var entry : this) {
1✔
991
          if (collection.contains(entry)) {
1✔
992
            modified |= remove(entry);
1✔
993
          }
994
        }
1✔
995
      } else {
996
        for (var o : collection) {
1✔
997
          modified |= remove(o);
1✔
998
        }
1✔
999
      }
1000
      return modified;
1✔
1001
    }
1002

1003
    @Override
1004
    @SuppressWarnings("SuspiciousMethodCalls")
1005
    public boolean remove(Object o) {
1006
      if (!(o instanceof Entry<?, ?>)) {
1✔
1007
        return false;
1✔
1008
      }
1009
      var entry = (Entry<?, ?>) o;
1✔
1010
      var key = entry.getKey();
1✔
1011
      return (key != null) && cache.remove(key, entry.getValue());
1✔
1012
    }
1013

1014
    @Override
1015
    public boolean removeIf(Predicate<? super Entry<K, V>> filter) {
1016
      requireNonNull(filter);
1✔
1017
      @Var boolean removed = false;
1✔
1018
      for (var entry : cache.data.entrySet()) {
1✔
1019
        if (filter.test(entry)) {
1✔
1020
          removed |= cache.remove(entry.getKey(), entry.getValue());
1✔
1021
        }
1022
      }
1✔
1023
      return removed;
1✔
1024
    }
1025

1026
    @Override
1027
    public boolean retainAll(Collection<?> collection) {
1028
      requireNonNull(collection);
1✔
1029
      @Var boolean modified = false;
1✔
1030
      for (var entry : this) {
1✔
1031
        if (!collection.contains(entry) && remove(entry)) {
1✔
1032
          modified = true;
1✔
1033
        }
1034
      }
1✔
1035
      return modified;
1✔
1036
    }
1037

1038
    @Override
1039
    public Iterator<Entry<K, V>> iterator() {
1040
      return new EntryIterator<>(cache);
1✔
1041
    }
1042

1043
    @Override
1044
    public Spliterator<Entry<K, V>> spliterator() {
1045
      return new EntrySpliterator<>(cache);
1✔
1046
    }
1047
  }
1048

1049
  /** An adapter to safely externalize the entry iterator. */
1050
  static final class EntryIterator<K, V> implements Iterator<Entry<K, V>> {
1051
    final UnboundedLocalCache<K, V> cache;
1052
    final Iterator<Entry<K, V>> iterator;
1053
    @Nullable Entry<K, V> entry;
1054

1055
    EntryIterator(UnboundedLocalCache<K, V> cache) {
1✔
1056
      this.iterator = cache.data.entrySet().iterator();
1✔
1057
      this.cache = cache;
1✔
1058
    }
1✔
1059

1060
    @Override
1061
    public boolean hasNext() {
1062
      return iterator.hasNext();
1✔
1063
    }
1064

1065
    @Override
1066
    public Entry<K, V> next() {
1067
      entry = iterator.next();
1✔
1068
      return new WriteThroughEntry<>(cache, entry.getKey(), entry.getValue());
1✔
1069
    }
1070

1071
    @Override
1072
    @SuppressWarnings("ResultOfMethodCallIgnored")
1073
    public void remove() {
1074
      if (entry == null) {
1✔
1075
        throw new IllegalStateException();
1✔
1076
      }
1077
      cache.remove(entry.getKey());
1✔
1078
      entry = null;
1✔
1079
    }
1✔
1080
  }
1081

1082
  /** An adapter to safely externalize the entry spliterator. */
1083
  static final class EntrySpliterator<K, V> implements Spliterator<Entry<K, V>> {
1084
    final Spliterator<Entry<K, V>> spliterator;
1085
    final UnboundedLocalCache<K, V> cache;
1086

1087
    EntrySpliterator(UnboundedLocalCache<K, V> cache) {
1088
      this(cache, cache.data.entrySet().spliterator());
1✔
1089
    }
1✔
1090

1091
    EntrySpliterator(UnboundedLocalCache<K, V> cache, Spliterator<Entry<K, V>> spliterator) {
1✔
1092
      this.spliterator = requireNonNull(spliterator);
1✔
1093
      this.cache = requireNonNull(cache);
1✔
1094
    }
1✔
1095

1096
    @Override
1097
    public void forEachRemaining(Consumer<? super Entry<K, V>> action) {
1098
      requireNonNull(action);
1✔
1099
      spliterator.forEachRemaining(entry -> {
1✔
1100
        var e = new WriteThroughEntry<>(cache, entry.getKey(), entry.getValue());
1✔
1101
        action.accept(e);
1✔
1102
      });
1✔
1103
    }
1✔
1104

1105
    @Override
1106
    public boolean tryAdvance(Consumer<? super Entry<K, V>> action) {
1107
      requireNonNull(action);
1✔
1108
      return spliterator.tryAdvance(entry -> {
1✔
1109
        var e = new WriteThroughEntry<>(cache, entry.getKey(), entry.getValue());
1✔
1110
        action.accept(e);
1✔
1111
      });
1✔
1112
    }
1113

1114
    @Override
1115
    public @Nullable EntrySpliterator<K, V> trySplit() {
1116
      Spliterator<Entry<K, V>> split = spliterator.trySplit();
1✔
1117
      return (split == null) ? null : new EntrySpliterator<>(cache, split);
1✔
1118
    }
1119

1120
    @Override
1121
    public long estimateSize() {
1122
      return spliterator.estimateSize();
1✔
1123
    }
1124

1125
    @Override
1126
    public int characteristics() {
1127
      return DISTINCT | CONCURRENT | NONNULL;
1✔
1128
    }
1129
  }
1130

1131
  /* --------------- Manual Cache --------------- */
1132

1133
  static class UnboundedLocalManualCache<K, V> implements LocalManualCache<K, V>, Serializable {
1134
    private static final long serialVersionUID = 1;
1135

1136
    final UnboundedLocalCache<K, V> cache;
1137
    @Nullable Policy<K, V> policy;
1138

1139
    UnboundedLocalManualCache(Caffeine<K, V> builder) {
1✔
1140
      cache = new UnboundedLocalCache<>(builder, /* isAsync= */ false);
1✔
1141
    }
1✔
1142

1143
    @Override
1144
    public final UnboundedLocalCache<K, V> cache() {
1145
      return cache;
1✔
1146
    }
1147

1148
    @Override
1149
    public final Policy<K, V> policy() {
1150
      if (policy == null) {
1✔
1151
        Function<@Nullable V, @Nullable V> identity = v -> v;
1✔
1152
        policy = new UnboundedPolicy<>(cache, identity);
1✔
1153
      }
1154
      return policy;
1✔
1155
    }
1156

1157
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1158
      throw new InvalidObjectException("Proxy required");
1✔
1159
    }
1160

1161
    Object writeReplace() {
1162
      var proxy = new SerializationProxy<K, V>();
1✔
1163
      proxy.isRecordingStats = cache.isRecordingStats;
1✔
1164
      proxy.removalListener = cache.removalListener;
1✔
1165
      return proxy;
1✔
1166
    }
1167
  }
1168

1169
  /** An eviction policy that supports no bounding. */
1170
  static final class UnboundedPolicy<K, V> implements Policy<K, V> {
1171
    final Function<@Nullable V, @Nullable V> transformer;
1172
    final UnboundedLocalCache<K, V> cache;
1173

1174
    UnboundedPolicy(UnboundedLocalCache<K, V> cache,
1175
        Function<@Nullable V, @Nullable V> transformer) {
1✔
1176
      this.transformer = transformer;
1✔
1177
      this.cache = cache;
1✔
1178
    }
1✔
1179
    @Override public boolean isRecordingStats() {
1180
      return cache.isRecordingStats;
1✔
1181
    }
1182
    @Override public @Nullable V getIfPresentQuietly(K key) {
1183
      return transformer.apply(cache.data.get(key));
1✔
1184
    }
1185
    @Override public @Nullable CacheEntry<K, V> getEntryIfPresentQuietly(K key) {
1186
      V value = transformer.apply(cache.data.get(key));
1✔
1187
      return (value == null) ? null : SnapshotEntry.forEntry(key, value);
1✔
1188
    }
1189
    @SuppressWarnings("Java9CollectionFactory")
1190
    @Override public Map<K, CompletableFuture<V>> refreshes() {
1191
      var refreshes = cache.refreshes;
1✔
1192
      if ((refreshes == null) || refreshes.isEmpty()) {
1✔
1193
        @SuppressWarnings({"ImmutableMapOf", "RedundantUnmodifiable"})
1194
        Map<K, CompletableFuture<V>> emptyMap = Collections.unmodifiableMap(Collections.emptyMap());
1✔
1195
        return emptyMap;
1✔
1196
      }
1197
      @SuppressWarnings("unchecked")
1198
      var castedRefreshes = (Map<K, CompletableFuture<V>>) (Object) refreshes;
1✔
1199
      return Collections.unmodifiableMap(new HashMap<>(castedRefreshes));
1✔
1200
    }
1201
    @Override public Optional<Eviction<K, V>> eviction() {
1202
      return Optional.empty();
1✔
1203
    }
1204
    @Override public Optional<FixedExpiration<K, V>> expireAfterAccess() {
1205
      return Optional.empty();
1✔
1206
    }
1207
    @Override public Optional<FixedExpiration<K, V>> expireAfterWrite() {
1208
      return Optional.empty();
1✔
1209
    }
1210
    @Override public Optional<VarExpiration<K, V>> expireVariably() {
1211
      return Optional.empty();
1✔
1212
    }
1213
    @Override public Optional<FixedRefresh<K, V>> refreshAfterWrite() {
1214
      return Optional.empty();
1✔
1215
    }
1216
  }
1217

1218
  /* --------------- Loading Cache --------------- */
1219

1220
  static final class UnboundedLocalLoadingCache<K, V> extends UnboundedLocalManualCache<K, V>
1221
      implements LocalLoadingCache<K, V> {
1222
    private static final long serialVersionUID = 1;
1223

1224
    final Function<K, @Nullable V> mappingFunction;
1225
    final CacheLoader<? super K, V> cacheLoader;
1226
    final @Nullable Function<Set<? extends K>, Map<K, V>> bulkMappingFunction;
1227

1228
    UnboundedLocalLoadingCache(Caffeine<K, V> builder, CacheLoader<? super K, V> cacheLoader) {
1229
      super(builder);
1✔
1230
      this.cacheLoader = cacheLoader;
1✔
1231
      this.mappingFunction = newMappingFunction(cacheLoader);
1✔
1232
      this.bulkMappingFunction = newBulkMappingFunction(cacheLoader);
1✔
1233
    }
1✔
1234

1235
    @Override
1236
    public AsyncCacheLoader<? super K, V> cacheLoader() {
1237
      return cacheLoader;
1✔
1238
    }
1239

1240
    @Override
1241
    public Function<K, @Nullable V> mappingFunction() {
1242
      return mappingFunction;
1✔
1243
    }
1244

1245
    @Override
1246
    public @Nullable Function<Set<? extends K>, Map<K, V>>  bulkMappingFunction() {
1247
      return bulkMappingFunction;
1✔
1248
    }
1249

1250
    @Override
1251
    Object writeReplace() {
1252
      @SuppressWarnings("unchecked")
1253
      var proxy = (SerializationProxy<K, V>) super.writeReplace();
1✔
1254
      proxy.cacheLoader = cacheLoader;
1✔
1255
      return proxy;
1✔
1256
    }
1257

1258
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1259
      throw new InvalidObjectException("Proxy required");
1✔
1260
    }
1261
  }
1262

1263
  /* --------------- Async Cache --------------- */
1264

1265
  static final class UnboundedLocalAsyncCache<K, V> implements LocalAsyncCache<K, V>, Serializable {
1266
    private static final long serialVersionUID = 1;
1267

1268
    final UnboundedLocalCache<K, CompletableFuture<V>> cache;
1269

1270
    @Nullable ConcurrentMap<K, CompletableFuture<V>> mapView;
1271
    @Nullable CacheView<K, V> cacheView;
1272
    @Nullable Policy<K, V> policy;
1273

1274
    @SuppressWarnings("unchecked")
1275
    UnboundedLocalAsyncCache(Caffeine<K, V> builder) {
1✔
1276
      cache = new UnboundedLocalCache<>(
1✔
1277
          (Caffeine<K, CompletableFuture<V>>) builder, /* isAsync= */ true);
1278
    }
1✔
1279

1280
    @Override
1281
    public UnboundedLocalCache<K, CompletableFuture<V>> cache() {
1282
      return cache;
1✔
1283
    }
1284

1285
    @Override
1286
    public ConcurrentMap<K, CompletableFuture<V>> asMap() {
1287
      return (mapView == null) ? (mapView = new AsyncAsMapView<>(this)) : mapView;
1✔
1288
    }
1289

1290
    @Override
1291
    public Cache<K, V> synchronous() {
1292
      return (cacheView == null) ? (cacheView = new CacheView<>(this)) : cacheView;
1✔
1293
    }
1294

1295
    @Override
1296
    public Policy<K, V> policy() {
1297
      @SuppressWarnings("unchecked")
1298
      var castCache = (UnboundedLocalCache<K, V>) cache;
1✔
1299
      Function<CompletableFuture<V>, @Nullable V> transformer = Async::getIfReady;
1✔
1300
      @SuppressWarnings("unchecked")
1301
      var castTransformer = (Function<@Nullable V, @Nullable V>) transformer;
1✔
1302
      return (policy == null)
1✔
1303
          ? (policy = new UnboundedPolicy<>(castCache, castTransformer))
1✔
1304
          : policy;
1✔
1305
    }
1306

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

1311
    Object writeReplace() {
1312
      var proxy = new SerializationProxy<K, V>();
1✔
1313
      proxy.isRecordingStats = cache.isRecordingStats;
1✔
1314
      proxy.removalListener = cache.removalListener;
1✔
1315
      proxy.async = true;
1✔
1316
      return proxy;
1✔
1317
    }
1318
  }
1319

1320
  /* --------------- Async Loading Cache --------------- */
1321

1322
  static final class UnboundedLocalAsyncLoadingCache<K, V>
1323
      extends LocalAsyncLoadingCache<K, V> implements Serializable {
1324
    private static final long serialVersionUID = 1;
1325

1326
    final UnboundedLocalCache<K, CompletableFuture<V>> cache;
1327

1328
    @Nullable ConcurrentMap<K, CompletableFuture<V>> mapView;
1329
    @Nullable Policy<K, V> policy;
1330

1331
    @SuppressWarnings("unchecked")
1332
    UnboundedLocalAsyncLoadingCache(Caffeine<K, V> builder, AsyncCacheLoader<? super K, V> loader) {
1333
      super(loader);
1✔
1334
      cache = new UnboundedLocalCache<>(
1✔
1335
          (Caffeine<K, CompletableFuture<V>>) builder, /* isAsync= */ true);
1336
    }
1✔
1337

1338
    @Override
1339
    public LocalCache<K, CompletableFuture<V>> cache() {
1340
      return cache;
1✔
1341
    }
1342

1343
    @Override
1344
    public ConcurrentMap<K, CompletableFuture<V>> asMap() {
1345
      return (mapView == null) ? (mapView = new AsyncAsMapView<>(this)) : mapView;
1✔
1346
    }
1347

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

1360
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1361
      throw new InvalidObjectException("Proxy required");
1✔
1362
    }
1363

1364
    Object writeReplace() {
1365
      var proxy = new SerializationProxy<K, V>();
1✔
1366
      proxy.isRecordingStats = cache.isRecordingStats();
1✔
1367
      proxy.removalListener = cache.removalListener;
1✔
1368
      proxy.cacheLoader = cacheLoader;
1✔
1369
      proxy.async = true;
1✔
1370
      return proxy;
1✔
1371
    }
1372
  }
1373
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc