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

ben-manes / caffeine / #5834

20 Oct 2026 06:36AM UTC coverage: 99.979% (-0.01%) from 99.989%
#5834

push

github

ben-manes
join an in-flight refresh of an expired entry

A refresh of an expired or collected entry replaced any registration,
so while another refresh's loader was still running, each caller
replaced the last and loaded again: 8 refreshes made 8 loads and kept
only the last value. Such a refresh now joins the placeholder and
takes the linearization point of the refresh it joined, so a reload
whose entry expired during it is discarded at commit as it is when the
entry expires after the join. This also rejects a loader refreshing its
own key without a separate owner check.

4872 of 4882 branches covered (99.8%)

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

2 existing lines in 2 files now uncovered.

9347 of 9349 relevant lines covered (99.98%)

1.0 hits per line

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

99.83
/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.LocalCache.castNonNull;
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.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 = fieldVarHandle(MethodHandles.lookup(),
1 ✔
69
      "refreshes", VarHandle.class, UnboundedLocalCache.class, ConcurrentMap.class);
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
  @Override
93
  public boolean isAsync() {
94
    return isAsync;
1 ✔
95
  }
96

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

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

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

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

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

123
  /* --------------- Cache --------------- */
124

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

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

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

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

151
  @Override
152
  public int getAllPresent(Map<K, @Nullable V> result, boolean retainAbsent) {
153
    @Var int absent = 0;
1 ✔
154
    int uniqueKeys = result.size();
1 ✔
155
    for (var iter = result.entrySet().iterator(); iter.hasNext();) {
1 ✔
156
      Map.Entry<K, @Nullable V> entry = iter.next();
1 ✔
157
      V value = data.get(entry.getKey());
1 ✔
158
      if (value != null) {
1 ✔
159
        entry.setValue(value);
1 ✔
160
        continue;
1 ✔
161
      }
162
      absent++;
1 ✔
163
      if (!retainAbsent) {
1 ✔
164
        iter.remove();
1 ✔
165
      }
166
    }
1 ✔
167
    statsCounter.recordHits(uniqueKeys - absent);
1 ✔
168
    statsCounter.recordMisses(absent);
1 ✔
169
    return absent;
1 ✔
170
  }
171

172
  @Override
173
  public void cleanUp() {}
1 ✔
174

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

180
  @Override
181
  @SuppressWarnings("PMD.AvoidInstanceofChecksInCatchClause")
182
  public void notifyRemoval(@Nullable K key, @Nullable V value, RemovalCause cause) {
183
    var removalListener = removalListener();
1 ✔
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
        if (t instanceof InterruptedException) {
1 ✔
192
          Thread.currentThread().interrupt();
1 ✔
193
        }
194
        logger.log(Level.WARNING, "Exception thrown by removal listener", t);
1 ✔
195
      }
1 ✔
196
    };
1 ✔
197
    try {
198
      executor.execute(task);
1 ✔
199
    } catch (Throwable t) {
1 ✔
200
      logger.log(Level.ERROR, "Exception thrown when submitting removal listener", t);
1 ✔
201
      task.run();
1 ✔
202
    }
1 ✔
203
  }
1 ✔
204

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

403
  /* --------------- Concurrent Map --------------- */
404

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

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

415
  @Override
416
  @SuppressWarnings("ResultOfMethodCallIgnored")
417
  public void clear() {
418
    var pending = refreshes;
1 ✔
419
    if (pending != null) {
1 ✔
420
      pending.clear();
1 ✔
421
    }
422

423
    var keys = (removalListener == null) ? data.keySet() : List.copyOf(data.keySet());
1 ✔
424
    for (K key : keys) {
1 ✔
425
      remove(key);
1 ✔
426
    }
1 ✔
427
  }
1 ✔
428

429
  @Override
430
  public boolean containsKey(@Nullable Object key) {
431
    requireNonNull(key);
1 ✔
432
    return data.containsKey(key);
1 ✔
433
  }
434

435
  @Override
436
  public boolean containsValue(@Nullable Object value) {
437
    requireNonNull(value);
1 ✔
438
    return data.containsValue(value);
1 ✔
439
  }
440

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

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

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

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

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

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

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

489
  @Override
490
  public @Nullable V remove(@Nullable Object key) {
491
    requireNonNull(key);
1 ✔
492

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

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

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

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

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

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

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

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

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

550
    notifyOnReplace(key, oldValue[0], value);
1 ✔
551
    return oldValue[0];
1 ✔
552
  }
