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

ben-manes / caffeine / #5156

03 Dec 2025 03:21AM UTC coverage: 0.0% (-100.0%) from 100.0%
#5156

push

github

ben-manes
add loading type to parameterized test dimensions to reduce task size

0 of 3834 branches covered (0.0%)

0 of 7848 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, 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, 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
    return Collections.unmodifiableMap(result);
×
170
  }
171

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

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

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

200
  @Override
201
  public boolean isRecordingStats() {
202
    return isRecordingStats;
×
203
  }
204

205
  @Override
206
  public Executor executor() {
207
    return executor;
×
208
  }
209

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

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

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

235
  /* --------------- JDK8+ Map extensions --------------- */
236

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

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

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

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

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

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

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

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

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

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

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

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

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

330
    return remap(key, (k, oldValue) ->
×
331
      (oldValue == null) ? value : statsAware(remappingFunction).apply(oldValue, value));
×
332
  }
333

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

352
      replaced[0] = (newValue != null);
×
353
      if (newValue != value) {
×
354
        oldValue[0] = value;
×
355
      }
356

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

368
  /* --------------- Concurrent Map --------------- */
369

370
  @Override
371
  public boolean isEmpty() {
372
    return data.isEmpty();
×
373
  }
374

375
  @Override
376
  public int size() {
377
    return data.size();
×
378
  }
379

380
  @Override
381
  public void clear() {
382
    var keys = (removalListener == null) ? data.keySet() : List.copyOf(data.keySet());
×
383
    for (K key : keys) {
×
384
      remove(key);
×
385
    }
×
386
  }
×
387

388
  @Override
389
  public boolean containsKey(Object key) {
390
    return data.containsKey(key);
×
391
  }
392

393
  @Override
394
  public boolean containsValue(Object value) {
395
    return data.containsValue(value);
×
396
  }
397

398
  @Override
399
  public @Nullable V get(Object key) {
400
    return getIfPresent(key, /* recordStats= */ false);
×
401
  }
402

403
  @Override
