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

ben-manes / caffeine / #4723

18 Jan 2025 01:27AM UTC coverage: 99.044% (-0.01%) from 99.057%
#4723

push

github

ben-manes
Release 3.2.0

7668 of 7742 relevant lines covered (99.04%)

0.99 hits per line

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

99.4
/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
import static java.util.function.Function.identity;
23

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

53
import org.jspecify.annotations.Nullable;
54

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

59
/**
60
 * An in-memory cache that has no capabilities for bounding the map. This implementation provides
61
 * a lightweight wrapper on top of {@link ConcurrentHashMap}.
62
 *
63
 * @author ben.manes@gmail.com (Ben Manes)
64
 */
65
@SuppressWarnings("serial")
66
final class UnboundedLocalCache<K, V> implements LocalCache<K, V> {
67
  static final Logger logger = System.getLogger(UnboundedLocalCache.class.getName());
1✔
68
  static final VarHandle REFRESHES;
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 {
92
    try {
93
      REFRESHES = MethodHandles.lookup()
1✔
94
          .findVarHandle(UnboundedLocalCache.class, "refreshes", ConcurrentMap.class);
1✔
95
    } catch (ReflectiveOperationException e) {
×
96
      throw new ExceptionInInitializerError(e);
×
97
    }
1✔
98
  }
1✔
99

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

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

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

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

122
  /* --------------- Cache --------------- */
123

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

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

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

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

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

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

170
    return Collections.unmodifiableMap(result);
1✔
171
  }
172

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

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

181
  private boolean hasRemovalListener() {
182
    return (removalListener != null);
1✔
183
  }
184

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

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

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

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

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

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

242
  /* --------------- JDK8+ Map extensions --------------- */
243

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

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

253
    // ensures that the removal notification is processed after the removal has completed
254
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
255
    @Nullable K[] notificationKey = (K[]) new Object[1];
1✔
256
    @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
257
    @Nullable V[] notificationValue = (V[]) new Object[1];
1✔
258
    data.replaceAll((key, value) -> {
1✔
259
      if (notificationKey[0] != null) {
1✔
260
        notifyRemoval(notificationKey[0], notificationValue[0], RemovalCause.REPLACED);
1✔
261
        notificationValue[0] = null;
1✔
262
        notificationKey[0] = null;
1✔
263
      }
264

265
      V newValue = requireNonNull(function.apply(key, value));
1✔
266
      if (newValue != value) {
1✔
267
        notificationKey[0] = key;
1✔
268
        notificationValue[0] = value;
1✔
269
      }
270

271
      return newValue;
1✔
272
    });
273
    if (notificationKey[0] != null) {
1✔
274
      notifyRemoval(notificationKey[0], notificationValue[0], RemovalCause.REPLACED);
1✔
275
    }
276
  }
1✔
277

278
  @Override
279
  public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction,
280
      boolean recordStats, boolean recordLoad) {
281
    requireNonNull(mappingFunction);
1✔
282

283
    // optimistic fast path due to computeIfAbsent always locking
284
    @Var V value = data.get(key);
1✔
285
    if (value != null) {
1✔
286
      if (recordStats) {
1✔
287
        statsCounter.recordHits(1);
1✔
288
      }
289
      return value;
1✔
290
    }
291

292
    boolean[] missed = new boolean[1];
1✔
293
    value = data.computeIfAbsent(key, k -> {
1✔
294
      // Do not communicate to CacheWriter on a load
295
      missed[0] = true;
1✔
296
      return recordStats
1✔
297
          ? statsAware(mappingFunction, recordLoad).apply(key)
1✔
298
          : mappingFunction.apply(key);
1✔
299
    });
300
    if (!missed[0] && recordStats) {
1✔
301
      statsCounter.recordHits(1);
1✔
302
    }
303
    return value;
1✔
304
  }
305

306
  @Override
