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

ben-manes / caffeine / #5173

29 Dec 2025 05:27AM UTC coverage: 0.0% (-100.0%) from 100.0%
#5173

push

github

ben-manes
speed up development ci build

0 of 3838 branches covered (0.0%)

0 of 7869 relevant lines covered (0.0%)

0.0 hits per line

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

0.0
/caffeine/src/main/java/com/github/benmanes/caffeine/cache/UnboundedLocalCache.java
1
/*
2
 * Copyright 2014 Ben Manes. All Rights Reserved.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package com.github.benmanes.caffeine.cache;
17

18
import static com.github.benmanes.caffeine.cache.Caffeine.calculateHashMapCapacity;
19
import static com.github.benmanes.caffeine.cache.LocalLoadingCache.newBulkMappingFunction;
20
import static com.github.benmanes.caffeine.cache.LocalLoadingCache.newMappingFunction;
21
import static java.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());
×
68
  static final VarHandle REFRESHES =
×
69
      findVarHandle(UnboundedLocalCache.class, "refreshes", 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) {
×
84
    this.data = new ConcurrentHashMap<>(builder.getInitialCapacity());
×
85
    this.statsCounter = builder.getStatsCounterSupplier().get();
×
86
    this.removalListener = builder.getRemovalListener(isAsync);
×
87
    this.isRecordingStats = builder.isRecordingStats();
×
88
    this.executor = builder.getExecutor();
×
89
    this.isAsync = isAsync;
×
90
  }
×
91

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

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

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

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

116
  @Override
117
  public boolean isPendingEviction(K key) {
118
    return false;
×
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);
×
127

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

244
  @Override
245
  public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
246
    requireNonNull(function);
×
247

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

255
  @Override
256
  public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction,
257
      boolean recordStats, boolean recordLoad) {
258
    requireNonNull(mappingFunction);
×
259

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

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

284
  @Override
285
  public @Nullable V computeIfPresent(K key,
286
      BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
287
    requireNonNull(remappingFunction);
×
288

289
    // An optimistic fast path to avoid unnecessary locking
290
    if (!data.containsKey(key)) {
×
291
      return null;
×
292
    }
293

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

303
      replaced[0] = (newValue != null);
×
304
      if (newValue != value) {
×
305
        oldValue[0] = value;
×
306
      }
307

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

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

327
  @Override
328
  public @Nullable V merge(K key, V value,
329
      BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
330
    requireNonNull(remappingFunction);
×
331
    requireNonNull(value);
×
332

333
    return remap(key, (K k, @Nullable V oldValue) ->
×
334
      (oldValue == null) ? value : statsAware(remappingFunction).apply(oldValue, value));
×
335
  }
336

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

356
      replaced[0] = (newValue != null);
×
357
      if (newValue != value) {
×
358
        oldValue[0] = value;
×
359
      }
360

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

372
  /* --------------- Concurrent Map --------------- */
373

374
  @Override
375
  public boolean isEmpty() {
376
    return data.isEmpty();
×
377
  }
378

379
  @Override
380
  public int size() {
381
    return data.size();
×
382
  }
383

384
  @Override
385
  public void clear() {
386
    var keys = (removalListener == null) ? data.keySet() : List.copyOf(data.keySet());
×
387
    for (K key : keys) {
×
388
      remove(key);
×
389
    }
×
390
  }
×
391

392
  @Override
393
  public boolean containsKey(Object key) {
394
    return data.containsKey(key);
×
395
  }
396

397
  @Override
398
  public boolean containsValue(Object value) {
399
    return data.containsValue(value);
×
400
  }
401

402
  @Override
403
  public @Nullable V get(Object key) {
404
    return getIfPresent(key, /* recordStats= */ false);
×
405
  }
406

407
  @Override