404
  public @Nullable V put(K key, V value) {
405
    requireNonNull(value);
×
406

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

420
  @Override
421
  public @Nullable V putIfAbsent(K key, V value) {
422
    requireNonNull(value);
×
423

424
    // An optimistic fast path to avoid unnecessary locking
425
    var v = data.get(key);
×
426
    if (v != null) {
×
427
      return v;
×
428
    }
429

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

439
  @Override
440
  public void putAll(Map<? extends K, ? extends V> map) {
441
    map.forEach(this::put);
×
442
  }
×
443

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

456
    if (oldValue[0] != null) {
×
457
      notifyRemoval(castKey, oldValue[0], RemovalCause.EXPLICIT);
×
458
    }
459

460
    return oldValue[0];
×
461
  }
462

463
  @Override
464
  public boolean remove(Object key, Object value) {
465
    if (value == null) {
×
466
      requireNonNull(key);
×
467
      return false;
×
468
    }
469

470
    @SuppressWarnings("unchecked")
471
    var castKey = (K) key;
×
472
    @SuppressWarnings({"rawtypes", "unchecked"})
473
    var oldValue = (V[]) new Object[1];
×
474

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

484
    if (oldValue[0] != null) {
×
485
      notifyRemoval(castKey, oldValue[0], RemovalCause.EXPLICIT);
×
486
      return true;
×
487
    }
488
    return false;
×
489
  }
490

491
  @Override
492
  public @Nullable V replace(K key, V value) {
493
    requireNonNull(value);
×
494

495
    @SuppressWarnings({"rawtypes", "unchecked"})
496
    var oldValue = (V[]) new Object[1];
×
497
    data.computeIfPresent(key, (k, v) -> {
×
498
      discardRefresh(k);
×
499
      oldValue[0] = v;
×
500
      return value;
×
501
    });
502

503
    if ((oldValue[0] != null) && (oldValue[0] != value)) {
×
504
      notifyRemoval(key, oldValue[0], RemovalCause.REPLACED);
×
505
    }
506
    return oldValue[0];
×
507
  }
508

509
  @Override
510
  public boolean replace(K key, V oldValue, V newValue) {
511
    return replace(key, oldValue, newValue, /* shouldDiscardRefresh= */ true);
×
512
  }
513

514
  @Override
515
  public boolean replace(K key, V oldValue, V newValue, boolean shouldDiscardRefresh) {
516
    requireNonNull(oldValue);
×
517
    requireNonNull(newValue);
×
518

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

532
    boolean replaced = (prev[0] != null);
×
533
    if (replaced && (prev[0] != newValue)) {
×
534
      notifyRemoval(key, prev[0], RemovalCause.REPLACED);
×
535
    }
536
    return replaced;
×
537
  }
538

539
  @Override
540
  @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
541
  public boolean equals(@Nullable Object o) {
542
    return (o == this) || data.equals(o);
×
543
  }
544

545
  @Override
546
  public int hashCode() {
547
    return data.hashCode();
×
548
  }
549

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

564
  @Override
565
  public Set<K> keySet() {
566
    Set<K> ks = keySet;
×
567
    return (ks == null) ? (keySet = new KeySetView<>(this)) : ks;
×
568
  }
569

570
  @Override
571
  public Collection<V> values() {
572
    Collection<V> vs = values;
×
573
    return (vs == null) ? (values = new ValuesView<>(this)) : vs;
×
574
  }
575

576
  @Override
577
  public Set<Entry<K, V>> entrySet() {
578
    Set<Entry<K, V>> es = entrySet;
×
579
    return (es == null) ? (entrySet = new EntrySetView<>(this)) : es;
×
580
  }
581

582
  /** An adapter to safely externalize the keys. */
583
  static final class KeySetView<K> extends AbstractSet<K> {
584
    final UnboundedLocalCache<K, ?> cache;
585

586
    KeySetView(UnboundedLocalCache<K, ?> cache) {
×
587
      this.cache = requireNonNull(cache);
×
588
    }
×
589

590
    @Override
591
    public boolean isEmpty() {
592
      return cache.isEmpty();
×
593
    }
594

595
    @Override
596
    public int size() {
597
      return cache.size();
×
598
    }
599

600
    @Override
601
    public void clear() {
602
      cache.clear();
×
603
    }
×
604

605
    @Override
606
    @SuppressWarnings("SuspiciousMethodCalls")
607
    public boolean contains(Object o) {
608
      return cache.containsKey(o);
×
609
    }
610

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

629
    @Override
630
    public boolean remove(Object o) {
631
      return (cache.remove(o) != null);
×
632
    }
633

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

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

658
    @Override
659
    public void forEach(Consumer<? super K> action) {
660
      cache.data.keySet().forEach(action);
×
661
    }
×
662

663
    @Override
664
    public Iterator<K> iterator() {
665
      return new KeyIterator<>(cache);
×
666
    }
667

668
    @Override
669
    public Spliterator<K> spliterator() {
670
      return new KeySpliterator<>(cache);
×
671
    }
672

673
    @Override
674
    public Object[] toArray() {
675
      return cache.data.keySet().toArray();
×
676
    }
677

678
    @Override
679
    public <T> T[] toArray(T[] array) {
680
      return cache.data.keySet().toArray(array);
×
681
    }
682
  }
683

684
  /** An adapter to safely externalize the key iterator. */
685
  static final class KeyIterator<K> implements Iterator<K> {
686
    final UnboundedLocalCache<K, ?> cache;
687
    final Iterator<K> iterator;
688
    @Nullable K current;
689

690
    KeyIterator(UnboundedLocalCache<K, ?> cache) {
×
691
      this.iterator = cache.data.keySet().iterator();
×
692
      this.cache = cache;
×
693
    }
×
694

695
    @Override
696
    public boolean hasNext() {
697
      return iterator.hasNext();
×
698
    }
699

700
    @Override
701
    public K next() {
702
      current = iterator.next();
×
703
      return current;
×
704
    }
705

706
    @Override
707
    public void remove() {
708
      if (current == null) {
×
709
        throw new IllegalStateException();
×
710
      }
711
      cache.remove(current);
×
712
      current = null;
×
713
    }
×
714
  }
715

716
  /** An adapter to safely externalize the key spliterator. */
717
  static final class KeySpliterator<K, V> implements Spliterator<K> {
718
    final Spliterator<K> spliterator;
719

720
    KeySpliterator(UnboundedLocalCache<K, V> cache) {
721
      this(cache.data.keySet().spliterator());
×
722
    }
×
723

724
    KeySpliterator(Spliterator<K> spliterator) {
×
725
      this.spliterator = requireNonNull(spliterator);
×
726
    }
×
727

728
    @Override
729
    public void forEachRemaining(Consumer<? super K> action) {
730
      requireNonNull(action);
×
731
      spliterator.forEachRemaining(action);
×
732
    }
×
733

734
    @Override
735
    public boolean tryAdvance(Consumer<? super K> action) {
736
      requireNonNull(action);
×
737
      return spliterator.tryAdvance(action);
×
738
    }
739

740
    @Override
741
    public @Nullable KeySpliterator<K, V> trySplit() {
742
      Spliterator<K> split = spliterator.trySplit();
×
743
      return (split == null) ? null : new KeySpliterator<>(split);
×
744
    }
745

746
    @Override
747
    public long estimateSize() {
748
      return spliterator.estimateSize();
×
749
    }
750

751
    @Override
752
    public int characteristics() {
753
      return DISTINCT | CONCURRENT | NONNULL;
×
754
    }
755
  }
756

757
  /** An adapter to safely externalize the values. */
758
  static final class ValuesView<K, V> extends AbstractCollection<V> {
759
    final UnboundedLocalCache<K, V> cache;
760

761
    ValuesView(UnboundedLocalCache<K, V> cache) {
×
762
      this.cache = requireNonNull(cache);
×
763
    }
×
764

765
    @Override
766
    public boolean isEmpty() {
767
      return cache.isEmpty();
×
768
    }
769

770
    @Override
771
    public int size() {
772
      return cache.size();
×
773
    }
774

775
    @Override
776
    public void clear() {
777
      cache.clear();
×
778
    }
×
779

780
    @Override
781
    @SuppressWarnings("SuspiciousMethodCalls")
782
    public boolean contains(Object o) {
783
      return cache.containsValue(o);
×
784
    }
785

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

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

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

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

837
    @Override
838
    public void forEach(Consumer<? super V> action) {
839
      cache.data.values().forEach(action);
×
840
    }
×
841

842
    @Override
843
    public Iterator<V> iterator() {
844
      return new ValueIterator<>(cache);
×
845
    }
846

847
    @Override
848
    public Spliterator<V> spliterator() {
849
      return new ValueSpliterator<>(cache);
×
850
    }
851

852
    @Override
853
    public Object[] toArray() {
854
      return cache.data.values().toArray();
×
855
    }
856

857
    @Override
858
    public <T> T[] toArray(T[] array) {
859
      return cache.data.values().toArray(array);
×
860
    }
861
  }
862

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

869
    ValueIterator(UnboundedLocalCache<K, V> cache) {
×
870
      this.iterator = cache.data.entrySet().iterator();
×
871
      this.cache = cache;
×
872
    }
×
873

874
    @Override
875
    public boolean hasNext() {
876
      return iterator.hasNext();
×
877
    }
878

879
    @Override
880
    public V next() {
881
      entry = iterator.next();
×
882
      return entry.getValue();
×
883
    }
884

885
    @Override
886
    public void remove() {
887
      if (entry == null) {
×
888
        throw new IllegalStateException();
×
889
      }
890
      cache.remove(entry.getKey());
×
891
      entry = null;
×
892
    }
×
893
  }