553

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

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

573
    notifyOnReplace(key, prev[0], newValue);
1 ✔
574
    return (prev[0] != null);
1 ✔
575
  }
576

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

590
  @Override
591
  public int hashCode() {
592
    return data.hashCode();
1 ✔
593
  }
594

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

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

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

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

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

631
    KeySetView(UnboundedLocalCache<K, ?> cache) {
1 ✔
632
      this.cache = requireNonNull(cache);
1 ✔
633
    }
1 ✔
634

635
    @Override
636
    public boolean isEmpty() {
637
      return cache.isEmpty();
1 ✔
638
    }
639

640
    @Override
641
    public int size() {
642
      return cache.size();
1 ✔
643
    }
644

645
    @Override
646
    public void clear() {
647
      cache.clear();
1 ✔
648
    }
1 ✔
649

650
    @Override
651
    @SuppressWarnings("SuspiciousMethodCalls")
652
    public boolean contains(@Nullable Object o) {
653
      return cache.containsKey(o);
1 ✔
654
    }
655

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

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

687
    @Override
688
    public boolean remove(@Nullable Object o) {
689
      return (cache.remove(o) != null);
1 ✔
690
    }
691

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

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

716
    @Override
717
    public void forEach(Consumer<? super K> action) {
718
      cache.data.keySet().forEach(action);
1 ✔
719
    }
1 ✔
720

721
    @Override
722
    public Iterator<K> iterator() {
723
      return new KeyIterator<>(cache);
1 ✔
724
    }
725

726
    @Override
727
    public Spliterator<K> spliterator() {
728
      return new KeySpliterator<>(cache);
1 ✔
729
    }
730

731
    @Override
732
    public @Nullable Object[] toArray() {
733
      return cache.data.keySet().toArray();
1 ✔
734
    }
735

736
    @Override
737
    public <T extends @Nullable Object> T[] toArray(T[] array) {
738
      return cache.data.keySet().toArray(array);
1 ✔
739
    }
740
  }
741

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

748
    KeyIterator(UnboundedLocalCache<K, ?> cache) {
1 ✔
749
      this.iterator = cache.data.keySet().iterator();
1 ✔
750
      this.cache = cache;
1 ✔
751
    }
1 ✔
752

753
    @Override
754
    public boolean hasNext() {
755
      return iterator.hasNext();
1 ✔
756
    }
757

758
    @Override
759
    public K next() {
760
      current = iterator.next();
1 ✔
761
      return current;
1 ✔
762
    }
763

764
    @Override
765
    @SuppressWarnings("ResultOfMethodCallIgnored")
766
    public void remove() {
767
      if (current == null) {
1 ✔
768
        throw new IllegalStateException();
1 ✔
769
      }
770
      cache.remove(current);
1 ✔
771
      current = null;
1 ✔
772
    }
1 ✔
773
  }
774

775
  /** An adapter to safely externalize the key spliterator. */
776
  static final class KeySpliterator<K> implements Spliterator<K> {
777
    final Spliterator<K> spliterator;
778

779
    KeySpliterator(UnboundedLocalCache<K, ?> cache) {
780
      this(cache.data.keySet().spliterator());
1 ✔
781
    }
1 ✔
782

783
    KeySpliterator(Spliterator<K> spliterator) {
1 ✔
784
      this.spliterator = requireNonNull(spliterator);
1 ✔
785
    }
1 ✔
786

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

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

799
    @Override
800
    public @Nullable KeySpliterator<K> trySplit() {
801
      Spliterator<K> split = spliterator.trySplit();
1 ✔
802
      return (split == null) ? null : new KeySpliterator<>(split);
1 ✔
803
    }
804

805
    @Override
806
    public long estimateSize() {
807
      return spliterator.estimateSize();
1 ✔
808
    }
809

810
    @Override
811
    public int characteristics() {
812
      return DISTINCT | CONCURRENT | NONNULL;
1 ✔
813
    }
814
  }
815

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

820
    ValuesView(UnboundedLocalCache<K, V> cache) {
1 ✔
821
      this.cache = requireNonNull(cache);
1 ✔
822
    }
1 ✔
823

824
    @Override
825
    public boolean isEmpty() {
826
      return cache.isEmpty();
1 ✔
827
    }
828

829
    @Override
