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

ben-manes / caffeine / #4811

18 Apr 2025 07:16AM UTC coverage: 99.277% (-0.01%) from 99.29%
#4811

push

github

ben-manes
generalize apache commons' map tests using a junit5 parameterized class

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

1 existing line in 1 file now uncovered.

7692 of 7748 relevant lines covered (99.28%)

0.99 hits per line

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

99.8
/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; // NOPMD
20
import static com.github.benmanes.caffeine.cache.LocalLoadingCache.newMappingFunction; // NOPMD
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.Optional;
40
import java.util.Set;
41
import java.util.Spliterator;
42
import java.util.concurrent.CompletableFuture;
43
import java.util.concurrent.ConcurrentHashMap;
44
import java.util.concurrent.ConcurrentMap;
45
import java.util.concurrent.Executor;
46
import java.util.function.BiConsumer;
47
import java.util.function.BiFunction;
48
import java.util.function.Consumer;
49
import java.util.function.Function;
50
import java.util.function.Predicate;
51

52
import org.jspecify.annotations.Nullable;
53

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

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

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

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

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

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

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

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

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

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

120
  /* --------------- Cache --------------- */
121

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

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

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

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

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

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

168
    return Collections.unmodifiableMap(result);
1✔
169
  }
170

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

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

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

199
  @Override
200
  public boolean isRecordingStats() {
201
    return isRecordingStats;
1✔
202
  }
203

204
  @Override
205
  public Executor executor() {
206
    return executor;
1✔
207
  }
208

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

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

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

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

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

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

245
    // ensures that the removal notification is processed after the removal has completed
246
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
247
    @Nullable K[] notificationKey = (K[]) new Object[1];
1✔
248
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
249
    @Nullable V[] notificationValue = (V[]) new Object[1];
1✔
250
    data.replaceAll((key, value) -> {
1✔
251
      if (notificationKey[0] != null) {
1✔
252
        notifyRemoval(notificationKey[0], notificationValue[0], RemovalCause.REPLACED);
1✔
253
        notificationValue[0] = null;
1✔
254
        notificationKey[0] = null;
1✔
255
      }
256

257
      V newValue = requireNonNull(function.apply(key, value));
1✔
258
      if (newValue != value) {
1✔
259
        notificationKey[0] = key;
1✔
260
        notificationValue[0] = value;
1✔
261
      }
262

263
      return newValue;
1✔
264
    });
265
    if (notificationKey[0] != null) {
1✔
266
      notifyRemoval(notificationKey[0], notificationValue[0], RemovalCause.REPLACED);
1✔
267
    }
268
  }
1✔
269

270
  @Override
271
  public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction,
272
      boolean recordStats, boolean recordLoad) {
273
    requireNonNull(mappingFunction);
1✔
274

275
    // optimistic fast path due to computeIfAbsent always locking
276
    @Var V value = data.get(key);
1✔
277
    if (value != null) {
1✔
278
      if (recordStats) {
1✔
279
        statsCounter.recordHits(1);
1✔
280
      }
281
      return value;
1✔
282
    }
283

284
    boolean[] missed = new boolean[1];
1✔
285
    value = data.computeIfAbsent(key, k -> {
1✔
286
      // Do not communicate to CacheWriter on a load
287
      missed[0] = true;
1✔
288
      return recordStats
1✔
289
          ? statsAware(mappingFunction, recordLoad).apply(key)
1✔
290
          : mappingFunction.apply(key);
1✔
291
    });
292
    if (!missed[0] && recordStats) {
1✔
293
      statsCounter.recordHits(1);
1✔
294
    }
295
    return value;
1✔
296
  }
297

298
  @Override