408
  public @Nullable V put(K key, V value) {
409
    requireNonNull(value);
×
410

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

424
  @Override
425
  public @Nullable V putIfAbsent(K key, V value) {
426
    requireNonNull(value);
×
427

428
    // An optimistic fast path to avoid unnecessary locking
429
    var v = data.get(key);
×
430
    if (v != null) {
×
431
      return v;
×
432
    }
433

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

443
  @Override
444
  public void putAll(Map<? extends K, ? extends V> map) {
445
    map.forEach(this::put);
×
446
  }
×
447

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

460
    if (oldValue[0] != null) {
×
461
      notifyRemoval(castKey, oldValue[0], RemovalCause.EXPLICIT);
×
462
    }
463

464
    return oldValue[0];
×
465
  }
466

467
  @Override
468
  public boolean remove(Object key, @Nullable Object value) {
469
    if (value == null) {
×
470
      requireNonNull(key);
×
471
      return false;
×
472
    }
473

474
    @SuppressWarnings("unchecked")
475
    var castKey = (K) key;
×
476
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
477
    @Nullable V[] oldValue = (V[]) new Object[1];
×
478

479
    data.computeIfPresent(castKey, (k, v) -> {
×
480
      if (Objects.equals(v, value)) {
×
481
        discardRefresh(k);
×
482
        oldValue[0] = v;
×
483
        return null;
×
484
      }
485
      return v;
×
486
    });
487

488
    if (oldValue[0] != null) {
×
489
      notifyRemoval(castKey, oldValue[0], RemovalCause.EXPLICIT);
×
490
      return true;
×
491
    }
492
    return false;
×
493
  }
494

495
  @Override
496
  public @Nullable V replace(K key, V value) {
497
    requireNonNull(value);
×
498

499
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
500
    @Nullable V[] oldValue = (@Nullable V[]) new Object[1];
×
501
    data.computeIfPresent(key, (k, v) -> {
×
502
      discardRefresh(k);
×
503
      oldValue[0] = v;
×
504
      return value;
×
505
    });
506

507
    if ((oldValue[0] != null) && (oldValue[0] != value)) {
×
508
      notifyRemoval(key, oldValue[0], RemovalCause.REPLACED);
×
509
    }
510
    return oldValue[0];
×
511
  }
512

513
  @Override
514
  public boolean replace(K key, V oldValue, V newValue) {
515
    return replace(key, oldValue, newValue, /* shouldDiscardRefresh= */ true);
×
516
  }
517

518
  @Override
519
  public boolean replace(K key, V oldValue, V newValue, boolean shouldDiscardRefresh) {
520
    requireNonNull(oldValue);
×
521
    requireNonNull(newValue);
×
522

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

536
    boolean replaced = (prev[0] != null);
×
537
    if (replaced && (prev[0] != newValue)) {
×
538
      notifyRemoval(key, prev[0], RemovalCause.REPLACED);
×
539
    }
540
    return replaced;
×
541
  }
542

543
  @Override
544
  @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
545
  public boolean equals(@Nullable Object o) {
546
    return (o == this) || data.equals(o);
×
547
  }
548

549
  @Override
550
  public int hashCode() {
551
    return data.hashCode();
×
552
  }
553

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

568
  @Override
569
  public Set<K> keySet() {
570
    Set<K> ks = keySet;
×
571
    return (ks == null) ? (keySet = new KeySetView<>(this)) : ks;
×
572
  }
573

574
  @Override
575
  public Collection<V> values() {
576
    Collection<V> vs = values;
×
577
    return (vs == null) ? (values = new ValuesView<>(this)) : vs;
×
578
  }
579

580
  @Override
581
  public Set<Entry<K, V>> entrySet() {
582
    Set<Entry<K, V>> es = entrySet;
×
583
    return (es == null) ? (entrySet = new EntrySetView<>(this)) : es;
×
584
  }
585

586
  /** An adapter to safely externalize the keys. */
587
  static final class KeySetView<K> extends AbstractSet<K> {
588
    final UnboundedLocalCache<K, ?> cache;
589

590
    KeySetView(UnboundedLocalCache<K, ?> cache) {
×
591
      this.cache = requireNonNull(cache);
×
592
    }
×
593

594
    @Override
595
    public boolean isEmpty() {
596
      return cache.isEmpty();
×
597
    }
598

599
    @Override
600
    public int size() {
601
      return cache.size();
×
602
    }
603

604
    @Override
605
    public void clear() {
606
      cache.clear();
×
607
    }
×
608

609
    @Override
610
    @SuppressWarnings("SuspiciousMethodCalls")
611
    public boolean contains(Object o) {
612
      return cache.containsKey(o);
×
613
    }
614

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

633
    @Override
634
    public boolean remove(Object o) {
635
      return (cache.remove(o) != null);
×
636
    }
637

638
    @Override
639
    public boolean removeIf(Predicate<? super K> filter) {
640
      requireNonNull(filter);
×
641
      @Var boolean modified = false;
×
642
      for (K key : this) {
×
643
        if (filter.test(key) && remove(key)) {
×
644
          modified = true;
×
645
        }
646
      }
×
647
      return modified;
×
648
    }
649

650
    @Override
651
    public boolean retainAll(Collection<?> collection) {
652
      requireNonNull(collection);
×
653
      @Var boolean modified = false;
×
654
      for (K key : this) {
×
655
        if (!collection.contains(key) && remove(key)) {
×
656
          modified = true;
×
657
        }
658
      }
×
659
      return modified;
×
660
    }
661

662
    @Override
663
    public void forEach(Consumer<? super K> action) {
664
      cache.data.keySet().forEach(action);
×
665
    }
×
666

667
    @Override
668
    public Iterator<K> iterator() {
669
      return new KeyIterator<>(cache);
×
670
    }
671

672
    @Override
673
    public Spliterator<K> spliterator() {
674
      return new KeySpliterator<>(cache);
×
675
    }
676

677
    @Override
678
    public Object[] toArray() {
679
      return cache.data.keySet().toArray();
×
680
    }
681

682
    @Override
683
    public <T> T[] toArray(T[] array) {
684
      return cache.data.keySet().toArray(array);
×
685
    }
686
  }
687

688
  /** An adapter to safely externalize the key iterator. */
689
  static final class KeyIterator<K> implements Iterator<K> {
690
    final UnboundedLocalCache<K, ?> cache;
691
    final Iterator<K> iterator;
692
    @Nullable K current;
693

694
    KeyIterator(UnboundedLocalCache<K, ?> cache) {
×
695
      this.iterator = cache.data.keySet().iterator();
×
696
      this.cache = cache;
×
697
    }
×
698

699
    @Override
700
    public boolean hasNext() {
701
      return iterator.hasNext();
×
702
    }
703

704
    @Override
705
    public K next() {
706
      current = iterator.next();
×
707
      return current;
×
708
    }
709

710
    @Override
711
    public void remove() {
712
      if (current == null) {
×
713
        throw new IllegalStateException();
×
714
      }
715
      cache.remove(current);
×
716
      current = null;
×
717
    }
×
718
  }
719

720
  /** An adapter to safely externalize the key spliterator. */
721
  static final class KeySpliterator<K, V> implements Spliterator<K> {
722
    final Spliterator<K> spliterator;
723

724
    KeySpliterator(UnboundedLocalCache<K, V> cache) {
725
      this(cache.data.keySet().spliterator());
×
726
    }
×
727

728
    KeySpliterator(Spliterator<K> spliterator) {
×
729
      this.spliterator = requireNonNull(spliterator);
×
730
    }
×
731

732
    @Override
733
    public void forEachRemaining(Consumer<? super K> action) {
734
      requireNonNull(action);
×
735
      spliterator.forEachRemaining(action);
×
736
    }
×
737

738
    @Override
739
    public boolean tryAdvance(Consumer<? super K> action) {
740
      requireNonNull(action);
×
741
      return spliterator.tryAdvance(action);
×
742
    }
743

744
    @Override
745
    public @Nullable KeySpliterator<K, V> trySplit() {
746
      Spliterator<K> split = spliterator.trySplit();
×
747
      return (split == null) ? null : new KeySpliterator<>(split);
×
748
    }
749

750
    @Override
751
    public long estimateSize() {
752
      return spliterator.estimateSize();
×
753
    }
754

755
    @Override
756
    public int characteristics() {
757
      return DISTINCT | CONCURRENT | NONNULL;
×
758
    }
759
  }
760

761
  /** An adapter to safely externalize the values. */
762
  static final class ValuesView<K, V> extends AbstractCollection<V> {
763
    final UnboundedLocalCache<K, V> cache;
764

765
    ValuesView(UnboundedLocalCache<K, V> cache) {
×
766
      this.cache = requireNonNull(cache);
×
767
    }
×
768

769
    @Override
770
    public boolean isEmpty() {
771
      return cache.isEmpty();
×
772
    }
773

774
    @Override
775
    public int size() {
776
      return cache.size();
×
777
    }
778

779
    @Override
780
    public void clear() {
781
      cache.clear();
×
782
    }
×
783

784
    @Override
785
    @SuppressWarnings("SuspiciousMethodCalls")
786
    public boolean contains(Object o) {
787
      return cache.containsValue(o);
×
788
    }
789

790
    @Override
791
    public boolean removeAll(Collection<?> collection) {
792
      requireNonNull(collection);
×
793
      @Var boolean modified = false;
×
794
      for (var entry : cache.data.entrySet()) {
×
795
        if (collection.contains(entry.getValue())
×
796
            && cache.remove(entry.getKey(), entry.getValue())) {
×
797
          modified = true;
×
798
        }
799
      }
×
800
      return modified;
×
801
    }
802

803
    @Override
804
    public boolean remove(@Nullable Object o) {
805
      if (o == null) {
×
806
        return false;
×
807
      }
808
      for (var entry : cache.data.entrySet()) {
×
809
        if (o.equals(entry.getValue()) && cache.remove(entry.getKey(), entry.getValue())) {
×
810
          return true;
×
811
        }
812
      }
×
813
      return false;
×
814
    }
815

816
    @Override
817
    public boolean removeIf(Predicate<? super V> filter) {
818
      requireNonNull(filter);
×
819
      @Var boolean removed = false;
×
820
      for (var entry : cache.data.entrySet()) {
×
821
        if (filter.test(entry.getValue())) {
×
822
          removed |= cache.remove(entry.getKey(), entry.getValue());
×
823
        }
824
      }
×
825
      return removed;
×
826
    }
827

828
    @Override
829
    public boolean retainAll(Collection<?> collection) {
830
      requireNonNull(collection);
×
831
      @Var boolean modified = false;
×
832
      for (var entry : cache.data.entrySet()) {
×
833
        if (!collection.contains(entry.getValue())
×
834
            && cache.remove(entry.getKey(), entry.getValue())) {
×
835
          modified = true;
×
836
        }
837
      }
×
838
      return modified;
×
839
    }
840

841
    @Override
842
    public void forEach(Consumer<? super V> action) {
843
      cache.data.values().forEach(action);
×
844
    }
×
845

846
    @Override
847
    public Iterator<V> iterator() {
848
      return new ValueIterator<>(cache);
×
849
    }
850

851
    @Override
852
    public Spliterator<V> spliterator() {
853
      return new ValueSpliterator<>(cache);
×
854
    }
855

856
    @Override
857
    public Object[] toArray() {
858
      return cache.data.values().toArray();
×
859
    }
860

861
    @Override
862
    public <T> T[] toArray(T[] array) {
863
      return cache.data.values().toArray(array);
×
864
    }
865
  }
866

867
  /** An adapter to safely externalize the value iterator. */
868
  static final class ValueIterator<K, V> implements Iterator<V> {
869
    final UnboundedLocalCache<K, V> cache;
870
    final Iterator<Entry<K, V>> iterator;
871
    @Nullable Entry<K, V> entry;
872

873
    ValueIterator(UnboundedLocalCache<K, V> cache) {
×
874
      this.iterator = cache.data.entrySet().iterator();
×
875
      this.cache = cache;
×
876
    }
×
877

878
    @Override
879
    public boolean hasNext() {
880
      return iterator.hasNext();
×
881
    }
882

883
    @Override
884
    public V next() {
885
      entry = iterator.next();
×
886
      return entry.getValue();
×
887
    }
888

889
    @Override
890
    public void remove() {
891
      if (entry == null) {
×
892
        throw new IllegalStateException();
×
893
      }
894
      cache.remove(entry.getKey());
×
895
      entry = null;
×
896
    }
×
897
  }
898

899
  /** An adapter to safely externalize the value spliterator. */
900
  static final class ValueSpliterator<K, V> implements Spliterator<V> {
901
    final Spliterator<V> spliterator;
902

903
    ValueSpliterator(UnboundedLocalCache<K, V> cache) {
904
      this(cache.data.values().spliterator());
×
905
    }
×
906

907
    ValueSpliterator(Spliterator<V> spliterator) {
×
908
      this.spliterator = requireNonNull(spliterator);
×
909
    }
×
910

911
    @Override
912
    public void forEachRemaining(Consumer<? super V> action) {
913
      requireNonNull(action);
×
914
      spliterator.forEachRemaining(action);
×
915
    }
×
916

917
    @Override
918
    public boolean tryAdvance(Consumer<? super V> action) {
919
      requireNonNull(action);
×
920
      return spliterator.tryAdvance(action);
×
921
    }
922

923
    @Override
924
    public @Nullable ValueSpliterator<K, V> trySplit() {
925
      Spliterator<V> split = spliterator.trySplit();
×
926
      return (split == null) ? null : new ValueSpliterator<>(split);
×
927
    }
928

929
    @Override
930
    public long estimateSize() {
931
      return spliterator.estimateSize();
×
932
    }
933

934
    @Override
935
    public int characteristics() {
936
      return CONCURRENT | NONNULL;
×
937
    }
938
  }
939

940
  /** An adapter to safely externalize the entries. */
941
  static final class EntrySetView<K, V> extends AbstractSet<Entry<K, V>> {
942
    final UnboundedLocalCache<K, V> cache;
943

944
    EntrySetView(UnboundedLocalCache<K, V> cache) {
×
945
      this.cache = requireNonNull(cache);
×
946
    }
×
947

948
    @Override
949
    public boolean isEmpty() {
950
      return cache.isEmpty();
×
951
    }
952

953
    @Override
954
    public int size() {
955
      return cache.size();
×
956
    }
957

958
    @Override
959
    public void clear() {
960
      cache.clear();
×
961
    }
×
962

963
    @Override
964
    @SuppressWarnings("SuspiciousMethodCalls")
965
    public boolean contains(Object o) {
966
      if (!(o instanceof Entry<?, ?>)) {
×
967
        return false;
×
968
      }
969
      var entry = (Entry<?, ?>) o;
×
970
      var key = entry.getKey();
×
971
      var value = entry.getValue();
×
972
      if ((key == null) || (value == null)) {
×
973
        return false;
×
974
      }
975
      V cachedValue = cache.get(key);
×
976
      return (cachedValue != null) && cachedValue.equals(value);
×
977
    }
978

979
    @Override
980
    public boolean removeAll(Collection<?> collection) {
981
      requireNonNull(collection);
×
982
      @Var boolean modified = false;
×
983
      if ((collection instanceof Set<?>) && (collection.size() > size())) {
×
984
        for (var entry : this) {
×
985
          if (collection.contains(entry)) {
×
986
            modified |= remove(entry);
×
987
          }
988
        }
×
989
      } else {
990
        for (var o : collection) {
×
991
          modified |= remove(o);
×
992
        }
×
993
      }
994
      return modified;
×
995
    }
996

997
    @Override
998
    @SuppressWarnings("SuspiciousMethodCalls")
999
    public boolean remove(Object o) {
1000
      if (!(o instanceof Entry<?, ?>)) {
×
1001
        return false;
×
1002
      }
1003
      var entry = (Entry<?, ?>) o;
×
1004
      var key = entry.getKey();
×
1005
      return (key != null) && cache.remove(key, entry.getValue());
×
1006
    }
1007

1008
    @Override
1009
    public boolean removeIf(Predicate<? super Entry<K, V>> filter) {
1010
      requireNonNull(filter);
×
1011
      @Var boolean removed = false;
×
1012
      for (var entry : cache.data.entrySet()) {
×
1013
        if (filter.test(entry)) {
×
1014
          removed |= cache.remove(entry.getKey(), entry.getValue());
×
1015
        }
1016
      }
×
1017
      return removed;
×
1018
    }
1019

1020
    @Override
1021
    public boolean retainAll(Collection<?> collection) {
1022
      requireNonNull(collection);
×
1023
      @Var boolean modified = false;
×
1024
      for (var entry : this) {
×
1025
        if (!collection.contains(entry) && remove(entry)) {
×
1026
          modified = true;
×
1027
        }
1028
      }
×
1029
      return modified;
×
1030
    }
1031

1032
    @Override
1033
    public Iterator<Entry<K, V>> iterator() {
1034
      return new EntryIterator<>(cache);
×
1035
    }
1036

1037
    @Override
1038
    public Spliterator<Entry<K, V>> spliterator() {
1039
      return new EntrySpliterator<>(cache);
×
1040
    }
1041
  }
1042

1043
  /** An adapter to safely externalize the entry iterator. */
1044
  static final class EntryIterator<K, V> implements Iterator<Entry<K, V>> {
1045
    final UnboundedLocalCache<K, V> cache;
1046
    final Iterator<Entry<K, V>> iterator;
1047
    @Nullable Entry<K, V> entry;
1048

1049
    EntryIterator(UnboundedLocalCache<K, V> cache) {
×
1050
      this.iterator = cache.data.entrySet().iterator();
×
1051
      this.cache = cache;
×
1052
    }
×
1053

1054
    @Override
1055
    public boolean hasNext() {
1056
      return iterator.hasNext();
×
1057
    }
1058

1059
    @Override
1060
    public Entry<K, V> next() {
1061
      entry = iterator.next();
×
1062
      return new WriteThroughEntry<>(cache, entry.getKey(), entry.getValue());
×
1063
    }
1064

1065
    @Override
1066
    public void remove() {
1067
      if (entry == null) {
×
1068
        throw new IllegalStateException();
×
1069
      }
1070
      cache.remove(entry.getKey());
×
1071
      entry = null;
×
1072
    }
×
1073
  }
1074

1075
  /** An adapter to safely externalize the entry spliterator. */
1076
  static final class EntrySpliterator<K, V> implements Spliterator<Entry<K, V>> {
1077
    final Spliterator<Entry<K, V>> spliterator;
1078
    final UnboundedLocalCache<K, V> cache;
1079

1080
    EntrySpliterator(UnboundedLocalCache<K, V> cache) {
1081
      this(cache, cache.data.entrySet().spliterator());
×
1082
    }
×
1083

1084
    EntrySpliterator(UnboundedLocalCache<K, V> cache, Spliterator<Entry<K, V>> spliterator) {
×
1085
      this.spliterator = requireNonNull(spliterator);
×
1086
      this.cache = requireNonNull(cache);
×
1087
    }
×
1088

1089
    @Override
1090
    public void forEachRemaining(Consumer<? super Entry<K, V>> action) {
1091
      requireNonNull(action);
×
1092
      spliterator.forEachRemaining(entry -> {
×
1093
        var e = new WriteThroughEntry<>(cache, entry.getKey(), entry.getValue());
×
1094
        action.accept(e);
×
1095
      });
×
1096
    }
×
1097

1098
    @Override
1099
    public boolean tryAdvance(Consumer<? super Entry<K, V>> action) {
1100
      requireNonNull(action);
×
1101
      return spliterator.tryAdvance(entry -> {
×
1102
        var e = new WriteThroughEntry<>(cache, entry.getKey(), entry.getValue());
×
1103
        action.accept(e);
×
1104
      });
×
1105
    }
1106

1107
    @Override
1108
    public @Nullable EntrySpliterator<K, V> trySplit() {
1109
      Spliterator<Entry<K, V>> split = spliterator.trySplit();
×
1110
      return (split == null) ? null : new EntrySpliterator<>(cache, split);
×
1111
    }
1112

1113
    @Override
1114
    public long estimateSize() {
1115
      return spliterator.estimateSize();
×
1116
    }
1117

1118
    @Override
1119
    public int characteristics() {
1120
      return DISTINCT | CONCURRENT | NONNULL;
×
1121
    }
1122
  }
1123

1124
  /* --------------- Manual Cache --------------- */
1125

1126
  static class UnboundedLocalManualCache<K, V> implements LocalManualCache<K, V>, Serializable {
1127
    private static final long serialVersionUID = 1;
1128

1129
    final UnboundedLocalCache<K, V> cache;
1130
    @Nullable Policy<K, V> policy;
1131

1132
    UnboundedLocalManualCache(Caffeine<K, V> builder) {
×
1133
      cache = new UnboundedLocalCache<>(builder, /* isAsync= */ false);
×
1134
    }
×
1135

1136
    @Override
1137
    public final UnboundedLocalCache<K, V> cache() {
1138
      return cache;
×
1139
    }
1140

1141
    @Override
1142
    public final Policy<K, V> policy() {
1143
      if (policy == null) {
×
1144
        Function<@Nullable V, @Nullable V> identity = v -> v;
×
1145
        policy = new UnboundedPolicy<>(cache, identity);
×
1146
      }
1147
      return policy;
×
1148
    }
1149

1150
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1151
      throw new InvalidObjectException("Proxy required");
×
1152
    }
1153

1154
    Object writeReplace() {
1155
      var proxy = new SerializationProxy<K, V>();
×
1156
      proxy.isRecordingStats = cache.isRecordingStats;
×
1157
      proxy.removalListener = cache.removalListener;
×
1158
      return proxy;
×
1159
    }
1160
  }