830
    public int size() {
831
      return cache.size();
1 ✔
832
    }
833

834
    @Override
835
    public void clear() {
836
      cache.clear();
1 ✔
837
    }
1 ✔
838

839
    @Override
840
    @SuppressWarnings("SuspiciousMethodCalls")
841
    public boolean contains(@Nullable Object o) {
842
      return cache.containsValue(o);
1 ✔
843
    }
844

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

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

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

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

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

909
    @Override
910
    public void forEach(Consumer<? super V> action) {
911
      cache.data.values().forEach(action);
1 ✔
912
    }
1 ✔
913

914
    @Override
915
    public Iterator<V> iterator() {
916
      return new ValueIterator<>(cache);
1 ✔
917
    }
918

919
    @Override
920
    public Spliterator<V> spliterator() {
921
      return new ValueSpliterator<>(cache);
1 ✔
922
    }
923

924
    @Override
925
    public @Nullable Object[] toArray() {
926
      return cache.data.values().toArray();
1 ✔
927
    }
928

929
    @Override
930
    public <T extends @Nullable Object> T[] toArray(T[] array) {
931
      return cache.data.values().toArray(array);
1 ✔
932
    }
933
  }
934

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

941
    ValueIterator(UnboundedLocalCache<K, V> cache) {
1 ✔
942
      this.iterator = cache.data.entrySet().iterator();
1 ✔
943
      this.cache = cache;
1 ✔
944
    }
1 ✔
945

946
    @Override
947
    public boolean hasNext() {
948
      return iterator.hasNext();
1 ✔
949
    }
950

951
    @Override
952
    public V next() {
953
      entry = iterator.next();
1 ✔
954
      return entry.getValue();
1 ✔
955
    }
956

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

968
  /** An adapter to safely externalize the value spliterator. */
969
  static final class ValueSpliterator<V> implements Spliterator<V> {
970
    final Spliterator<V> spliterator;
971

972
    ValueSpliterator(UnboundedLocalCache<?, V> cache) {
973
      this(cache.data.values().spliterator());
1 ✔
974
    }
1 ✔
975

976
    ValueSpliterator(Spliterator<V> spliterator) {
1 ✔
977
      this.spliterator = requireNonNull(spliterator);
1 ✔
978
    }
1 ✔
979

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

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

992
    @Override
993
    public @Nullable ValueSpliterator<V> trySplit() {
994
      Spliterator<V> split = spliterator.trySplit();
1 ✔
995
      return (split == null) ? null : new ValueSpliterator<>(split);
1 ✔
996
    }
997

998
    @Override
999
    public long estimateSize() {
1000
      return spliterator.estimateSize();
1 ✔
1001
    }
1002

1003
    @Override
1004
    public int characteristics() {
1005
      return CONCURRENT | NONNULL;
1 ✔
1006
    }
1007
  }
1008

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

1013
    EntrySetView(UnboundedLocalCache<K, V> cache) {
1 ✔
1014
      this.cache = requireNonNull(cache);
1 ✔
1015
    }
1 ✔
1016

1017
    @Override
1018
    public boolean isEmpty() {
1019
      return cache.isEmpty();
1 ✔
1020
    }
1021

1022
    @Override
1023
    public int size() {
1024
      return cache.size();
1 ✔
1025
    }
1026

1027
    @Override
1028
    public void clear() {
1029
      cache.clear();
1 ✔
1030
    }
1 ✔
1031

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

1048
    @Override
1049
    public boolean containsAll(Collection<?> collection) {
1050
      requireNonNull(collection);
1 ✔
1051
      if (collection != this) {
1 ✔
1052
        for (Object o : collection) {
1 ✔
1053
          if ((o == null) || !contains(o)) {
1 ✔
1054
            return false;
1 ✔
1055
          }
1056
        }
1 ✔
1057
      }
1058
      return true;
1 ✔
1059
    }
1060

1061
    @Override
1062
    public boolean removeAll(Collection<?> collection) {
1063
      requireNonNull(collection);
1 ✔
1064
      @Var boolean modified = false;
1 ✔
1065
      if ((collection instanceof Set<?>) && (collection.size() > size())) {
1 ✔
1066
        for (var entry : this) {
1 ✔
1067
          if (collection.contains(entry)) {
1 ✔
1068
            modified |= remove(entry);
1 ✔
1069
          }
1070
        }
1 ✔
1071
      } else {
1072
        for (var o : collection) {
1 ✔
1073
          modified |= remove(o);
1 ✔
1074
        }
1 ✔
1075
      }
1076
      return modified;
1 ✔
1077
    }