307
  public @Nullable V computeIfPresent(K key,
308
      BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
309
    requireNonNull(remappingFunction);
1✔
310

311
    // optimistic fast path due to computeIfAbsent always locking
312
    if (!data.containsKey(key)) {
1✔
313
      return null;
1✔
314
    }
315

316
    // ensures that the removal notification is processed after the removal has completed
317
    @SuppressWarnings({"rawtypes", "unchecked"})
318
    var oldValue = (V[]) new Object[1];
1✔
319
    boolean[] replaced = new boolean[1];
1✔
320
    V nv = data.computeIfPresent(key, (K k, V value) -> {
1✔
321
      BiFunction<? super K, ? super V, ? extends V> function = statsAware(remappingFunction,
1✔
322
          /* recordLoad= */ true, /* recordLoadFailure= */ true);
323
      V newValue = function.apply(k, value);
1✔
324

325
      replaced[0] = (newValue != null);
1✔
326
      if (newValue != value) {
1✔
327
        oldValue[0] = value;
1✔
328
      }
329

330
      discardRefresh(k);
1✔
331
      return newValue;
1✔
332
    });
333
    if (replaced[0]) {
1✔
334
      notifyOnReplace(key, oldValue[0], nv);
1✔
335
    } else if (oldValue[0] != null) {
1✔
336
      notifyRemoval(key, oldValue[0], RemovalCause.EXPLICIT);
1✔
337
    }
338
    return nv;
1✔
339
  }
340

341
  @Override
342
  public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction,
343
      @Nullable Expiry<? super K, ? super V> expiry, boolean recordLoad,
344
      boolean recordLoadFailure) {
345
    requireNonNull(remappingFunction);
1✔
346
    return remap(key, statsAware(remappingFunction, recordLoad, recordLoadFailure));
1✔
347
  }
348

349
  @Override
350
  public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
351
    requireNonNull(remappingFunction);
1✔
352
    requireNonNull(value);
1✔
353

354
    return remap(key, (k, oldValue) ->
1✔
355
      (oldValue == null) ? value : statsAware(remappingFunction).apply(oldValue, value));
1✔
356
  }
357

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

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

381
      discardRefresh(k);
1✔
382
      return newValue;
1✔
383
    });
384
    if (replaced[0]) {
1✔
385
      notifyOnReplace(key, oldValue[0], nv);
1✔
386
    } else if (oldValue[0] != null) {
1✔
387
      notifyRemoval(key, oldValue[0], RemovalCause.EXPLICIT);
1✔
388
    }
389
    return nv;
1✔
390
  }
391

392
  /* --------------- Concurrent Map --------------- */
393

394
  @Override
395
  public boolean isEmpty() {
396
    return data.isEmpty();
1✔
397
  }
398

399
  @Override
400
  public int size() {
401
    return data.size();
1✔
402
  }
403

404
  @Override
405
  public void clear() {
406
    if (!hasRemovalListener() && ((refreshes == null) || refreshes.isEmpty())) {
1✔
407
      data.clear();
1✔
408
      return;
1✔
409
    }
410
    for (K key : List.copyOf(data.keySet())) {
1✔
411
      remove(key);
1✔
412
    }
1✔
413
  }
1✔
414

415
  @Override
416
  public boolean containsKey(Object key) {
417
    return data.containsKey(key);
1✔
418
  }
419

420
  @Override
421
  public boolean containsValue(Object value) {
422
    return data.containsValue(value);
1✔
423
  }
424

425
  @Override
426
  public @Nullable V get(Object key) {
427
    return getIfPresent(key, /* recordStats= */ false);
1✔
428
  }
429

430
  @Override
431
  public @Nullable V put(K key, V value) {
432
    V oldValue = data.put(key, value);
1✔
433
    notifyOnReplace(key, oldValue, value);
1✔
434
    return oldValue;
1✔
435
  }
436

437
  @Override
438
  public @Nullable V putIfAbsent(K key, V value) {
439
    return data.putIfAbsent(key, value);
1✔
440
  }
441

442
  @Override