299
  public @Nullable V computeIfPresent(K key,
300
      BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
301
    requireNonNull(remappingFunction);
1✔
302

303
    // optimistic fast path due to computeIfAbsent always locking
304
    if (!data.containsKey(key)) {
1✔
305
      return null;
1✔
306
    }
307

308
    // ensures that the removal notification is processed after the removal has completed
309
    @SuppressWarnings({"rawtypes", "unchecked"})
310
    var oldValue = (V[]) new Object[1];
1✔
311
    boolean[] replaced = new boolean[1];
1✔
312
    V nv = data.computeIfPresent(key, (K k, V value) -> {
1✔
313
      BiFunction<? super K, ? super V, ? extends V> function = statsAware(remappingFunction,
1✔
314
          /* recordLoad= */ true, /* recordLoadFailure= */ true);
315
      V newValue = function.apply(k, value);
1✔
316

317
      replaced[0] = (newValue != null);
1✔
318
      if (newValue != value) {
1✔
319
        oldValue[0] = value;
1✔
320
      }
321

322
      discardRefresh(k);
1✔
323
      return newValue;
1✔
324
    });
325
    if (replaced[0]) {
1✔
326
      notifyOnReplace(key, oldValue[0], nv);
1✔
327
    } else if (oldValue[0] != null) {
1✔
328
      notifyRemoval(key, oldValue[0], RemovalCause.EXPLICIT);
1✔
329
    }
330
    return nv;
1✔
331
  }
332

333
  @Override
334
  public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction,
335
      @Nullable Expiry<? super K, ? super V> expiry, boolean recordLoad,
336
      boolean recordLoadFailure) {
337
    requireNonNull(remappingFunction);
1✔
338
    return remap(key, statsAware(remappingFunction, recordLoad, recordLoadFailure));
1✔
339
  }
340

341
  @Override
342
  public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
343
    requireNonNull(remappingFunction);
1✔
344
    requireNonNull(value);
1✔
345

346
    return remap(key, (k, oldValue) ->
1✔
347
      (oldValue == null) ? value : statsAware(remappingFunction).apply(oldValue, value));
1✔
348
  }
349

350
  /**
351
   * A {@link Map#compute(Object, BiFunction)} that does not directly record any cache statistics.
352
   *
353
   * @param key key with which the specified value is to be associated
354
   * @param remappingFunction the function to compute a value
355
   * @return the new value associated with the specified key, or null if none
356
   */
357
  V remap(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
358
    // ensures that the removal notification is processed after the removal has completed
359
    @SuppressWarnings({"rawtypes", "unchecked"})
360
    var oldValue = (V[]) new Object[1];
1✔
361
    boolean[] replaced = new boolean[1];
1✔
362
    V nv = data.compute(key, (K k, V value) -> {
1✔
363
      V newValue = remappingFunction.apply(k, value);
1✔
364
      if ((value == null) && (newValue == null)) {
1✔
365
        return null;
1✔
366
      }
367

368
      replaced[0] = (newValue != null);
1✔
369
      if (newValue != value) {
1✔
370
        oldValue[0] = value;
1✔
371
      }
372

373
      discardRefresh(k);
1✔
374
      return newValue;
1✔
375
    });
376
    if (replaced[0]) {
1✔
377
      notifyOnReplace(key, oldValue[0], nv);
1✔
378
    } else if (oldValue[0] != null) {
1✔
379
      notifyRemoval(key, oldValue[0], RemovalCause.EXPLICIT);
1✔
380
    }
381
    return nv;
1✔
382
  }
383

384
  /* --------------- Concurrent Map --------------- */
385

386
  @Override
387
  public boolean isEmpty() {
388
    return data.isEmpty();
1✔
389
  }
390

391
  @Override
392
  public int size() {
393
    return data.size();
1✔
394
  }
395

396
  @Override
397
  public void clear() {
398
    if ((removalListener == null) && ((refreshes == null) || refreshes.isEmpty())) {
1✔
399
      data.clear();
1✔
400
      return;
1✔
401
    }
402
    for (K key : List.copyOf(data.keySet())) {
1✔
403
      remove(key);
1✔
404
    }
1✔
405
  }
1✔
406

407
  @Override
408
  public boolean containsKey(Object key) {
409
    return data.containsKey(key);
1✔
410
  }
411

412
  @Override
413
  public boolean containsValue(Object value) {
414
    return data.containsValue(value);
1✔
415
  }
416

417
  @Override