1161

1162
  /** An eviction policy that supports no bounding. */
1163
  static final class UnboundedPolicy<K, V> implements Policy<K, V> {
1164
    final Function<@Nullable V, @Nullable V> transformer;
1165
    final UnboundedLocalCache<K, V> cache;
1166

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

1211
  /* --------------- Loading Cache --------------- */
1212

1213
  static final class UnboundedLocalLoadingCache<K, V> extends UnboundedLocalManualCache<K, V>
1214
      implements LocalLoadingCache<K, V> {
1215
    private static final long serialVersionUID = 1;
1216

1217
    final Function<K, @Nullable V> mappingFunction;
1218
    final CacheLoader<? super K, V> cacheLoader;
1219
    final @Nullable Function<Set<? extends K>, Map<K, V>> bulkMappingFunction;
1220

1221
    UnboundedLocalLoadingCache(Caffeine<K, V> builder, CacheLoader<? super K, V> cacheLoader) {
1222
      super(builder);
×
1223
      this.cacheLoader = cacheLoader;
×
1224
      this.mappingFunction = newMappingFunction(cacheLoader);
×
1225
      this.bulkMappingFunction = newBulkMappingFunction(cacheLoader);
×
1226
    }
×
1227

1228
    @Override
1229
    public AsyncCacheLoader<? super K, V> cacheLoader() {
1230
      return cacheLoader;
×
1231
    }
1232

1233
    @Override
1234
    public Function<K, @Nullable V> mappingFunction() {
1235
      return mappingFunction;
×
1236
    }
1237

1238
    @Override
1239
    public @Nullable Function<Set<? extends K>, Map<K, V>>  bulkMappingFunction() {
1240
      return bulkMappingFunction;
×
1241
    }
1242

1243
    @Override
1244
    Object writeReplace() {
1245
      @SuppressWarnings("unchecked")
1246
      var proxy = (SerializationProxy<K, V>) super.writeReplace();
×
1247
      proxy.cacheLoader = cacheLoader;
×
1248
      return proxy;
×
1249
    }
1250

1251
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1252
      throw new InvalidObjectException("Proxy required");
×
1253
    }
1254
  }