894

895
  /** An adapter to safely externalize the value spliterator. */
896
  static final class ValueSpliterator<K, V> implements Spliterator<V> {
897
    final Spliterator<V> spliterator;
898

899
    ValueSpliterator(UnboundedLocalCache<K, V> cache) {
900
      this(cache.data.values().spliterator());
×
901
    }
×
902

903
    ValueSpliterator(Spliterator<V> spliterator) {
×
904
      this.spliterator = requireNonNull(spliterator);
×
905
    }
×
906

907
    @Override
908
    public void forEachRemaining(Consumer<? super V> action) {
909
      requireNonNull(action);
×
910
      spliterator.forEachRemaining(action);
×
911
    }
×
912

913
    @Override
914
    public boolean tryAdvance(Consumer<? super V> action) {
915
      requireNonNull(action);
×
916
      return spliterator.tryAdvance(action);
×
917
    }
918

919
    @Override
920
    public @Nullable ValueSpliterator<K, V> trySplit() {
921
      Spliterator<V> split = spliterator.trySplit();
×
922
      return (split == null) ? null : new ValueSpliterator<>(split);
×
923
    }
924

925
    @Override
926
    public long estimateSize() {
927
      return spliterator.estimateSize();
×
928
    }
929

930
    @Override
931
    public int characteristics() {
932
      return CONCURRENT | NONNULL;
×
933
    }
934
  }
935

936
  /** An adapter to safely externalize the entries. */
937
  static final class EntrySetView<K, V> extends AbstractSet<Entry<K, V>> {
938
    final UnboundedLocalCache<K, V> cache;
939

940
    EntrySetView(UnboundedLocalCache<K, V> cache) {
×
941
      this.cache = requireNonNull(cache);
×
942
    }
×
943

944
    @Override
945
    public boolean isEmpty() {
946
      return cache.isEmpty();
×
947
    }
948

949
    @Override
950
    public int size() {
951
      return cache.size();
×
952
    }
953

954
    @Override
955
    public void clear() {
956
      cache.clear();
×
957
    }
×
958

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

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

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

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

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

1028
    @Override
1029
    public Iterator<Entry<K, V>> iterator() {
1030
      return new EntryIterator<>(cache);
×
1031
    }
1032

1033
    @Override
1034
    public Spliterator<Entry<K, V>> spliterator() {
1035
      return new EntrySpliterator<>(cache);
×
1036
    }
1037
  }