418
  public @Nullable V get(Object key) {
419
    return getIfPresent(key, /* recordStats= */ false);
1✔
420
  }
421

422
  @Override
423
  public @Nullable V put(K key, V value) {
424
    V oldValue = data.put(key, value);
1✔
425
    notifyOnReplace(key, oldValue, value);
1✔
426
    return oldValue;
1✔
427
  }
428

429
  @Override
430
  public @Nullable V putIfAbsent(K key, V value) {
431
    return data.putIfAbsent(key, value);
1✔
432
  }
433

434
  @Override
435
  public void putAll(Map<? extends K, ? extends V> map) {
436
    if (removalListener == null) {
1✔
437
      data.putAll(map);
1✔
438
    } else {
439
      map.forEach(this::put);
1✔
440
    }
441
  }
1✔
442

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

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

459
    return oldValue[0];
1✔
460
  }
461

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

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

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

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

490
  @Override
491
  public @Nullable V replace(K key, V value) {
492
    requireNonNull(value);
1✔
493

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

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

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

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

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

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

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

544
  @Override
545
  public int hashCode() {
546
    return data.hashCode();
1✔
547
  }
548

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

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

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

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

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

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

589
    @Override
590
    public boolean isEmpty() {
591
      return cache.isEmpty();
1✔
592
    }
593

594
    @Override
595
    public int size() {
596
      return cache.size();
1✔
597
    }
598

599
    @Override
600
    public void clear() {
601
      cache.clear();
1✔
602
    }
1✔
603

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

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

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

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

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

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

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

667
    @Override
668
    public Spliterator<K> spliterator() {
669
      return cache.data.keySet().spliterator();
1✔
670
    }
671

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

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

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

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

694
    @Override
695
    public boolean hasNext() {
696
      return iterator.hasNext();
1✔
697
    }
698

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

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

715
  /** An adapter to safely externalize the values. */
716
  static final class ValuesView<K, V> extends AbstractCollection<V> {
717
    final UnboundedLocalCache<K, V> cache;
718

719
    ValuesView(UnboundedLocalCache<K, V> cache) {
1✔
720
      this.cache = requireNonNull(cache);
1✔
721
    }
1✔
722

723
    @Override
724
    public boolean isEmpty() {
725
      return cache.isEmpty();
1✔
726
    }
727

728
    @Override
729
    public int size() {
730
      return cache.size();
1✔
731
    }
732

733
    @Override
734
    public void clear() {
735
      cache.clear();
1✔
736
    }
1✔
737

738
    @Override
739
    @SuppressWarnings("SuspiciousMethodCalls")
740
    public boolean contains(Object o) {
741
      return cache.containsValue(o);
1✔
742
    }
743

744
    @Override
745
    public boolean removeAll(Collection<?> collection) {
746
      requireNonNull(collection);
1✔
747
      @Var boolean modified = false;
1✔
748
      for (var entry : cache.data.entrySet()) {
1✔
749
        if (collection.contains(entry.getValue())
1✔
750
            && cache.remove(entry.getKey(), entry.getValue())) {
1✔
751
          modified = true;
1✔
752
        }
753
      }
1✔
754
      return modified;
1✔
755
    }
756

757
    @Override
758
    public boolean remove(Object o) {
759
      if (o == null) {
1✔
760
        return false;
1✔
761
      }
762
      for (var entry : cache.data.entrySet()) {
1✔
763
        if (o.equals(entry.getValue()) && cache.remove(entry.getKey(), entry.getValue())) {
1✔
764
          return true;
1✔
765
        }
766
      }
1✔
767
      return false;
1✔
768
    }
769

770
    @Override
771
    public boolean removeIf(Predicate<? super V> filter) {
772
      requireNonNull(filter);
1✔
773
      @Var boolean removed = false;
1✔
774
      for (var entry : cache.data.entrySet()) {
1✔
775
        if (filter.test(entry.getValue())) {
1✔
776
          removed |= cache.remove(entry.getKey(), entry.getValue());
1✔
777
        }
778
      }
1✔
779
      return removed;
1✔
780
    }
781

782
    @Override
783
    public boolean retainAll(Collection<?> collection) {
784
      requireNonNull(collection);
1✔
785
      @Var boolean modified = false;
1✔
786
      for (var entry : cache.data.entrySet()) {
1✔
787
        if (!collection.contains(entry.getValue())
1✔
788
            && cache.remove(entry.getKey(), entry.getValue())) {
1✔
789
          modified = true;
1✔
790
        }
791
      }
1✔
792
      return modified;
1✔
793
    }
794

795
    @Override
796
    public void forEach(Consumer<? super V> action) {
797
      cache.data.values().forEach(action);
1✔
798
    }
1✔
799

800
    @Override
801
    public Iterator<V> iterator() {
802
      return new ValuesIterator<>(cache);
1✔
803
    }
804

805
    @Override
806
    public Spliterator<V> spliterator() {
807
      return cache.data.values().spliterator();
1✔
808
    }
809

810
    @Override
811
    public Object[] toArray() {
812
      return cache.data.values().toArray();
1✔
813
    }
814

815
    @Override
816
    public <T> T[] toArray(T[] array) {
817
      return cache.data.values().toArray(array);
1✔
818
    }
819
  }
820

821
  /** An adapter to safely externalize the value iterator. */
822
  static final class ValuesIterator<K, V> implements Iterator<V> {
823
    final UnboundedLocalCache<K, V> cache;
824
    final Iterator<Entry<K, V>> iterator;
825
    @Nullable Entry<K, V> entry;
826

827
    ValuesIterator(UnboundedLocalCache<K, V> cache) {
1✔
828
      this.iterator = cache.data.entrySet().iterator();
1✔
829
      this.cache = cache;
1✔
830
    }
1✔
831

832
    @Override
833
    public boolean hasNext() {
834
      return iterator.hasNext();
1✔
835
    }
836

837
    @Override
838
    public V next() {
839
      entry = iterator.next();
1✔
840
      return entry.getValue();
1✔
841
    }
842

843
    @Override
844
    public void remove() {
845
      if (entry == null) {
1✔
846
        throw new IllegalStateException();
1✔
847
      }
848
      cache.remove(entry.getKey());
1✔
849
      entry = null;
1✔
850
    }
1✔
851
  }
852

853
  /** An adapter to safely externalize the entries. */
854
  static final class EntrySetView<K, V> extends AbstractSet<Entry<K, V>> {
855
    final UnboundedLocalCache<K, V> cache;
856

857
    EntrySetView(UnboundedLocalCache<K, V> cache) {
1✔
858
      this.cache = requireNonNull(cache);
1✔
859
    }
1✔
860

861
    @Override
862
    public boolean isEmpty() {
863
      return cache.isEmpty();
1✔
864
    }
865

866
    @Override
867
    public int size() {
868
      return cache.size();
1✔
869
    }
870

871
    @Override
872
    public void clear() {
873
      cache.clear();
1✔
874
    }
1✔
875

876
    @Override
877
    @SuppressWarnings("SuspiciousMethodCalls")
878
    public boolean contains(Object o) {
879
      if (!(o instanceof Entry<?, ?>)) {
1✔
880
        return false;
1✔
881
      }
882
      var entry = (Entry<?, ?>) o;
1✔
883
      var key = entry.getKey();
1✔
884
      var value = entry.getValue();
1✔
885
      if ((key == null) || (value == null)) {
1✔
886
        return false;
1✔
887
      }
888
      V cachedValue = cache.get(key);
1✔
889
      return (cachedValue != null) && cachedValue.equals(value);
1✔
890
    }
891

892
    @Override
893
    public boolean removeAll(Collection<?> collection) {
894
      requireNonNull(collection);
1✔
895
      @Var boolean modified = false;
1✔
896
      if ((collection instanceof Set<?>) && (collection.size() > size())) {
1✔
897
        for (var entry : this) {
1✔
898
          if (collection.contains(entry)) {
1✔
899
            modified |= remove(entry);
1✔
900
          }
901
        }
1✔
902
      } else {
903
        for (var o : collection) {
1✔
904
          modified |= remove(o);
1✔
905
        }
1✔
906
      }
907
      return modified;
1✔
908
    }
909

910
    @Override
911
    @SuppressWarnings("SuspiciousMethodCalls")
912
    public boolean remove(Object o) {
913
      if (!(o instanceof Entry<?, ?>)) {
1✔
914
        return false;
1✔
915
      }
916
      var entry = (Entry<?, ?>) o;
1✔
917
      var key = entry.getKey();
1✔
918
      return (key != null) && cache.remove(key, entry.getValue());
1✔
919
    }
920

921
    @Override
922
    public boolean removeIf(Predicate<? super Entry<K, V>> filter) {
923
      requireNonNull(filter);
1✔
924
      @Var boolean removed = false;
1✔
925
      for (var entry : cache.data.entrySet()) {
1✔
926
        if (filter.test(entry)) {
1✔
927
          removed |= cache.remove(entry.getKey(), entry.getValue());
1✔
928
        }
929
      }
1✔
930
      return removed;
1✔
931
    }
932

933
    @Override
934
    public boolean retainAll(Collection<?> collection) {
935
      requireNonNull(collection);
1✔
936
      @Var boolean modified = false;
1✔
937
      for (var entry : this) {
1✔
938
        if (!collection.contains(entry) && remove(entry)) {
1✔
939
          modified = true;
1✔
940
        }
941
      }
1✔
942
      return modified;
1✔
943
    }
944

945
    @Override
946
    public Iterator<Entry<K, V>> iterator() {
947
      return new EntryIterator<>(cache);
1✔
948
    }
949

950
    @Override
951
    public Spliterator<Entry<K, V>> spliterator() {
952
      return new EntrySpliterator<>(cache);
1✔
953
    }
954
  }
955

956
  /** An adapter to safely externalize the entry iterator. */
957
  static final class EntryIterator<K, V> implements Iterator<Entry<K, V>> {
958
    final UnboundedLocalCache<K, V> cache;
959
    final Iterator<Entry<K, V>> iterator;
960
    @Nullable Entry<K, V> entry;
961

962
    EntryIterator(UnboundedLocalCache<K, V> cache) {
1✔
963
      this.iterator = cache.data.entrySet().iterator();
1✔
964
      this.cache = cache;
1✔
965
    }
1✔
966

967
    @Override
968
    public boolean hasNext() {
969
      return iterator.hasNext();
1✔
970
    }
971

972
    @Override
973
    public Entry<K, V> next() {
974
      entry = iterator.next();
1✔
975
      return new WriteThroughEntry<>(cache, entry.getKey(), entry.getValue());
1✔
976
    }
977

978
    @Override
979
    public void remove() {
980
      if (entry == null) {
1✔
981
        throw new IllegalStateException();
1✔
982
      }
983
      cache.remove(entry.getKey());
1✔
984
      entry = null;
1✔
985
    }
1✔
986
  }
987

988
  /** An adapter to safely externalize the entry spliterator. */
989
  static final class EntrySpliterator<K, V> implements Spliterator<Entry<K, V>> {
990
    final Spliterator<Entry<K, V>> spliterator;
991
    final UnboundedLocalCache<K, V> cache;
992

993
    EntrySpliterator(UnboundedLocalCache<K, V> cache) {
994
      this(cache, cache.data.entrySet().spliterator());
1✔
995
    }
1✔
996

997
    EntrySpliterator(UnboundedLocalCache<K, V> cache, Spliterator<Entry<K, V>> spliterator) {
1✔
998
      this.spliterator = requireNonNull(spliterator);
1✔
999
      this.cache = requireNonNull(cache);
1✔
1000
    }
1✔
1001

1002
    @Override
1003
    public void forEachRemaining(Consumer<? super Entry<K, V>> action) {
1004
      requireNonNull(action);
1✔
1005
      spliterator.forEachRemaining(entry -> {
1✔
1006
        var e = new WriteThroughEntry<>(cache, entry.getKey(), entry.getValue());
1✔
1007
        action.accept(e);
1✔
1008
      });
1✔
1009
    }
1✔
1010

1011
    @Override
1012
    public boolean tryAdvance(Consumer<? super Entry<K, V>> action) {
1013
      requireNonNull(action);
1✔
1014
      return spliterator.tryAdvance(entry -> {
1✔
1015
        var e = new WriteThroughEntry<>(cache, entry.getKey(), entry.getValue());
1✔
1016
        action.accept(e);
1✔
1017
      });
1✔
1018
    }
1019

1020
    @Override
1021
    public @Nullable EntrySpliterator<K, V> trySplit() {
1022
      Spliterator<Entry<K, V>> split = spliterator.trySplit();
1✔
1023
      return (split == null) ? null : new EntrySpliterator<>(cache, split);
1✔
1024
    }
1025

1026
    @Override
1027
    public long estimateSize() {
1028
      return spliterator.estimateSize();
1✔
1029
    }
1030

1031
    @Override
1032
    public int characteristics() {
1033
      return spliterator.characteristics();
1✔
1034
    }
1035
  }
1036

1037
  /* --------------- Manual Cache --------------- */
1038

1039
  static class UnboundedLocalManualCache<K, V> implements LocalManualCache<K, V>, Serializable {
1040
    private static final long serialVersionUID = 1;
1041

1042
    final UnboundedLocalCache<K, V> cache;
1043
    @Nullable Policy<K, V> policy;
1044

1045
    UnboundedLocalManualCache(Caffeine<K, V> builder) {
1✔
1046
      cache = new UnboundedLocalCache<>(builder, /* isAsync= */ false);
1✔
1047
    }
1✔
1048

1049
    @Override
1050
    public final UnboundedLocalCache<K, V> cache() {
1051
      return cache;
1✔
1052
    }
1053

1054
    @Override
1055
    public final Policy<K, V> policy() {
1056
      if (policy == null) {
1✔
1057
        Function<@Nullable V, @Nullable V> identity = v -> v;
1✔
1058
        policy = new UnboundedPolicy<>(cache, identity);
1✔
1059
      }
1060
      return policy;
1✔
1061
    }
1062

1063
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1064
      throw new InvalidObjectException("Proxy required");
1✔
1065
    }
1066

1067
    Object writeReplace() {
1068
      var proxy = new SerializationProxy<K, V>();
1✔
1069
      proxy.isRecordingStats = cache.isRecordingStats;
1✔
1070
      proxy.removalListener = cache.removalListener;
1✔
1071
      return proxy;
1✔
1072
    }
1073
  }