1078

1079
    @Override
1080
    @SuppressWarnings("SuspiciousMethodCalls")
1081
    public boolean remove(@Nullable Object o) {
1082
      if (!(o instanceof Entry<?, ?>)) {
1 ✔
1083
        return false;
1 ✔
1084
      }
1085
      var entry = (Entry<?, ?>) o;
1 ✔
1086
      var key = entry.getKey();
1 ✔
1087
      return (key != null) && cache.remove(key, entry.getValue());
1 ✔
1088
    }
1089

1090
    @Override
1091
    public boolean removeIf(Predicate<? super Entry<K, V>> filter) {
1092
      requireNonNull(filter);
1 ✔
1093
      @Var boolean removed = false;
1 ✔
1094
      for (var entry : cache.data.entrySet()) {
1 ✔
1095
        var key = entry.getKey();
1 ✔
1096
        var value = entry.getValue();
1 ✔
1097
        if (filter.test(Map.entry(key, value))) {
1 ✔
1098
          removed |= cache.remove(key, value);
1 ✔
1099
        }
1100
      }
1 ✔
1101
      return removed;
1 ✔
1102
    }
1103

1104
    @Override
1105
    public boolean retainAll(Collection<?> collection) {
1106
      requireNonNull(collection);
1 ✔
1107
      @Var boolean modified = false;
1 ✔
1108
      for (var entry : this) {
1 ✔
1109
        if (!collection.contains(entry) && remove(entry)) {
1 ✔
1110
          modified = true;
1 ✔
1111
        }
1112
      }
1 ✔
1113
      return modified;
1 ✔
1114
    }
1115

1116
    @Override
1117
    public Iterator<Entry<K, V>> iterator() {
1118
      return new EntryIterator<>(cache);
1 ✔
1119
    }
1120

1121
    @Override
1122
    public Spliterator<Entry<K, V>> spliterator() {
1123
      return new EntrySpliterator<>(cache);
1 ✔
1124
    }
1125
  }
1126

1127
  /** An adapter to safely externalize the entry iterator. */
1128
  static final class EntryIterator<K, V> implements Iterator<Entry<K, V>> {
1129
    final UnboundedLocalCache<K, V> cache;
1130
    final Iterator<Entry<K, V>> iterator;
1131
    @Nullable Entry<K, V> entry;
1132

1133
    EntryIterator(UnboundedLocalCache<K, V> cache) {
1 ✔
1134
      this.iterator = cache.data.entrySet().iterator();
1 ✔
1135
      this.cache = cache;
1 ✔
1136
    }
1 ✔
1137

1138
    @Override
1139
    public boolean hasNext() {
1140
      return iterator.hasNext();
1 ✔
1141
    }
1142

1143
    @Override
1144
    public Entry<K, V> next() {
1145
      entry = iterator.next();
1 ✔
1146
      return new WriteThroughEntry<>(cache, entry.getKey(), entry.getValue());
1 ✔
1147
    }
1148

1149
    @Override
1150
    @SuppressWarnings("ResultOfMethodCallIgnored")
1151
    public void remove() {
1152
      if (entry == null) {
1 ✔
1153
        throw new IllegalStateException();
1 ✔
1154
      }
1155
      cache.remove(entry.getKey());
1 ✔
1156
      entry = null;
1 ✔
1157
    }
1 ✔
1158
  }
1159

1160
  /** An adapter to safely externalize the entry spliterator. */