443
  public void putAll(Map<? extends K, ? extends V> map) {
444
    if (hasRemovalListener()) {
1✔
445
      map.forEach(this::put);
1✔
446
    } else {
447
      data.putAll(map);
1✔
448
    }
449
  }
1✔
450

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

675
    @Override
676
    public Spliterator<K> spliterator() {
677
      return cache.data.keySet().spliterator();
1✔
678
    }
679

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

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

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

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

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

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

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

723
  /** An adapter to safely externalize the values. */
724
  static final class ValuesView<K, V> extends AbstractCollection<V> {
725
    final UnboundedLocalCache<K, V> cache;
726

727
    ValuesView(UnboundedLocalCache<K, V> cache) {
1✔
728
      this.cache = requireNonNull(cache);
1✔
729
    }
1✔
730

731
    @Override
732
    public boolean isEmpty() {
733
      return cache.isEmpty();
1✔
734
    }
735

736
    @Override
737
    public int size() {
738
      return cache.size();
1✔
739
    }
740

741
    @Override
742
    public void clear() {
743
      cache.clear();
1✔
744
    }
1✔
745

746
    @Override
747
    @SuppressWarnings("SuspiciousMethodCalls")
748
    public boolean contains(Object o) {
749
      return cache.containsValue(o);
1✔
750
    }
751

752
    @Override
753
    public boolean removeAll(Collection<?> collection) {
754
      requireNonNull(collection);
1✔
755
      @Var boolean modified = false;
1✔
756
      for (var entry : cache.data.entrySet()) {
1✔
757
        if (collection.contains(entry.getValue())
1✔
758
            && cache.remove(entry.getKey(), entry.getValue())) {
1✔
759
          modified = true;
1✔
760
        }
761
      }
1✔
762
      return modified;
1✔
763
    }
764

765
    @Override
766
    public boolean remove(Object o) {
767
      if (o == null) {
1✔
768
        return false;
1✔
769
      }
770
      for (var entry : cache.data.entrySet()) {
1✔
771
        if (o.equals(entry.getValue()) && cache.remove(entry.getKey(), entry.getValue())) {
1✔
772
          return true;
1✔
773
        }
774
      }
1✔
775
      return false;
1✔
776
    }
777

778
    @Override
779
    public boolean removeIf(Predicate<? super V> filter) {
780
      requireNonNull(filter);
1✔
781
      @Var boolean removed = false;
1✔
782
      for (var entry : cache.data.entrySet()) {
1✔
783
        if (filter.test(entry.getValue())) {
1✔
784
          removed |= cache.remove(entry.getKey(), entry.getValue());
1✔
785
        }
786
      }
1✔
787
      return removed;
1✔
788
    }
789

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

803
    @Override
804
    public void forEach(Consumer<? super V> action) {
805
      cache.data.values().forEach(action);
1✔
806
    }
1✔
807

808
    @Override
809
    public Iterator<V> iterator() {
810
      return new ValuesIterator<>(cache);
1✔
811
    }
812

813
    @Override
814
    public Spliterator<V> spliterator() {
815
      return cache.data.values().spliterator();
1✔
816
    }
817

818
    @Override
819
    public Object[] toArray() {
820
      return cache.data.values().toArray();
1✔
821
    }
822

823
    @Override
824
    public <T> T[] toArray(T[] array) {
825
      return cache.data.values().toArray(array);
1✔
826
    }
827
  }
828

829
  /** An adapter to safely externalize the value iterator. */
830
  static final class ValuesIterator<K, V> implements Iterator<V> {
831
    final UnboundedLocalCache<K, V> cache;
832
    final Iterator<Entry<K, V>> iterator;
833
    @Nullable Entry<K, V> entry;
834

835
    ValuesIterator(UnboundedLocalCache<K, V> cache) {
1✔
836
      this.iterator = cache.data.entrySet().iterator();
1✔
837
      this.cache = cache;
1✔
838
    }
1✔
839

840
    @Override
841
    public boolean hasNext() {
842
      return iterator.hasNext();
1✔
843
    }
844

845
    @Override
846
    public V next() {
847
      entry = iterator.next();
1✔
848
      return entry.getValue();
1✔
849
    }
850

851
    @Override
852
    public void remove() {
853
      if (entry == null) {
1✔
854
        throw new IllegalStateException();
1✔
855
      }
856
      cache.remove(entry.getKey());
1✔
857
      entry = null;
1✔
858
    }
1✔
859
  }