1074

1075
  /** An eviction policy that supports no bounding. */
1076
  static final class UnboundedPolicy<K, V> implements Policy<K, V> {
1077
    final Function<@Nullable V, @Nullable V> transformer;
1078
    final UnboundedLocalCache<K, V> cache;
1079

1080
    UnboundedPolicy(UnboundedLocalCache<K, V> cache,
1081
        Function<@Nullable V, @Nullable V> transformer) {
1✔
1082
      this.transformer = transformer;
1✔
1083
      this.cache = cache;
1✔
1084
    }
1✔
1085
    @Override public boolean isRecordingStats() {
1086
      return cache.isRecordingStats;
1✔
1087
    }
1088
    @Override public @Nullable V getIfPresentQuietly(K key) {
1089
      return transformer.apply(cache.data.get(key));
1✔
1090
    }
1091
    @Override public @Nullable CacheEntry<K, V> getEntryIfPresentQuietly(K key) {
1092
      V value = transformer.apply(cache.data.get(key));
1✔
1093
      return (value == null) ? null : SnapshotEntry.forEntry(key, value);
1✔
1094
    }
1095
    @SuppressWarnings("Java9CollectionFactory")
1096
    @Override public Map<K, CompletableFuture<V>> refreshes() {
1097
      var refreshes = cache.refreshes;
1✔
1098
      if ((refreshes == null) || refreshes.isEmpty()) {
1✔
1099
        @SuppressWarnings("ImmutableMapOf")
1100
        Map<K, CompletableFuture<V>> emptyMap = Collections.unmodifiableMap(Collections.emptyMap());
1✔
1101
        return emptyMap;
1✔
1102
      }
1103
      @SuppressWarnings("unchecked")
1104
      var castedRefreshes = (Map<K, CompletableFuture<V>>) (Object) refreshes;
1✔
1105
      return Collections.unmodifiableMap(new HashMap<>(castedRefreshes));
1✔
1106
    }
1107
    @Override public Optional<Eviction<K, V>> eviction() {
1108
      return Optional.empty();
1✔
1109
    }
1110
    @Override public Optional<FixedExpiration<K, V>> expireAfterAccess() {
1111
      return Optional.empty();
1✔
1112
    }
1113
    @Override public Optional<FixedExpiration<K, V>> expireAfterWrite() {
1114
      return Optional.empty();
1✔
1115
    }
1116
    @Override public Optional<VarExpiration<K, V>> expireVariably() {
1117
      return Optional.empty();
1✔
1118
    }
1119
    @Override public Optional<FixedRefresh<K, V>> refreshAfterWrite() {
1120
      return Optional.empty();
1✔
1121
    }
1122
  }