1038

1039
  /** An adapter to safely externalize the entry iterator. */
1040
  static final class EntryIterator<K, V> implements Iterator<Entry<K, V>> {
1041
    final UnboundedLocalCache<K, V> cache;
1042
    final Iterator<Entry<K, V>> iterator;
1043
    @Nullable Entry<K, V> entry;
1044

1045
    EntryIterator(UnboundedLocalCache<K, V> cache) {
×
1046
      this.iterator = cache.data.entrySet().iterator();
×
1047
      this.cache = cache;
×
1048
    }
×
1049

1050
    @Override
1051
    public boolean hasNext() {
1052
      return iterator.hasNext();
×
1053
    }
1054

1055
    @Override
1056
    public Entry<K, V> next() {
1057
      entry = iterator.next();
×
1058
      return new WriteThroughEntry<>(cache, entry.getKey(), entry.getValue());
×
1059
    }
1060

1061
    @Override
1062
    public void remove() {
1063
      if (entry == null) {
×
1064
        throw new IllegalStateException();
×
1065
      }
1066
      cache.remove(entry.getKey());
×
1067
      entry = null;
×
1068
    }
×
1069
  }
1070

1071
  /** An adapter to safely externalize the entry spliterator. */
1072
  static final class EntrySpliterator<K, V> implements Spliterator<Entry<K, V>> {
1073
    final Spliterator<Entry<K, V>> spliterator;
1074
    final UnboundedLocalCache<K, V> cache;
1075

1076
    EntrySpliterator(UnboundedLocalCache<K, V> cache) {
1077
      this(cache, cache.data.entrySet().spliterator());
×
1078
    }
×
1079

1080
    EntrySpliterator(UnboundedLocalCache<K, V> cache, Spliterator<Entry<K, V>> spliterator) {
×
1081
      this.spliterator = requireNonNull(spliterator);
×
1082
      this.cache = requireNonNull(cache);
×
1083
    }
×
1084

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

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

1103
    @Override
1104
    public @Nullable EntrySpliterator<K, V> trySplit() {
1105
      Spliterator<Entry<K, V>> split = spliterator.trySplit();
×
1106
      return (split == null) ? null : new EntrySpliterator<>(cache, split);
×
1107
    }
1108

1109
    @Override
1110
    public long estimateSize() {
1111
      return spliterator.estimateSize();
×
1112
    }
1113

1114
    @Override
1115
    public int characteristics() {
1116
      return DISTINCT | CONCURRENT | NONNULL;
×
1117
    }
1118
  }
1119

1120
  /* --------------- Manual Cache --------------- */
1121

1122
  static class UnboundedLocalManualCache<K, V> implements LocalManualCache<K, V>, Serializable {
1123
    private static final long serialVersionUID = 1;
1124

1125
    final UnboundedLocalCache<K, V> cache;
1126
    @Nullable Policy<K, V> policy;
1127

1128
    UnboundedLocalManualCache(Caffeine<K, V> builder) {
×
1129
      cache = new UnboundedLocalCache<>(builder, /* isAsync= */ false);
×
1130
    }
×
1131

1132
    @Override
1133
    public final UnboundedLocalCache<K, V> cache() {
1134
      return cache;
×
1135
    }
1136

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

1146
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1147
      throw new InvalidObjectException("Proxy required");
×
1148
    }
1149

1150
    Object writeReplace() {
1151
      var proxy = new SerializationProxy<K, V>();
×
1152
      proxy.isRecordingStats = cache.isRecordingStats;
×
1153
      proxy.removalListener = cache.removalListener;
×
1154
      return proxy;
×
1155
    }
1156
  }
1157

1158
  /** An eviction policy that supports no bounding. */