860

861
  /** An adapter to safely externalize the entries. */
862
  static final class EntrySetView<K, V> extends AbstractSet<Entry<K, V>> {
863
    final UnboundedLocalCache<K, V> cache;
864

865
    EntrySetView(UnboundedLocalCache<K, V> cache) {
1✔
866
      this.cache = requireNonNull(cache);
1✔
867
    }
1✔
868

869
    @Override
870
    public boolean isEmpty() {
871
      return cache.isEmpty();
1✔
872
    }
873

874
    @Override
875
    public int size() {
876
      return cache.size();
1✔
877
    }
878

879
    @Override
880
    public void clear() {
881
      cache.clear();
1✔
882
    }
1✔
883

884
    @Override
885
    @SuppressWarnings("SuspiciousMethodCalls")
886
    public boolean contains(Object o) {
887
      if (!(o instanceof Entry<?, ?>)) {
1✔
888
        return false;
1✔
889
      }
890
      var entry = (Entry<?, ?>) o;
1✔
891
      var key = entry.getKey();
1✔
892
      var value = entry.getValue();
1✔
893
      if ((key == null) || (value == null)) {
1✔
894
        return false;
1✔
895
      }
896
      V cachedValue = cache.get(key);
1✔
897
      return (cachedValue != null) && cachedValue.equals(value);
1✔
898
    }
899

900
    @Override
901
    public boolean removeAll(Collection<?> collection) {
902
      requireNonNull(collection);
1✔
903
      @Var boolean modified = false;
1✔
904
      if ((collection instanceof Set<?>) && (collection.size() > size())) {
1✔
905
        for (var entry : this) {
1✔
906
          if (collection.contains(entry)) {
1✔
907
            modified |= remove(entry);
1✔
908
          }
909
        }
1✔
910
      } else {
911
        for (var o : collection) {
1✔
912
          modified |= remove(o);
1✔
913
        }
1✔
914
      }
915
      return modified;
1✔
916
    }
917

918
    @Override
919
    @SuppressWarnings("SuspiciousMethodCalls")
920
    public boolean remove(Object o) {
921
      if (!(o instanceof Entry<?, ?>)) {
1✔
922
        return false;
1✔
923
      }
924
      var entry = (Entry<?, ?>) o;
1✔
925
      var key = entry.getKey();
1✔
926
      return (key != null) && cache.remove(key, entry.getValue());
1✔
927
    }
928

929
    @Override
930
    public boolean removeIf(Predicate<? super Entry<K, V>> filter) {
931
      requireNonNull(filter);
1✔
932
      @Var boolean removed = false;
1✔
933
      for (var entry : cache.data.entrySet()) {
1✔
934
        if (filter.test(entry)) {
1✔
935
          removed |= cache.remove(entry.getKey(), entry.getValue());
1✔
936
        }
937
      }
1✔
938
      return removed;
1✔
939
    }
940

941
    @Override
942
    public boolean retainAll(Collection<?> collection) {
943
      requireNonNull(collection);
1✔
944
      @Var boolean modified = false;
1✔
945
      for (var entry : this) {
1✔
946
        if (!collection.contains(entry) && remove(entry)) {
1✔
947
          modified = true;
1✔
948
        }
949
      }
1✔
950
      return modified;
1✔
951
    }
952

953
    @Override
954
    public Iterator<Entry<K, V>> iterator() {
955
      return new EntryIterator<>(cache);
1✔
956
    }
957

958
    @Override
959
    public Spliterator<Entry<K, V>> spliterator() {
960
      return new EntrySpliterator<>(cache);
1✔
961
    }
962
  }