1123

1124
  /* --------------- Loading Cache --------------- */
1125

1126
  static final class UnboundedLocalLoadingCache<K, V> extends UnboundedLocalManualCache<K, V>
1127
      implements LocalLoadingCache<K, V> {
1128
    private static final long serialVersionUID = 1;
1129

1130
    final Function<K, @Nullable V> mappingFunction;
1131
    final CacheLoader<? super K, V> cacheLoader;
1132
    final @Nullable Function<Set<? extends K>, Map<K, V>> bulkMappingFunction;
1133

1134
    UnboundedLocalLoadingCache(Caffeine<K, V> builder, CacheLoader<? super K, V> cacheLoader) {
1135
      super(builder);
1✔
1136
      this.cacheLoader = cacheLoader;
1✔
1137
      this.mappingFunction = newMappingFunction(cacheLoader);
1✔
1138
      this.bulkMappingFunction = newBulkMappingFunction(cacheLoader);
1✔
1139
    }
1✔
1140

1141
    @Override
1142
    public AsyncCacheLoader<? super K, V> cacheLoader() {
1143
      return cacheLoader;
1✔
1144
    }
1145

1146
    @Override
1147
    public Function<K, @Nullable V> mappingFunction() {
1148
      return mappingFunction;
1✔
1149
    }
1150

1151
    @Override
1152
    public @Nullable Function<Set<? extends K>, Map<K, V>>  bulkMappingFunction() {
1153
      return bulkMappingFunction;
1✔
1154
    }
1155

1156
    @Override
1157
    Object writeReplace() {
1158
      @SuppressWarnings("unchecked")
1159
      var proxy = (SerializationProxy<K, V>) super.writeReplace();
1✔
1160
      proxy.cacheLoader = cacheLoader;
1✔
1161
      return proxy;
1✔
1162
    }
1163

1164
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1165
      throw new InvalidObjectException("Proxy required");
1✔
1166
    }