1255

1256
  /* --------------- Async Cache --------------- */
1257

1258
  static final class UnboundedLocalAsyncCache<K, V> implements LocalAsyncCache<K, V>, Serializable {
1259
    private static final long serialVersionUID = 1;
1260

1261
    final UnboundedLocalCache<K, CompletableFuture<V>> cache;
1262

1263
    @Nullable ConcurrentMap<K, CompletableFuture<V>> mapView;
1264
    @Nullable CacheView<K, V> cacheView;
1265
    @Nullable Policy<K, V> policy;
1266

1267
    @SuppressWarnings("unchecked")
1268
    UnboundedLocalAsyncCache(Caffeine<K, V> builder) {
×
1269
      cache = new UnboundedLocalCache<>(
×
1270
          (Caffeine<K, CompletableFuture<V>>) builder, /* isAsync= */ true);
1271
    }
×
1272

1273
    @Override
1274
    public UnboundedLocalCache<K, CompletableFuture<V>> cache() {
1275
      return cache;
×
1276
    }
1277

1278
    @Override
1279
    public ConcurrentMap<K, CompletableFuture<V>> asMap() {
1280
      return (mapView == null) ? (mapView = new AsyncAsMapView<>(this)) : mapView;
×
1281
    }
1282

1283
    @Override
1284
    public Cache<K, V> synchronous() {
1285
      return (cacheView == null) ? (cacheView = new CacheView<>(this)) : cacheView;
×
1286
    }
1287

1288
    @Override
1289
    public Policy<K, V> policy() {
1290
      @SuppressWarnings("unchecked")
1291
      var castCache = (UnboundedLocalCache<K, V>) cache;
×
1292
      Function<CompletableFuture<V>, @Nullable V> transformer = Async::getIfReady;
×
1293
      @SuppressWarnings("unchecked")
1294
      var castTransformer = (Function<@Nullable V, @Nullable V>) transformer;
×
1295
      return (policy == null)
×
1296
          ? (policy = new UnboundedPolicy<>(castCache, castTransformer))
×
1297
          : policy;
×
1298
    }
1299

1300
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1301
      throw new InvalidObjectException("Proxy required");
×
1302
    }