963

964
  /** An adapter to safely externalize the entry iterator. */
965
  static final class EntryIterator<K, V> implements Iterator<Entry<K, V>> {
966
    final UnboundedLocalCache<K, V> cache;
967
    final Iterator<Entry<K, V>> iterator;
968
    @Nullable Entry<K, V> entry;
969

970
    EntryIterator(UnboundedLocalCache<K, V> cache) {
1✔
971
      this.iterator = cache.data.entrySet().iterator();
1✔
972
      this.cache = cache;
1✔
973
    }
1✔
974

975
    @Override
976
    public boolean hasNext() {
977
      return iterator.hasNext();
1✔
978
    }
979

980
    @Override
981
    public Entry<K, V> next() {
982
      entry = iterator.next();
1✔
983
      return new WriteThroughEntry<>(cache, entry.getKey(), entry.getValue());
1✔
984
    }
985

986
    @Override
987
    public void remove() {
988
      if (entry == null) {
1✔
989
        throw new IllegalStateException();
1✔
990
      }
991
      cache.remove(entry.getKey());
1✔
992
      entry = null;
1✔
993
    }
1✔
994
  }
995

996
  /** An adapter to safely externalize the entry spliterator. */
997
  static final class EntrySpliterator<K, V> implements Spliterator<Entry<K, V>> {
998
    final Spliterator<Entry<K, V>> spliterator;
999
    final UnboundedLocalCache<K, V> cache;
1000

1001
    EntrySpliterator(UnboundedLocalCache<K, V> cache) {
1002
      this(cache, cache.data.entrySet().spliterator());
1✔
1003
    }
1✔
1004

1005
    EntrySpliterator(UnboundedLocalCache<K, V> cache, Spliterator<Entry<K, V>> spliterator) {
1✔
1006
      this.spliterator = requireNonNull(spliterator);
1✔
1007
      this.cache = requireNonNull(cache);
1✔
1008
    }
1✔
1009

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

1019
    @Override
1020
    public boolean tryAdvance(Consumer<? super Entry<K, V>> action) {
1021
      requireNonNull(action);
1✔
1022
      return spliterator.tryAdvance(entry -> {
1✔
1023
        var e = new WriteThroughEntry<>(cache, entry.getKey(), entry.getValue());
1✔
1024
        action.accept(e);
1✔
1025
      });
1✔
1026
    }
1027

1028
    @Override
1029
    public @Nullable EntrySpliterator<K, V> trySplit() {
1030
      Spliterator<Entry<K, V>> split = spliterator.trySplit();
1✔
1031
      return (split == null) ? null : new EntrySpliterator<>(cache, split);
1✔
1032
    }
1033

1034
    @Override
1035
    public long estimateSize() {
1036
      return spliterator.estimateSize();
1✔
1037
    }
1038

1039
    @Override
1040
    public int characteristics() {
1041
      return spliterator.characteristics();
1✔
1042
    }
1043
  }
1044

1045
  /* --------------- Manual Cache --------------- */
1046

1047
  static class UnboundedLocalManualCache<K, V> implements LocalManualCache<K, V>, Serializable {
1048
    private static final long serialVersionUID = 1;
1049

1050
    final UnboundedLocalCache<K, V> cache;
1051
    @Nullable Policy<K, V> policy;
1052

1053
    UnboundedLocalManualCache(Caffeine<K, V> builder) {
1✔
1054
      cache = new UnboundedLocalCache<>(builder, /* isAsync= */ false);
1✔
1055
    }
1✔
1056

1057
    @Override
1058
    public final UnboundedLocalCache<K, V> cache() {
1059
      return cache;
1✔
1060
    }
1061

1062
    @Override
1063
    public final Policy<K, V> policy() {
1064
      if (policy == null) {
1✔
1065
        @SuppressWarnings("NullAway")
1066
        Function<@Nullable V, @Nullable V> identity = identity();
1✔
1067
        policy = new UnboundedPolicy<>(cache, identity);
1✔
1068
      }
1069
      return policy;
1✔
1070
    }
1071

1072
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1073
      throw new InvalidObjectException("Proxy required");
1✔
1074
    }