1167
  }
1168

1169
  /* --------------- Async Cache --------------- */
1170

1171
  static final class UnboundedLocalAsyncCache<K, V> implements LocalAsyncCache<K, V>, Serializable {
1172
    private static final long serialVersionUID = 1;
1173

1174
    final UnboundedLocalCache<K, CompletableFuture<V>> cache;
1175

1176
    @Nullable ConcurrentMap<K, CompletableFuture<V>> mapView;
1177
    @Nullable CacheView<K, V> cacheView;
1178
    @Nullable Policy<K, V> policy;
1179

1180
    @SuppressWarnings("unchecked")
1181
    UnboundedLocalAsyncCache(Caffeine<K, V> builder) {
1✔
1182
      cache = new UnboundedLocalCache<>(
1✔
1183
          (Caffeine<K, CompletableFuture<V>>) builder, /* isAsync= */ true);
1184
    }
1✔
1185

1186
    @Override
1187
    public UnboundedLocalCache<K, CompletableFuture<V>> cache() {
1188
      return cache;
1✔
1189
    }
1190

1191
    @Override
1192
    public ConcurrentMap<K, CompletableFuture<V>> asMap() {
1193
      return (mapView == null) ? (mapView = new AsyncAsMapView<>(this)) : mapView;
1✔
1194
    }
1195

1196
    @Override
1197
    public Cache<K, V> synchronous() {
1198
      return (cacheView == null) ? (cacheView = new CacheView<>(this)) : cacheView;
1✔
1199
    }
1200

1201
    @Override
1202
    public Policy<K, V> policy() {
1203
      @SuppressWarnings("unchecked")
1204
      var castCache = (UnboundedLocalCache<K, V>) cache;
1✔
1205
      Function<CompletableFuture<V>, @Nullable V> transformer = Async::getIfReady;
1✔
1206
      @SuppressWarnings("unchecked")
1207
      var castTransformer = (Function<@Nullable V, @Nullable V>) transformer;
1✔
1208
      return (policy == null)
1✔
1209
          ? (policy = new UnboundedPolicy<>(castCache, castTransformer))
1✔
1210
          : policy;
1✔
1211
    }
1212

1213
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1214
      throw new InvalidObjectException("Proxy required");
1✔
1215
    }