1303

1304
    Object writeReplace() {
1305
      var proxy = new SerializationProxy<K, V>();
×
1306
      proxy.isRecordingStats = cache.isRecordingStats;
×
1307
      proxy.removalListener = cache.removalListener;
×
1308
      proxy.async = true;
×
1309
      return proxy;
×
1310
    }
1311
  }
1312

1313
  /* --------------- Async Loading Cache --------------- */
1314

1315
  static final class UnboundedLocalAsyncLoadingCache<K, V>
1316
      extends LocalAsyncLoadingCache<K, V> implements Serializable {
1317
    private static final long serialVersionUID = 1;
1318

1319
    final UnboundedLocalCache<K, CompletableFuture<V>> cache;
1320

1321
    @Nullable ConcurrentMap<K, CompletableFuture<V>> mapView;
1322
    @Nullable Policy<K, V> policy;
1323

1324
    @SuppressWarnings("unchecked")
1325
    UnboundedLocalAsyncLoadingCache(Caffeine<K, V> builder, AsyncCacheLoader<? super K, V> loader) {
1326
      super(loader);
×
1327
      cache = new UnboundedLocalCache<>(
×
1328
          (Caffeine<K, CompletableFuture<V>>) builder, /* isAsync= */ true);
1329
    }
×
1330

1331
    @Override
1332
    public LocalCache<K, CompletableFuture<V>> cache() {
1333
      return cache;
×
1334
    }
1335

1336
    @Override
1337
    public ConcurrentMap<K, CompletableFuture<V>> asMap() {
1338
      return (mapView == null) ? (mapView = new AsyncAsMapView<>(this)) : mapView;
×
1339
    }
1340

1341
    @Override
1342
    public Policy<K, V> policy() {
1343
      @SuppressWarnings("unchecked")
1344
      var castCache = (UnboundedLocalCache<K, V>) cache;
×
1345
      Function<CompletableFuture<V>, @Nullable V> transformer = Async::getIfReady;
×
1346
      @SuppressWarnings("unchecked")
1347
      var castTransformer = (Function<@Nullable V, @Nullable V>) transformer;
×
1348
      return (policy == null)
×
1349
          ? (policy = new UnboundedPolicy<>(castCache, castTransformer))
×
1350
          : policy;
×
1351
    }
1352

1353
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1354
      throw new InvalidObjectException("Proxy required");
×
1355
    }
1356

1357
    Object writeReplace() {
1358
      var proxy = new SerializationProxy<K, V>();
×
1359
      proxy.isRecordingStats = cache.isRecordingStats();
×
1360
      proxy.removalListener = cache.removalListener;
×
1361
      proxy.cacheLoader = cacheLoader;
×
1362
      proxy.async = true;
×
1363
      return proxy;
×
1364
    }
1365
  }
1366
}
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