1075

1076
    Object writeReplace() {
1077
      var proxy = new SerializationProxy<K, V>();
1✔
1078
      proxy.isRecordingStats = cache.isRecordingStats;
1✔
1079
      proxy.removalListener = cache.removalListener;
1✔
1080
      return proxy;
1✔
1081
    }
1082
  }
1083

1084
  /** An eviction policy that supports no bounding. */
1085
  static final class UnboundedPolicy<K, V> implements Policy<K, V> {
1086
    final Function<@Nullable V, @Nullable V> transformer;
1087
    final UnboundedLocalCache<K, V> cache;
1088

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

1133
  /* --------------- Loading Cache --------------- */
1134

1135
  static final class UnboundedLocalLoadingCache<K, V> extends UnboundedLocalManualCache<K, V>
1136
      implements LocalLoadingCache<K, V> {
1137
    private static final long serialVersionUID = 1;
1138

1139
    final Function<K, V> mappingFunction;
1140
    final CacheLoader<? super K, V> cacheLoader;
1141
    final @Nullable Function<Set<? extends K>, Map<K, V>> bulkMappingFunction;
1142

1143
    UnboundedLocalLoadingCache(Caffeine<K, V> builder, CacheLoader<? super K, V> cacheLoader) {
1144
      super(builder);
1✔
1145
      this.cacheLoader = cacheLoader;
1✔
1146
      this.mappingFunction = newMappingFunction(cacheLoader);
1✔
1147
      this.bulkMappingFunction = newBulkMappingFunction(cacheLoader);
1✔
1148
    }
1✔
1149

1150
    @Override
1151
    public AsyncCacheLoader<? super K, V> cacheLoader() {
1152
      return cacheLoader;
1✔
1153
    }
1154

1155
    @Override
1156
    public Function<K, V> mappingFunction() {
1157
      return mappingFunction;
1✔
1158
    }
1159

1160
    @Override
1161
    public @Nullable Function<Set<? extends K>, Map<K, V>>  bulkMappingFunction() {
1162
      return bulkMappingFunction;
1✔
1163
    }
1164

1165
    @Override
1166
    Object writeReplace() {
1167
      @SuppressWarnings("unchecked")
1168
      var proxy = (SerializationProxy<K, V>) super.writeReplace();
1✔
1169
      proxy.cacheLoader = cacheLoader;
1✔
1170
      return proxy;
1✔
1171
    }
1172

1173
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1174
      throw new InvalidObjectException("Proxy required");
1✔
1175
    }
1176
  }
1177

1178
  /* --------------- Async Cache --------------- */
1179

1180
  static final class UnboundedLocalAsyncCache<K, V> implements LocalAsyncCache<K, V>, Serializable {
1181
    private static final long serialVersionUID = 1;
1182

1183
    final UnboundedLocalCache<K, CompletableFuture<V>> cache;
1184

1185
    @Nullable ConcurrentMap<K, CompletableFuture<V>> mapView;
1186
    @Nullable CacheView<K, V> cacheView;
1187
    @Nullable Policy<K, V> policy;
1188

1189
    @SuppressWarnings("unchecked")
1190
    UnboundedLocalAsyncCache(Caffeine<K, V> builder) {
1✔
1191
      cache = new UnboundedLocalCache<>(
1✔
1192
          (Caffeine<K, CompletableFuture<V>>) builder, /* isAsync= */ true);
1193
    }
1✔
1194

1195
    @Override
1196
    public UnboundedLocalCache<K, CompletableFuture<V>> cache() {
1197
      return cache;
1✔
1198
    }
1199

1200
    @Override
1201
    public ConcurrentMap<K, CompletableFuture<V>> asMap() {
1202
      return (mapView == null) ? (mapView = new AsyncAsMapView<>(this)) : mapView;
1✔
1203
    }
1204

1205
    @Override
1206
    public Cache<K, V> synchronous() {
1207
      return (cacheView == null) ? (cacheView = new CacheView<>(this)) : cacheView;
1✔
1208
    }
1209

1210
    @Override
1211
    public Policy<K, V> policy() {
1212
      @SuppressWarnings("unchecked")
1213
      var castCache = (UnboundedLocalCache<K, V>) cache;
1✔
1214
      Function<CompletableFuture<V>, @Nullable V> transformer = Async::getIfReady;
1✔
1215
      @SuppressWarnings({"NullAway", "unchecked", "Varifier"})
1216
      Function<@Nullable V, @Nullable V> castTransformer = (Function<V, V>) transformer;
1✔
1217
      return (policy == null)
1✔
1218
          ? (policy = new UnboundedPolicy<>(castCache, castTransformer))
1✔
1219
          : policy;
1✔
1220
    }
1221

1222
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1223
      throw new InvalidObjectException("Proxy required");
1✔
1224
    }