1161
  static final class EntrySpliterator<K, V> implements Spliterator<Entry<K, V>> {
1162
    final Spliterator<Entry<K, V>> spliterator;
1163
    final UnboundedLocalCache<K, V> cache;
1164

1165
    EntrySpliterator(UnboundedLocalCache<K, V> cache) {
1166
      this(cache, cache.data.entrySet().spliterator());
1 ✔
1167
    }
1 ✔
1168

1169
    EntrySpliterator(UnboundedLocalCache<K, V> cache, Spliterator<Entry<K, V>> spliterator) {
1 ✔
1170
      this.spliterator = requireNonNull(spliterator);
1 ✔
1171
      this.cache = requireNonNull(cache);
1 ✔
1172
    }
1 ✔
1173

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

1183
    @Override
1184
    public boolean tryAdvance(Consumer<? super Entry<K, V>> action) {
1185
      requireNonNull(action);
1 ✔
1186
      return spliterator.tryAdvance(entry -> {
1 ✔
1187
        var e = new WriteThroughEntry<>(cache, entry.getKey(), entry.getValue());
1 ✔
1188
        action.accept(e);
1 ✔
1189
      });
1 ✔
1190
    }
1191

1192
    @Override
1193
    public @Nullable EntrySpliterator<K, V> trySplit() {
1194
      Spliterator<Entry<K, V>> split = spliterator.trySplit();
1 ✔
1195
      return (split == null) ? null : new EntrySpliterator<>(cache, split);
1 ✔
1196
    }
1197

1198
    @Override
1199
    public long estimateSize() {
1200
      return spliterator.estimateSize();
1 ✔
1201
    }
1202

1203
    @Override
1204
    public int characteristics() {
1205
      return DISTINCT | CONCURRENT | NONNULL;
1 ✔
1206
    }
1207
  }
1208

1209
  /* --------------- Manual Cache --------------- */
1210

1211
  static class UnboundedLocalManualCache<K, V> implements LocalManualCache<K, V>, Serializable {
1212
    private static final long serialVersionUID = 1;
1213

1214
    final UnboundedLocalCache<K, V> cache;
1215
    @Nullable Policy<K, V> policy;
1216

1217
    UnboundedLocalManualCache(Caffeine<K, V> builder) {
1 ✔
1218
      cache = new UnboundedLocalCache<>(builder, /* isAsync= */ false);
1 ✔
1219
    }
1 ✔
1220

1221
    @Override
1222
    public final UnboundedLocalCache<K, V> cache() {
1223
      return cache;
1 ✔
1224
    }
1225

1226
    @Override
1227
    public final Policy<K, V> policy() {
1228
      @Var var p = policy;
1 ✔
1229
      if (p == null) {
1 ✔
1230
        Function<@Nullable V, @Nullable V> identity = v -> v;
1 ✔
1231
        p = new UnboundedPolicy<>(cache, identity);
1 ✔
1232
        policy = p;
1 ✔
1233
      }
1234
      return p;
1 ✔
1235
    }
1236

1237
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1238
      throw new InvalidObjectException("Proxy required");
1 ✔
1239
    }
1240

1241
    Object writeReplace() {
1242
      var proxy = new SerializationProxy<K, V>();
1 ✔
1243
      proxy.isRecordingStats = cache.isRecordingStats;
1 ✔
1244
      proxy.removalListener = cache.removalListener;
1 ✔
1245
      return proxy;
1 ✔
1246
    }
1247
  }
1248

1249
  /** An eviction policy that supports no bounding. */
1250
  static final class UnboundedPolicy<K, V> implements Policy<K, V> {
1251
    final Function<@Nullable V, @Nullable V> transformer;
1252
    final UnboundedLocalCache<K, V> cache;
1253

1254
    UnboundedPolicy(UnboundedLocalCache<K, V> cache,
1255
        Function<@Nullable V, @Nullable V> transformer) {
1 ✔
1256
      this.transformer = transformer;
1 ✔
1257
      this.cache = cache;
1 ✔
1258
    }
1 ✔
1259
    @Override public boolean isRecordingStats() {
1260
      return cache.isRecordingStats;
1 ✔
1261
    }
1262
    @Override public @Nullable V getIfPresentQuietly(K key) {
1263
      return transformer.apply(cache.data.get(key));
1 ✔
1264
    }
1265
    @Override public @Nullable CacheEntry<K, V> getEntryIfPresentQuietly(K key) {
1266
      V value = transformer.apply(cache.data.get(key));
1 ✔
1267
      return (value == null) ? null : SnapshotEntry.forEntry(key, value);
1 ✔
1268
    }
1269
    @SuppressWarnings("Java9CollectionFactory")
1270
    @Override public Map<K, CompletableFuture<V>> refreshes() {
1271
      var refreshes = cache.refreshes;
1 ✔
1272
      if ((refreshes == null) || refreshes.isEmpty()) {
1 ✔
1273
        @SuppressWarnings({"ImmutableMapOf", "RedundantUnmodifiable"})
1274
        Map<K, CompletableFuture<V>> emptyMap = Collections.unmodifiableMap(Collections.emptyMap());
1 ✔
1275
        return emptyMap;
1 ✔
1276
      }
1277
      @SuppressWarnings("unchecked")
1278
      var castedRefreshes = (Map<K, CompletableFuture<V>>) (Object) refreshes;
1 ✔
1279
      var inFlight = new HashMap<>(castedRefreshes);
1 ✔
1280
      inFlight.values().removeIf(LocalLoadingCache.PendingRefresh.class::isInstance);
1 ✔
1281
      return Collections.unmodifiableMap(inFlight);
1 ✔
1282
    }
1283
    @Override public Optional<Eviction<K, V>> eviction() {
1284
      return Optional.empty();
1 ✔
1285
    }
1286
    @Override public Optional<FixedExpiration<K, V>> expireAfterAccess() {
1287
      return Optional.empty();
1 ✔
1288
    }
1289
    @Override public Optional<FixedExpiration<K, V>> expireAfterWrite() {
1290
      return Optional.empty();
1 ✔
1291
    }
1292
    @Override public Optional<VarExpiration<K, V>> expireVariably() {
1293
      return Optional.empty();
1 ✔
1294
    }
1295
    @Override public Optional<FixedRefresh<K, V>> refreshAfterWrite() {
1296
      return Optional.empty();
1 ✔
1297
    }
1298
  }