1216

1217
    Object writeReplace() {
1218
      var proxy = new SerializationProxy<K, V>();
1✔
1219
      proxy.isRecordingStats = cache.isRecordingStats;
1✔
1220
      proxy.removalListener = cache.removalListener;
1✔
1221
      proxy.async = true;
1✔
1222
      return proxy;
1✔
1223
    }
1224
  }
1225

1226
  /* --------------- Async Loading Cache --------------- */
1227

1228
  static final class UnboundedLocalAsyncLoadingCache<K, V>
1229
      extends LocalAsyncLoadingCache<K, V> implements Serializable {
1230
    private static final long serialVersionUID = 1;
1231

1232
    final UnboundedLocalCache<K, CompletableFuture<V>> cache;
1233

1234
    @Nullable ConcurrentMap<K, CompletableFuture<V>> mapView;
1235
    @Nullable Policy<K, V> policy;
1236

1237
    @SuppressWarnings("unchecked")
1238
    UnboundedLocalAsyncLoadingCache(Caffeine<K, V> builder, AsyncCacheLoader<? super K, V> loader) {
1239
      super(loader);
1✔
1240
      cache = new UnboundedLocalCache<>(
1✔
1241
          (Caffeine<K, CompletableFuture<V>>) builder, /* isAsync= */ true);
1242
    }
1✔
1243

1244
    @Override
1245
    public LocalCache<K, CompletableFuture<V>> cache() {
1246
      return cache;
1✔
1247
    }
1248

1249
    @Override
1250
    public ConcurrentMap<K, CompletableFuture<V>> asMap() {
1251
      return (mapView == null) ? (mapView = new AsyncAsMapView<>(this)) : mapView;
1✔
1252
    }
1253

1254
    @Override
1255
    public Policy<K, V> policy() {
1256
      @SuppressWarnings("unchecked")
1257
      var castCache = (UnboundedLocalCache<K, V>) cache;
1✔
1258
      Function<CompletableFuture<V>, @Nullable V> transformer = Async::getIfReady;
1✔
1259
      @SuppressWarnings("unchecked")
1260
      var castTransformer = (Function<@Nullable V, @Nullable V>) transformer;
1✔
1261
      return (policy == null)
1✔
1262
          ? (policy = new UnboundedPolicy<>(castCache, castTransformer))
1✔
1263
          : policy;
1✔
1264
    }
1265

1266
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1267
      throw new InvalidObjectException("Proxy required");
1✔
1268
    }
1269

1270
    Object writeReplace() {
1271
      var proxy = new SerializationProxy<K, V>();
1✔
1272
      proxy.isRecordingStats = cache.isRecordingStats();
1✔
1273
      proxy.removalListener = cache.removalListener;
1✔
1274
      proxy.cacheLoader = cacheLoader;
1✔
1275
      proxy.async = true;
1✔
1276
      return proxy;
1✔
1277
    }
1278
  }
1279
}
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