1225

1226
    Object writeReplace() {
1227
      var proxy = new SerializationProxy<K, V>();
1✔
1228
      proxy.isRecordingStats = cache.isRecordingStats;
1✔
1229
      proxy.removalListener = cache.removalListener;
1✔
1230
      proxy.async = true;
1✔
1231
      return proxy;
1✔
1232
    }
1233
  }
1234

1235
  /* --------------- Async Loading Cache --------------- */
1236

1237
  static final class UnboundedLocalAsyncLoadingCache<K, V>
1238
      extends LocalAsyncLoadingCache<K, V> implements Serializable {
1239
    private static final long serialVersionUID = 1;
1240

1241
    final UnboundedLocalCache<K, CompletableFuture<V>> cache;
1242

1243
    @Nullable ConcurrentMap<K, CompletableFuture<V>> mapView;
1244
    @Nullable Policy<K, V> policy;
1245

1246
    @SuppressWarnings("unchecked")
1247
    UnboundedLocalAsyncLoadingCache(Caffeine<K, V> builder, AsyncCacheLoader<? super K, V> loader) {
1248
      super(loader);
1✔
1249
      cache = new UnboundedLocalCache<>(
1✔
1250
          (Caffeine<K, CompletableFuture<V>>) builder, /* isAsync= */ true);
1251
    }
1✔
1252

1253
    @Override
1254
    public LocalCache<K, CompletableFuture<V>> cache() {
1255
      return cache;
1✔
1256
    }
1257

1258
    @Override
1259
    public ConcurrentMap<K, CompletableFuture<V>> asMap() {
1260
      return (mapView == null) ? (mapView = new AsyncAsMapView<>(this)) : mapView;
1✔
1261
    }
1262

1263
    @Override
1264
    public Policy<K, V> policy() {
1265
      @SuppressWarnings("unchecked")
1266
      var castCache = (UnboundedLocalCache<K, V>) cache;
1✔
1267
      Function<CompletableFuture<V>, @Nullable V> transformer = Async::getIfReady;
1✔
1268
      @SuppressWarnings({"NullAway", "unchecked", "Varifier"})
1269
      Function<@Nullable V, @Nullable V> castTransformer = (Function<V, V>) transformer;
1✔
1270
      return (policy == null)
1✔
1271
          ? (policy = new UnboundedPolicy<>(castCache, castTransformer))
1✔
1272
          : policy;
1✔
1273
    }
1274

1275
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
1276
      throw new InvalidObjectException("Proxy required");
1✔
1277
    }
1278

1279
    Object writeReplace() {
1280
      var proxy = new SerializationProxy<K, V>();
1✔
1281
      proxy.isRecordingStats = cache.isRecordingStats();
1✔
1282
      proxy.removalListener = cache.removalListener;
1✔
1283
      proxy.cacheLoader = cacheLoader;
1✔
1284
      proxy.async = true;
1✔
1285
      return proxy;
1✔
1286
    }
1287
  }
1288
}
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