1299

1300
  /* --------------- Loading Cache --------------- */
1301

1302
  static final class UnboundedLocalLoadingCache<K, V> extends UnboundedLocalManualCache<K, V>
1303
      implements LocalLoadingCache<K, V> {
1304
    private static final long serialVersionUID = 1;
1305

1306
    final Function<K, @Nullable V> mappingFunction;
1307
    final CacheLoader<? super K, V> cacheLoader;
1308
    final @Nullable Function<Set<? extends K>, Map<K, V>> bulkMappingFunction;
1309

1310
    UnboundedLocalLoadingCache(Caffeine<K, V> builder, CacheLoader<? super K, V> cacheLoader) {
1311
      super(builder);
1 ✔
1312
      this.cacheLoader = cacheLoader;
1 ✔
1313
      this.mappingFunction = newMappingFunction(cacheLoader);
1 ✔
1314
      this.bulkMappingFunction = newBulkMappingFunction(cacheLoader);
1 ✔
1315
    }
1 ✔
1316

1317
    @Override
1318
    public AsyncCacheLoader<? super K, V> cacheLoader() {
1319
      return cacheLoader;
1 ✔
1320
    }
1321

1322
    @Override
1323
    public Function<K, @Nullable V> mappingFunction() {
1324
      return mappingFunction;
1 ✔
1325
    }
1326

1327
    @Override
1328
    public @Nullable Function<Set<? extends K>, Map<K, V>>  bulkMappingFunction() {
1329
      return bulkMappingFunction;
1 ✔
1330
    }
1331

1332
    @Override
1333
    Object writeReplace() {
1334
      @SuppressWarnings("unchecked")
1335
      var proxy = (SerializationProxy<K, V>) super.writeReplace();
1 ✔
1336
      proxy.cacheLoader = cacheLoader;
1 ✔
1337
      return proxy;
1 ✔
1338
    }
1339

1340
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1341
      throw new InvalidObjectException("Proxy required");
1 ✔
1342
    }
1343
  }
1344

1345
  /* --------------- Async Cache --------------- */
1346