1159
  static final class UnboundedPolicy<K, V> implements Policy<K, V> {
1160
    final Function<@Nullable V, @Nullable V> transformer;
1161
    final UnboundedLocalCache<K, V> cache;
1162

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

1207
  /* --------------- Loading Cache --------------- */
1208

1209
  static final class UnboundedLocalLoadingCache<K, V> extends UnboundedLocalManualCache<K, V>
1210
      implements LocalLoadingCache<K, V> {
1211
    private static final long serialVersionUID = 1;
1212

1213
    final Function<K, @Nullable V> mappingFunction;
1214
    final CacheLoader<? super K, V> cacheLoader;
1215
    final @Nullable Function<Set<? extends K>, Map<K, V>> bulkMappingFunction;
1216

1217
    UnboundedLocalLoadingCache(Caffeine<K, V> builder, CacheLoader<? super K, V> cacheLoader) {
1218
      super(builder);
×
1219
      this.cacheLoader = cacheLoader;
×
1220
      this.mappingFunction = newMappingFunction(cacheLoader);
×
1221
      this.bulkMappingFunction = newBulkMappingFunction(cacheLoader);
×
1222
    }
×
1223

1224
    @Override
1225
    public AsyncCacheLoader<? super K, V> cacheLoader() {
1226
      return cacheLoader;
×
1227
    }
1228

1229
    @Override
1230
    public Function<K, @Nullable V> mappingFunction() {
1231
      return mappingFunction;
×
1232
    }
1233

1234
    @Override
1235
    public @Nullable Function<Set<? extends K>, Map<K, V>>  bulkMappingFunction() {
1236
      return bulkMappingFunction;
×
1237
    }
1238

1239
    @Override
1240
    Object writeReplace() {
1241
      @SuppressWarnings("unchecked")
1242
      var proxy = (SerializationProxy<K, V>) super.writeReplace();
×
1243
      proxy.cacheLoader = cacheLoader;
×
1244
      return proxy;
×
1245
    }
1246

1247
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1248
      throw new InvalidObjectException("Proxy required");
×
1249
    }
1250
  }
1251

1252
  /* --------------- Async Cache --------------- */
1253

1254
  static final class UnboundedLocalAsyncCache<K, V> implements LocalAsyncCache<K, V>, Serializable {
1255
    private static final long serialVersionUID = 1;
1256

1257
    final UnboundedLocalCache<K, CompletableFuture<V>> cache;
1258

1259
    @Nullable ConcurrentMap<K, CompletableFuture<V>> mapView;
1260
    @Nullable CacheView<K, V> cacheView;
1261
    @Nullable Policy<K, V> policy;
1262

1263
    @SuppressWarnings("unchecked")
1264
    UnboundedLocalAsyncCache(Caffeine<K, V> builder) {
×
1265
      cache = new UnboundedLocalCache<>(
×
1266
          (Caffeine<K, CompletableFuture<V>>) builder, /* isAsync= */ true);
1267
    }
×
1268

1269
    @Override
1270
    public UnboundedLocalCache<K, CompletableFuture<V>> cache() {
1271
      return cache;
×
1272
    }
1273

1274
    @Override
1275
    public ConcurrentMap<K, CompletableFuture<V>> asMap() {
1276
      return (mapView == null) ? (mapView = new AsyncAsMapView<>(this)) : mapView;
×
1277
    }
1278

1279
    @Override
1280
    public Cache<K, V> synchronous() {
1281
      return (cacheView == null) ? (cacheView = new CacheView<>(this)) : cacheView;
×
1282
    }
1283

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

1296
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1297
      throw new InvalidObjectException("Proxy required");
×
1298
    }
1299

1300
    Object writeReplace() {
1301
      var proxy = new SerializationProxy<K, V>();
×
1302
      proxy.isRecordingStats = cache.isRecordingStats;
×
1303
      proxy.removalListener = cache.removalListener;
×
1304
      proxy.async = true;
×
1305
      return proxy;
×
1306
    }
1307
  }
1308

1309
  /* --------------- Async Loading Cache --------------- */
1310

1311
  static final class UnboundedLocalAsyncLoadingCache<K, V>
1312
      extends LocalAsyncLoadingCache<K, V> implements Serializable {
1313
    private static final long serialVersionUID = 1;
1314

1315
    final UnboundedLocalCache<K, CompletableFuture<V>> cache;
1316

1317
    @Nullable ConcurrentMap<K, CompletableFuture<V>> mapView;
1318
    @Nullable Policy<K, V> policy;
1319

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

1327
    @Override
1328
    public LocalCache<K, CompletableFuture<V>> cache() {
1329
      return cache;
×
1330
    }
1331

1332
    @Override
1333
    public ConcurrentMap<K, CompletableFuture<V>> asMap() {
1334
      return (mapView == null) ? (mapView = new AsyncAsMapView<>(this)) : mapView;
×
1335
    }
1336

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

1349
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1350
      throw new InvalidObjectException("Proxy required");
×
1351
    }
1352

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