1347
  static final class UnboundedLocalAsyncCache<K, V> implements LocalAsyncCache<K, V>, Serializable {
1348
    private static final long serialVersionUID = 1;
1349

1350
    final UnboundedLocalCache<K, CompletableFuture<V>> cache;
1351

1352
    @Nullable ConcurrentMap<K, CompletableFuture<V>> mapView;
1353
    @Nullable CacheView<K, V> cacheView;
1354
    @Nullable Policy<K, V> policy;
1355

1356
    @SuppressWarnings("unchecked")
1357
    UnboundedLocalAsyncCache(Caffeine<K, V> builder) {
1 ✔
1358
      cache = new UnboundedLocalCache<>(
1 ✔
1359
          (Caffeine<K, CompletableFuture<V>>) builder, /* isAsync= */ true);
1360
    }
1 ✔
1361

1362
    @Override
1363
    public UnboundedLocalCache<K, CompletableFuture<V>> cache() {
1364
      return cache;
1 ✔
1365
    }
1366

1367
    @Override
1368
    public ConcurrentMap<K, CompletableFuture<V>> asMap() {
1369
      var mv = mapView;
1 ✔
1370
      return (mv == null) ? (mapView = new AsyncAsMapView<>(this)) : mv;
1 ✔
1371
    }
1372

1373
    @Override
1374
    public Cache<K, V> synchronous() {
1375
      var cv = cacheView;
1 ✔
1376
      return (cv == null) ? (cacheView = new CacheView<>(this)) : cv;
1 ✔
1377
    }
1378

1379
    @Override
1380
    public Policy<K, V> policy() {
1381
      @SuppressWarnings("unchecked")
1382
      var castCache = (UnboundedLocalCache<K, V>) cache;
1 ✔
1383
      Function<CompletableFuture<V>, @Nullable V> transformer = Async::getIfReady;
1 ✔
1384
      @SuppressWarnings("unchecked")
1385
      var castTransformer = (Function<@Nullable V, @Nullable V>) transformer;
1 ✔
1386
      var p = policy;
1 ✔
1387
      return (p == null)
1 ✔
1388
          ? (policy = new UnboundedPolicy<>(castCache, castTransformer))
1 ✔
1389
          : p;
1 ✔
1390
    }
1391

1392
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1393
      throw new InvalidObjectException("Proxy required");
1 ✔
1394
    }
1395

1396
    Object writeReplace() {
1397
      var proxy = new SerializationProxy<K, V>();
1 ✔
1398
      proxy.isRecordingStats = cache.isRecordingStats;
1 ✔
1399
      proxy.removalListener = cache.removalListener;
1 ✔
1400
      proxy.async = true;
1 ✔
1401
      return proxy;
1 ✔
1402
    }
1403
  }
1404

1405
  /* --------------- Async Loading Cache --------------- */
1406

1407
  static final class UnboundedLocalAsyncLoadingCache<K, V>
1408
      extends LocalAsyncLoadingCache<K, V> implements Serializable {
1409
    private static final long serialVersionUID = 1;
1410

1411
    final UnboundedLocalCache<K, CompletableFuture<V>> cache;
1412

1413
    @Nullable ConcurrentMap<K, CompletableFuture<V>> mapView;
1414
    @Nullable Policy<K, V> policy;
1415

1416
    @SuppressWarnings("unchecked")
1417
    UnboundedLocalAsyncLoadingCache(Caffeine<K, V> builder, AsyncCacheLoader<? super K, V> loader) {
1418
      super(loader);
1 ✔
1419
      cache = new UnboundedLocalCache<>(
1 ✔
1420
          (Caffeine<K, CompletableFuture<V>>) builder, /* isAsync= */ true);
1421
    }
1 ✔
1422

1423
    @Override
1424
    public LocalCache<K, CompletableFuture<V>> cache() {
1425
      return cache;
1 ✔
1426
    }
1427

1428
    @Override
1429
    public ConcurrentMap<K, CompletableFuture<V>> asMap() {
1430
      var mv = mapView;
1 ✔
1431
      return (mv == null) ? (mapView = new AsyncAsMapView<>(this)) : mv;
1 ✔
1432
    }
1433

1434
    @Override
1435
    public Policy<K, V> policy() {
1436
      @SuppressWarnings("unchecked")
1437
      var castCache = (UnboundedLocalCache<K, V>) cache;
1 ✔
1438
      Function<CompletableFuture<V>, @Nullable V> transformer = Async::getIfReady;
1 ✔
1439
      @SuppressWarnings("unchecked")
1440
      var castTransformer = (Function<@Nullable V, @Nullable V>) transformer;
1 ✔
1441
      var p = policy;
1 ✔
1442
      return (p == null)
1 ✔
1443
          ? (policy = new UnboundedPolicy<>(castCache, castTransformer))
1 ✔
1444
          : p;
1 ✔
1445
    }
1446

1447
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1448
      throw new InvalidObjectException("Proxy required");
1 ✔
1449
    }
1450

1451
    Object writeReplace() {
1452
      var proxy = new SerializationProxy<K, V>();
1 ✔
1453
      proxy.isRecordingStats = cache.isRecordingStats();
1 ✔
1454
      proxy.removalListener = cache.removalListener;
1 ✔
1455
      proxy.cacheLoader = cacheLoader;
1 ✔
1456
      proxy.async = true;
1 ✔
1457
      return proxy;
1 ✔
1458
    }
1459
  }
1460
}
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