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

ben-manes / caffeine / #5842

25 Oct 2026 08:18PM UTC coverage: 99.979% (-0.02%) from 100.0%
#5842

push

github

ben-manes
style polish

4881 of 4890 branches covered (99.82%)

17 of 17 new or added lines in 2 files covered. (100.0%)

2 existing lines in 2 files now uncovered.

9347 of 9349 relevant lines covered (99.98%)

1.0 hits per line

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

99.88
/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalAsyncCache.java
1
/*
2
 * Copyright 2018 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.Caffeine.requireArgument;
20
import static com.github.benmanes.caffeine.cache.Caffeine.requireState;
21
import static com.github.benmanes.caffeine.cache.Caffeine.toUnchecked;
22
import static com.github.benmanes.caffeine.cache.LocalCache.castNonNull;
23
import static java.util.Locale.US;
24
import static java.util.Objects.requireNonNull;
25

26
import java.io.IOException;
27
import java.io.InvalidObjectException;
28
import java.io.ObjectInputStream;
29
import java.io.Serializable;
30
import java.lang.System.Logger;
31
import java.lang.System.Logger.Level;
32
import java.util.AbstractCollection;
33
import java.util.AbstractSet;
34
import java.util.Collection;
35
import java.util.Collections;
36
import java.util.Iterator;
37
import java.util.LinkedHashMap;
38
import java.util.Map;
39
import java.util.NoSuchElementException;
40
import java.util.Objects;
41
import java.util.Set;
42
import java.util.Spliterator;
43
import java.util.concurrent.CancellationException;
44
import java.util.concurrent.CompletableFuture;
45
import java.util.concurrent.CompletionException;
46
import java.util.concurrent.ConcurrentMap;
47
import java.util.concurrent.Executor;
48
import java.util.concurrent.TimeoutException;
49
import java.util.function.BiConsumer;
50
import java.util.function.BiFunction;
51
import java.util.function.Consumer;
52
import java.util.function.Function;
53
import java.util.function.Predicate;
54

55
import org.jspecify.annotations.Nullable;
56

57
import com.github.benmanes.caffeine.cache.LocalAsyncCache.AsyncBulkCompleter.NullMapCompletionException;
58
import com.github.benmanes.caffeine.cache.stats.CacheStats;
59
import com.google.errorprone.annotations.CanIgnoreReturnValue;
60
import com.google.errorprone.annotations.Var;
61

62
/**
63
 * This class provides a skeletal implementation of the {@link AsyncCache} interface to minimize the
64
 * effort required to implement a {@link LocalCache}.
65
 *
66
 * @author ben.manes@gmail.com (Ben Manes)
67
 */
68
interface LocalAsyncCache<K, V> extends AsyncCache<K, V> {
69
  Logger logger = System.getLogger(LocalAsyncCache.class.getName());
1 ✔
70

71
  /** Returns the backing {@link LocalCache} data store. */
72
  LocalCache<K, CompletableFuture<V>> cache();
73

74
  /** Returns the policy supported by this implementation and its configuration. */
75
  Policy<K, V> policy();
76

77
  @Override
78
  default @Nullable CompletableFuture<V> getIfPresent(K key) {
79
    return cache().getIfPresent(key, /* recordStats= */ true);
1 ✔
80
  }
81

82
  @Override
83
  default CompletableFuture<V> get(K key, Function<? super K, ? extends V> mappingFunction) {
84
    requireNonNull(mappingFunction);
1 ✔
85
    return get(key, (k, executor) -> CompletableFuture.supplyAsync(
1 ✔
86
        () -> mappingFunction.apply(k), executor));
1 ✔
87
  }
88

89
  @Override
90
  default CompletableFuture<V> get(K key, BiFunction<? super K, ? super Executor,
91
      ? extends CompletableFuture<? extends V>> mappingFunction) {
92
    requireNonNull(mappingFunction);
1 ✔
93
    return get(key, mappingFunction, /* recordStats= */ true);
1 ✔
94
  }
95

96
  default CompletableFuture<V> get(K key, BiFunction<? super K, ? super Executor,
97
      ? extends CompletableFuture<? extends V>> mappingFunction, boolean recordStats) {
98
    CompletableFuture<V> present = cache().getIfPresent(key, /* recordStats= */ false);
1 ✔
99
    if (present != null) {
1 ✔
100
      if (recordStats) {
1 ✔
101
        cache().statsCounter().recordHits(1);
1 ✔
102
      }
103
      return present;
1 ✔
104
    }
105

106
    var deferred = new boolean[1];
1 ✔
107
    long startTime = cache().statsTicker().read();
1 ✔
108
    @SuppressWarnings({"rawtypes", "unchecked"})
109
    @Nullable CompletableFuture<? extends V>[] result = new CompletableFuture[1];
1 ✔
110
    CompletableFuture<V> future = cache().computeIfAbsent(key, k -> {
1 ✔
111
      @SuppressWarnings("unchecked")
112
      var castedResult = (CompletableFuture<V>) mappingFunction.apply(key, cache().executor());
1 ✔
113
      deferred[0] = !Async.isReady(castedResult);
1 ✔
114
      result[0] = castedResult;
1 ✔
115
      return requireNonNull(castedResult);
1 ✔
116
    }, recordStats, /* recordLoad= */ false);
117
    if (result[0] != null) {
1 ✔
118
      handleCompletion(key, result[0], startTime, deferred[0], /* computed= */ true);
1 ✔
119
    }
120
    return requireNonNull(future);
1 ✔
121
  }
122

123
  @Override
124
  default CompletableFuture<Map<K, V>> getAll(Iterable<? extends K> keys,
125
      Function<? super Set<? extends K>, ? extends Map<? extends K, ? extends V>> mappingFunction) {
126
    requireNonNull(mappingFunction);
1 ✔
127
    return getAll(keys, (keysToLoad, executor) ->
1 ✔
128
        CompletableFuture.supplyAsync(() -> mappingFunction.apply(keysToLoad), executor));
1 ✔
129
  }
130

131
  @Override
132
  @SuppressWarnings("FutureReturnValueIgnored")
133
  default CompletableFuture<Map<K, V>> getAll(Iterable<? extends K> keys,
134
      BiFunction<? super Set<? extends K>, ? super Executor,
135
          ? extends CompletableFuture<? extends Map<? extends K, ? extends V>>> mappingFunction) {
136
    requireNonNull(mappingFunction);
1 ✔
137
    requireNonNull(keys);
1 ✔
138

139
    int initialCapacity = calculateHashMapCapacity(keys);
1 ✔
140
    var futures = new LinkedHashMap<K, @Nullable CompletableFuture<@Nullable V>>(initialCapacity);
1 ✔
141
    for (K key : keys) {
1 ✔
142
      futures.put(requireNonNull(key), null);
1 ✔
143
    }
1 ✔
144

145
    var proxies = new LinkedHashMap<K, CompletableFuture<V>>(initialCapacity);
1 ✔
146
    try {
147
      for (var entry : futures.entrySet()) {
1 ✔
148
        K key = entry.getKey();
1 ✔
149
        @Var CompletableFuture<V> future = cache().getIfPresent(key, /* recordStats= */ false);
1 ✔
150
        if (future == null) {
1 ✔
151
          var proxy = new CompletableFuture<V>();
1 ✔
152
          future = cache().putIfAbsent(key, proxy);
1 ✔
153
          if (future == null) {
1 ✔
154
            future = proxy;
1 ✔
155
            proxies.put(key, proxy);
1 ✔
156
          }
157
        }
158
        @SuppressWarnings("NullAway")
159
        CompletableFuture<@Nullable V> castedFuture = future;
1 ✔
160
        entry.setValue(castedFuture);
1 ✔
161
      }
1 ✔
162
    } catch (Throwable t) {
1 ✔
163
      for (var entry : proxies.entrySet()) {
1 ✔
164
        cache().remove(entry.getKey(), entry.getValue());
1 ✔
165
        entry.getValue().obtrudeException(t);
1 ✔
166
      }
1 ✔
167
      throw t;
1 ✔
168
    }
1 ✔
169
    cache().statsCounter().recordMisses(proxies.size());
1 ✔
170
    cache().statsCounter().recordHits(futures.size() - proxies.size());
1 ✔
171

172
    @SuppressWarnings("NullAway")
173
    Map<K, CompletableFuture<@Nullable V>> results = futures;
1 ✔
174
    if (proxies.isEmpty()) {
1 ✔
175
      return composeResult(results);
1 ✔
176
    }
177

178
    @SuppressWarnings("NullAway")
179
    Map<K, CompletableFuture<@Nullable V>> pending = proxies;
1 ✔
180
    var completer = new AsyncBulkCompleter<>(cache(), pending);
1 ✔
181
    try {
182
      var loader = mappingFunction.apply(
1 ✔
183
          Collections.unmodifiableSet(proxies.keySet()), cache().executor());
1 ✔
184
      return loader.handle(completer).thenCompose(ignored -> composeResult(results));
1 ✔
185
    } catch (Throwable t) {
1 ✔
186
      throw completer.error(t);
1 ✔
187
    }
188
  }
189

190
  /**
191
   * Returns a future that waits for all of the dependent futures to complete and returns the
192
   * combined mapping if successful. If any future fails then it is automatically removed from
193
   * the cache if still present.
194
   */
195
  static <K, V> CompletableFuture<Map<K, V>> composeResult(
196
      Map<K, CompletableFuture<@Nullable V>> futures) {
197
    if (futures.isEmpty()) {
1 ✔
198
      @SuppressWarnings({"ImmutableMapOf", "RedundantUnmodifiable"})
199
      Map<K, V> emptyMap = Collections.unmodifiableMap(Collections.emptyMap());
1 ✔
200
      return CompletableFuture.completedFuture(emptyMap);
1 ✔
201
    }
202
    @SuppressWarnings("rawtypes")
203
    CompletableFuture<?>[] array = futures.values().toArray(new CompletableFuture[0]);
1 ✔
204
    return CompletableFuture.allOf(array).thenApply(ignored -> {
1 ✔
205
      var result = new LinkedHashMap<K, V>(calculateHashMapCapacity(futures.size()));
1 ✔
206
      futures.forEach((key, future) -> {
1 ✔
207
        V value = future.getNow(null);
1 ✔
208
        if (value != null) {
1 ✔
209
          result.put(key, value);
1 ✔
210
        }
211
      });
1 ✔
212
      return Collections.unmodifiableMap(result);
1 ✔
213
    });
214
  }
215

216
  @Override
217
  @SuppressWarnings("FutureReturnValueIgnored")
218
  default void put(K key, CompletableFuture<? extends @Nullable V> valueFuture) {
219
    long startTime = cache().statsTicker().read();
1 ✔
220

221
    @SuppressWarnings("unchecked")
222
    var castedFuture = (CompletableFuture<V>) valueFuture;
1 ✔
223
    boolean deferred = !Async.isReady(castedFuture);
1 ✔
224
    CompletableFuture<V> prior = cache().put(key, castedFuture);
1 ✔
225
    if (prior != castedFuture) {
1 ✔
226
      handleCompletion(key, valueFuture, startTime, deferred, /* computed= */ false);
1 ✔
227
    } else if (deferred) {
1 ✔
228
      handleRewrite(key, castedFuture);
1 ✔
229
    }
230
  }
1 ✔
231

232
  /**
233
   * Registers the callback that completes the insertion of the value once it is available.
234
   *
235
   * @param key the key that the future was inserted with
236
   * @param valueFuture the inserted future
237
   * @param startTime the time when the load started, in nanoseconds
238
   * @param deferred whether the insertion deferred the entry's weight and expiration to the
239
   *        completion, which it does when the value was not yet available to evaluate them
240
   * @param computed whether the caller computed the value, which a write does not
241
   */
242
  @SuppressWarnings({"FutureReturnValueIgnored", "ResultOfMethodCallIgnored"})
243
  default void handleCompletion(K key, CompletableFuture<? extends @Nullable V> valueFuture,
244
      long startTime, boolean deferred, boolean computed) {
245
    boolean recordLoad = computed || deferred;
1 ✔
246
    valueFuture.whenComplete((value, error) -> {
1 ✔
247
      long loadTime = cache().statsTicker().read() - startTime;
1 ✔
248
      if (value == null) {
1 ✔
249
        if ((error != null) && !(error instanceof CancellationException)
1 ✔
250
            && !(error instanceof TimeoutException)) {
251
          logger.log(Level.WARNING, "Exception thrown during asynchronous load", error);
1 ✔
252
        }
253
        if (recordLoad) {
1 ✔
254
          cache().statsCounter().recordLoadFailure(loadTime);
1 ✔
255
        }
256
        cache().remove(key, valueFuture);
1 ✔
257
      } else if (!Async.isReady(valueFuture)) {
1 ✔
258
        logger.log(Level.ERROR, String.format(US, "An invalid state was detected, occurring when "
1 ✔
259
            + "the future's dependent action has completed successfully with a value, but the "
260
            + "future remains either in-flight or in a failed completion state. This may occur "
261
            + "when using a custom future that does not abide by the CompletableFuture contract "
262
            + "(key: %s, key type: %s, value type: %s, future: %s, cache type: %s).",
263
            key, key.getClass().getName(), value.getClass().getName(), valueFuture,
1 ✔
264
            cache().getClass().getSimpleName()), new IllegalStateException());
1 ✔
265
        if (recordLoad) {
1 ✔
266
          cache().statsCounter().recordLoadFailure(loadTime);
1 ✔
267
        }
268
        cache().remove(key, valueFuture);
1 ✔
269
      } else {
270
        @SuppressWarnings("unchecked")
271
        var castedFuture = (CompletableFuture<V>) valueFuture;
1 ✔
272

273
        try {
274
          if (deferred) {
1 ✔
275
            // update the weight and expiration timestamps
276
            cache().replace(key, castedFuture, castedFuture,
1 ✔
277
                /* shouldDiscardRefresh= */ false, /* quietly= */ true);
278
          }
279
          if (recordLoad) {
1 ✔
280
            cache().statsCounter().recordLoadSuccess(loadTime);
1 ✔
281
          }
282
        } catch (Throwable t) {
1 ✔
283
          logger.log(Level.WARNING, "Exception thrown during asynchronous load", t);
1 ✔
284
          cache().statsCounter().recordLoadFailure(loadTime);
1 ✔
285
          cache().remove(key, valueFuture);
1 ✔
286
        }
1 ✔
287
      }
288
    });
1 ✔
289
  }
1 ✔
290

291
  /**
292
   * Finalizes a future that was written over itself if it completed during the write. The write
293
   * weighs the value before it locks the entry, so it may have overwritten the weight that the
294
   * insertion's callback finalized and a future still in-flight is left to that callback.
295
   *
296
   * @param key the key that the future was written with
297
   * @param valueFuture the future that replaced itself
298
   */
299
  @SuppressWarnings("ResultOfMethodCallIgnored")
300
  default void handleRewrite(K key, CompletableFuture<V> valueFuture) {
301
    if (!Async.isReady(valueFuture)) {
1 ✔
302
      return;
1 ✔
303
    }
304
    try {
305
      cache().replace(key, valueFuture, valueFuture,
1 ✔
306
          /* shouldDiscardRefresh= */ false, /* quietly= */ true);
307
    } catch (Throwable t) {
1 ✔
308
      logger.log(Level.WARNING, "Exception thrown during asynchronous load", t);
1 ✔
309
      cache().remove(key, valueFuture);
1 ✔
310
    }
1 ✔
311
  }
1 ✔
312

313
  /** A function executed asynchronously after a bulk load completes. */
314
  final class AsyncBulkCompleter<K, V>
315
      implements BiFunction<@Nullable Map<? extends K, ? extends V>,
316
          @Nullable Throwable, @Nullable Map<? extends K, ? extends V>> {
317
    @SuppressWarnings("ImmutableMemberCollection")
318
    private final Map<K, CompletableFuture<@Nullable V>> proxies;
319
    private final LocalCache<K, CompletableFuture<V>> cache;
320
    private final long startTime;
321

322
    AsyncBulkCompleter(LocalCache<K, CompletableFuture<V>> cache,
323
        Map<K, CompletableFuture<@Nullable V>> proxies) {
1 ✔
324
      this.startTime = cache.statsTicker().read();
1 ✔
325
      this.proxies = proxies;
1 ✔
326
      this.cache = cache;
1 ✔
327
    }
1 ✔
328

329
    @Override
330
    @CanIgnoreReturnValue
331
    public @Nullable Map<? extends K, ? extends V> apply(
332
        @Nullable Map<? extends K, ? extends V> result, @Nullable Throwable error) {
333
      long loadTime = cache.statsTicker().read() - startTime;
1 ✔
334
      var failure = handleResponse(result, error);
1 ✔
335

336
      if (failure == null) {
1 ✔
337
        if (requireNonNull(result).isEmpty()) {
1 ✔
338
          cache.statsCounter().recordLoadFailure(loadTime);
1 ✔
339
        } else {
340
          cache.statsCounter().recordLoadSuccess(loadTime);
1 ✔
341
        }
342
        return result;
1 ✔
343
      }
344

345
      cache.statsCounter().recordLoadFailure(loadTime);
1 ✔
346
      throw toUnchecked(failure);
1 ✔
347
    }
348

349
    public RuntimeException error(Throwable error) {
350
      long loadTime = cache.statsTicker().read() - startTime;
1 ✔
351
      var failure = requireNonNull(handleResponse(/* result= */ null, error));
1 ✔
352
      cache.statsCounter().recordLoadFailure(loadTime);
1 ✔
353
      return toUnchecked(failure);
1 ✔
354
    }
355

356
    private @Nullable Throwable handleResponse(
357
        @Nullable Map<? extends K, ? extends V> result, @Nullable Throwable error) {
358
      if (result == null) {
1 ✔
359
        var failure = (error == null) ? new NullMapCompletionException() : error;
1 ✔
360
        failProxies(failure);
1 ✔
361
        return failure;
1 ✔
362
      }
363

364
      Map<K, V> snapshot;
365
      try {
366
        snapshot = snapshotOf(result);
1 ✔
367
      } catch (Throwable t) {
1 ✔
368
        failProxies(t);
1 ✔
369
        return t;
1 ✔
370
      }
1 ✔
371

372
      var failure = fillProxies(snapshot);
1 ✔
373
      return addNewEntries(snapshot, failure);
1 ✔
374
    }
375

376
    /** Returns a copy of the computed result, rejecting a null or duplicate mapping. */
377
    private static <K, V> Map<K, V> snapshotOf(Map<? extends K, ? extends V> result) {
378
      var snapshot = new LinkedHashMap<K, V>(calculateHashMapCapacity(result.size()));
1 ✔
379
      for (var entry : result.entrySet()) {
1 ✔
380
        K key = requireNonNull(entry.getKey());
1 ✔
381
        V value = requireNonNull(entry.getValue());
1 ✔
382
        requireArgument(snapshot.put(key, value) == null, "duplicate key: %s", key);
1 ✔
383
      }
1 ✔
384
      return snapshot;
1 ✔
385
    }
386

387
    /** Removes and exceptionally completes every requested proxy. */
388
    private void failProxies(Throwable failure) {
389
      for (var entry : proxies.entrySet()) {
1 ✔
390
        cache.remove(entry.getKey(), entry.getValue());
1 ✔
391
        entry.getValue().obtrudeException(failure);
1 ✔
392
      }
1 ✔
393
      if (!(failure instanceof CancellationException) && !(failure instanceof TimeoutException)) {
1 ✔
394
        logger.log(Level.WARNING, "Exception thrown during asynchronous load", failure);
1 ✔
395
      }
396
    }
1 ✔
397

398
    /** Populates the proxies with the computed result. */
399
    private @Nullable Throwable fillProxies(Map<? extends K, ? extends V> result) {
400
      @Var Throwable error = null;
1 ✔
401
      for (var entry : proxies.entrySet()) {
1 ✔
402
        var key = entry.getKey();
1 ✔
403
        var value = result.get(key);
1 ✔
404
        var future = entry.getValue();
1 ✔
405
        future.obtrudeValue(value);
1 ✔
406

407
        if (value == null) {
1 ✔
408
          cache.remove(key, future);
1 ✔
409
        } else {
410
          @SuppressWarnings("NullAway")
411
          CompletableFuture<V> castedFuture = future;
1 ✔
412

413
          try {
414
            // update the weight and expiration timestamps; a same-instance finalization is not a
415
            // mutation, so an in-flight refresh is preserved (mirrors handleCompletion)
416
            cache.replace(key, castedFuture, castedFuture,
1 ✔
417
                /* shouldDiscardRefresh= */ false, /* quietly= */ true);
418
          } catch (Throwable t) {
1 ✔
419
            logger.log(Level.WARNING, "Exception thrown during asynchronous load", t);
1 ✔
420
            cache.remove(key, future);
1 ✔
421
            if (error == null) {
1 ✔
422
              error = t;
1 ✔
423
            } else if (error != t) {
1 ✔
424
              error.addSuppressed(t);
1 ✔
425
            }
426
          }
1 ✔
427
        }
428
      }
1 ✔
429
      return error;
1 ✔
430
    }
431

432
    /** Adds to the cache any extra entries computed that were not requested. */
433
    private @Nullable Throwable addNewEntries(
434
        Map<? extends K, ? extends V> result, @Nullable Throwable failure) {
435
      @Var Throwable error = failure;
1 ✔
436
      for (var entry : result.entrySet()) {
1 ✔
437
        var key = entry.getKey();
1 ✔
438
        if (!proxies.containsKey(key)) {
1 ✔
439
          V value = entry.getValue();
1 ✔
440
          try {
441
            cache.put(key, CompletableFuture.completedFuture(value));
1 ✔
442
          } catch (Throwable t) {
1 ✔
443
            logger.log(Level.WARNING, "Exception thrown during asynchronous load", t);
1 ✔
444
            if (error == null) {
1 ✔
445
              error = t;
1 ✔
446
            } else if (error != t) {
1 ✔
447
              error.addSuppressed(t);
1 ✔
448
            }
449
          }
1 ✔
450
        }
451
      }
1 ✔
452
      return error;
1 ✔
453
    }
454

455
    static final class NullMapCompletionException extends CompletionException {
1 ✔
456
      private static final long serialVersionUID = 1L;
457
    }
458
  }
459

460
  /* --------------- Asynchronous view --------------- */
461
  final class AsyncAsMapView<K, V> implements ConcurrentMap<K, CompletableFuture<V>> {
462
    private @Nullable Set<Entry<K, CompletableFuture<V>>> entrySet;
463
    private final LocalAsyncCache<K, V> asyncCache;
464

465
    AsyncAsMapView(LocalAsyncCache<K, V> asyncCache) {
1 ✔
466
      this.asyncCache = requireNonNull(asyncCache);
1 ✔
467
    }
1 ✔
468
    @Override public boolean isEmpty() {
469
      return asyncCache.cache().isEmpty();
1 ✔
470
    }
471
    @Override public int size() {
472
      return asyncCache.cache().size();
1 ✔
473
    }
474
    @Override public void clear() {
475
      asyncCache.cache().clear();
1 ✔
476
    }
1 ✔
477
    @Override public boolean containsKey(@Nullable Object key) {
478
      return asyncCache.cache().containsKey(key);
1 ✔
479
    }
480
    @SuppressWarnings("CollectionUndefinedEquality")
481
    @Override public boolean containsValue(@Nullable Object value) {
482
      return asyncCache.cache().containsValue(value);
1 ✔
483
    }
484
    @Override public @Nullable CompletableFuture<V> get(@Nullable Object key) {
485
      return asyncCache.cache().get(key);
1 ✔
486
    }
487
    @Override public @Nullable CompletableFuture<V> putIfAbsent(K key, CompletableFuture<V> value) {
488
      boolean deferred = !Async.isReady(value);
1 ✔
489
      CompletableFuture<V> prior = asyncCache.cache().putIfAbsent(key, value);
1 ✔
490
      if (prior == null) {
1 ✔
491
        long startTime = asyncCache.cache().statsTicker().read();
1 ✔
492
        asyncCache.handleCompletion(key, value, startTime, deferred, /* computed= */ false);
1 ✔
493
      }
494
      return prior;
1 ✔
495
    }
496
    @Override public @Nullable CompletableFuture<V> put(K key, CompletableFuture<V> value) {
497
      boolean deferred = !Async.isReady(value);
1 ✔
498
      CompletableFuture<V> prior = asyncCache.cache().put(key, value);
1 ✔
499
      if (prior != value) {
1 ✔
500
        long startTime = asyncCache.cache().statsTicker().read();
1 ✔
501
        asyncCache.handleCompletion(key, value, startTime, deferred, /* computed= */ false);
1 ✔
502
      } else if (deferred) {
1 ✔
503
        asyncCache.handleRewrite(key, value);
1 ✔
504
      }
505
      return prior;
1 ✔
506
    }
507
    @SuppressWarnings({"FutureReturnValueIgnored", "ResultOfMethodCallIgnored"})
508
    @Override public void putAll(Map<? extends K, ? extends CompletableFuture<V>> map) {
509
      map.forEach(this::put);
1 ✔
510
    }
1 ✔
511
    @Override public @Nullable CompletableFuture<V> replace(K key, CompletableFuture<V> value) {
512
      boolean deferred = !Async.isReady(value);
1 ✔
513
      CompletableFuture<V> prior = asyncCache.cache().replace(key, value);
1 ✔
514
      if ((prior != null) && (prior != value)) {
1 ✔
515
        long startTime = asyncCache.cache().statsTicker().read();
1 ✔
516
        asyncCache.handleCompletion(key, value, startTime, deferred, /* computed= */ false);
1 ✔
517
      } else if ((prior != null) && deferred) {
1 ✔
518
        asyncCache.handleRewrite(key, value);
1 ✔
519
      }
520
      return prior;
1 ✔
521
    }
522
    @Override
523
    public boolean replace(K key, CompletableFuture<V> oldValue, CompletableFuture<V> newValue) {
524
      boolean deferred = !Async.isReady(newValue);
1 ✔
525
      boolean replaced = asyncCache.cache().replace(key, oldValue, newValue);
1 ✔
526
      if (replaced && (newValue != oldValue)) {
1 ✔
527
        long startTime = asyncCache.cache().statsTicker().read();
1 ✔
528
        asyncCache.handleCompletion(key, newValue, startTime, deferred, /* computed= */ false);
1 ✔
529
      } else if (replaced && deferred) {
1 ✔
530
        asyncCache.handleRewrite(key, newValue);
1 ✔
531
      }
532
      return replaced;
1 ✔
533
    }
534
    @SuppressWarnings({"CheckReturnValue", "FutureReturnValueIgnored"})
535
    @Override public void replaceAll(BiFunction<? super K, ? super CompletableFuture<V>,
536
        ? extends CompletableFuture<V>> function) {
537
      requireNonNull(function);
1 ✔
538
      for (K key : keySet()) {
1 ✔
539
        computeIfPresent(key, (k, oldValue) -> requireNonNull(function.apply(k, oldValue)));
1 ✔
540
      }
1 ✔
541
    }
1 ✔
542
    @Override public @Nullable CompletableFuture<V> remove(@Nullable Object key) {
543
      return asyncCache.cache().remove(key);
1 ✔
544
    }
545
    @Override public boolean remove(@Nullable Object key, @Nullable Object value) {
546
      return asyncCache.cache().remove(key, value);
1 ✔
547
    }
548
    @Override public CompletableFuture<V> computeIfAbsent(K key,
549
        Function<? super K, ? extends @Nullable CompletableFuture<V>> mappingFunction) {
550
      requireNonNull(mappingFunction);
1 ✔
551
      var deferred = new boolean[1];
1 ✔
552
      @SuppressWarnings({"rawtypes", "unchecked"})
553
      @Nullable CompletableFuture<V>[] result = new CompletableFuture[1];
1 ✔
554
      long startTime = asyncCache.cache().statsTicker().read();
1 ✔
555
      Function<K, @Nullable CompletableFuture<V>> function = k -> {
1 ✔
556
        result[0] = mappingFunction.apply(k);
1 ✔
557
        deferred[0] = !Async.isReady(result[0]);
1 ✔
558
        return result[0];
1 ✔
559
      };
560
      CompletableFuture<V> future = asyncCache.cache().computeIfAbsent(
1 ✔
561
          key, function, /* recordStats= */ true, /* recordLoad= */ false);
562
      if (result[0] != null) {
1 ✔
563
        asyncCache.handleCompletion(key, result[0], startTime, deferred[0], /* computed= */ true);
1 ✔
564
      }
565
      return castNonNull(future);
1 ✔
566
    }
567
    @Override public @Nullable CompletableFuture<V> computeIfPresent(K key,
568
        BiFunction<? super K, ? super CompletableFuture<V>,
569
            ? extends @Nullable CompletableFuture<V>> remappingFunction) {
570
      requireNonNull(remappingFunction);
1 ✔
571
      return compute(key, (k, oldValue) ->
1 ✔
572
          (oldValue == null) ? null : remappingFunction.apply(k, oldValue));
1 ✔
573
    }
574
    @Override public @Nullable CompletableFuture<V> compute(K key,
575
        BiFunction<? super K, ? super @Nullable CompletableFuture<V>,
576
            ? extends @Nullable CompletableFuture<V>> remappingFunction) {
577
      requireNonNull(remappingFunction);
1 ✔
578

579
      var deferred = new boolean[1];
1 ✔
580
      @SuppressWarnings({"rawtypes", "unchecked"})
581
      @Nullable CompletableFuture<V>[] prior = new CompletableFuture[1];
1 ✔
582
      long startTime = asyncCache.cache().statsTicker().read();
1 ✔
583
      CompletableFuture<V> result = asyncCache.cache().compute(
1 ✔
584
          key, (K k, @Nullable CompletableFuture<V> oldValue) -> {
585
            var computed = remappingFunction.apply(k, oldValue);
1 ✔
586
            deferred[0] = !Async.isReady(computed);
1 ✔
587
            prior[0] = oldValue;
1 ✔
588
            return computed;
1 ✔
589
          }, asyncCache.cache().expiry(), /* recordLoad= */ false);
1 ✔
590

591
      if ((result != null) && (result != prior[0])) {
1 ✔
592
        asyncCache.handleCompletion(key, result, startTime, deferred[0], /* computed= */ true);
1 ✔
593
      }
594
      return result;
1 ✔
595
    }
596
    @Override public @Nullable CompletableFuture<V> merge(K key, CompletableFuture<V> value,
597
        BiFunction<? super CompletableFuture<V>, ? super CompletableFuture<V>,
598
            ? extends @Nullable CompletableFuture<V>> remappingFunction) {
599
      requireNonNull(value);
1 ✔
600
      requireNonNull(remappingFunction);
1 ✔
601
      return compute(key, (k, oldValue) ->
1 ✔
602
          (oldValue == null) ? value : remappingFunction.apply(oldValue, value));
1 ✔
603
    }
604
    @Override public void forEach(BiConsumer<? super K, ? super CompletableFuture<V>> action) {
605
      asyncCache.cache().forEach(action);
1 ✔
606
    }
1 ✔
607
    @Override public Set<K> keySet() {
608
      return asyncCache.cache().keySet();
1 ✔
609
    }
610
    @Override public Collection<CompletableFuture<V>> values() {
611
      return asyncCache.cache().values();
1 ✔
612
    }
613
    @Override public Set<Entry<K, CompletableFuture<V>>> entrySet() {
614
      var es = entrySet;
1 ✔
615
      return (es == null) ? (entrySet = new AsyncEntrySet()) : es;
1 ✔
616
    }
617
    @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
618
    @Override public boolean equals(@Nullable Object o) {
619
      return (o == this) || asyncCache.cache().equals(o);
1 ✔
620
    }
621
    @Override public int hashCode() {
622
      return asyncCache.cache().hashCode();
1 ✔
623
    }
624
    @Override public String toString() {
625
      return asyncCache.cache().toString();
1 ✔
626
    }
627

628
    private final class AsyncEntrySet extends AbstractSet<Entry<K, CompletableFuture<V>>> {
1 ✔
629
      @Override public int size() {
630
        return AsyncAsMapView.this.size();
1 ✔
631
      }
632
      @Override public boolean isEmpty() {
633
        return AsyncAsMapView.this.isEmpty();
1 ✔
634
      }
635
      @Override public void clear() {
636
        AsyncAsMapView.this.clear();
1 ✔
637
      }
1 ✔
638
      @Override public boolean contains(@Nullable Object o) {
639
        return asyncCache.cache().entrySet().contains(o);
1 ✔
640
      }
641
      @Override public boolean containsAll(Collection<?> collection) {
642
        return (collection == this) || asyncCache.cache().entrySet().containsAll(collection);
1 ✔
643
      }
644
      @Override public boolean remove(@Nullable Object o) {
645
        return asyncCache.cache().entrySet().remove(o);
1 ✔
646
      }
647
      @Override public boolean removeAll(Collection<?> collection) {
648
        return asyncCache.cache().entrySet().removeAll(collection);
1 ✔
649
      }
650
      @Override public boolean retainAll(Collection<?> collection) {
651
        return asyncCache.cache().entrySet().retainAll(collection);
1 ✔
652
      }
653
      @Override public boolean removeIf(Predicate<? super Entry<K, CompletableFuture<V>>> filter) {
654
        return asyncCache.cache().entrySet().removeIf(filter);
1 ✔
655
      }
656
      @Override public Iterator<Entry<K, CompletableFuture<V>>> iterator() {
657
        return new AsyncEntryIterator();
1 ✔
658
      }
659
      @Override public Spliterator<Entry<K, CompletableFuture<V>>> spliterator() {
660
        return new AsyncEntrySpliterator();
1 ✔
661
      }
662
    }
663

664
    private final class AsyncEntryIterator implements Iterator<Entry<K, CompletableFuture<V>>> {
1 ✔
665
      final Iterator<Entry<K, CompletableFuture<V>>> iterator =
1 ✔
666
          asyncCache.cache().entrySet().iterator();
1 ✔
667

668
      @Override public boolean hasNext() {
669
        return iterator.hasNext();
1 ✔
670
      }
671
      @Override public Entry<K, CompletableFuture<V>> next() {
672
        var entry = iterator.next();
1 ✔
673
        return new WriteThroughEntry<>(AsyncAsMapView.this, entry.getKey(), entry.getValue());
1 ✔
674
      }
675
      @Override public void remove() {
676
        iterator.remove();
1 ✔
677
      }
1 ✔
678
    }
679

680
    private final class AsyncEntrySpliterator
681
        implements Spliterator<Entry<K, CompletableFuture<V>>> {
682
      final Spliterator<Entry<K, CompletableFuture<V>>> spliterator;
683

684
      AsyncEntrySpliterator() {
685
        this(asyncCache.cache().entrySet().spliterator());
1 ✔
686
      }
1 ✔
687
      AsyncEntrySpliterator(Spliterator<Entry<K, CompletableFuture<V>>> spliterator) {
1 ✔
688
        this.spliterator = requireNonNull(spliterator);
1 ✔
689
      }
1 ✔
690
      @Override public boolean tryAdvance(Consumer<? super Entry<K, CompletableFuture<V>>> action) {
691
        requireNonNull(action);
1 ✔
692
        return spliterator.tryAdvance(entry -> action.accept(
1 ✔
693
            new WriteThroughEntry<>(AsyncAsMapView.this, entry.getKey(), entry.getValue())));
1 ✔
694
      }
695
      @Override public void forEachRemaining(
696
          Consumer<? super Entry<K, CompletableFuture<V>>> action) {
697
        requireNonNull(action);
1 ✔
698
        spliterator.forEachRemaining(entry -> action.accept(
1 ✔
699
            new WriteThroughEntry<>(AsyncAsMapView.this, entry.getKey(), entry.getValue())));
1 ✔
700
      }
1 ✔
701
      @Override public @Nullable Spliterator<Entry<K, CompletableFuture<V>>> trySplit() {
702
        var split = spliterator.trySplit();
1 ✔
703
        return (split == null) ? null : new AsyncEntrySpliterator(split);
1 ✔
704
      }
705
      @Override public long estimateSize() {
706
        return spliterator.estimateSize();
1 ✔
707
      }
708
      @Override public int characteristics() {
709
        return spliterator.characteristics();
1 ✔
710
      }
711
    }
712
  }
713

714
  /* --------------- Synchronous view --------------- */
715
  final class CacheView<K, V> extends AbstractCacheView<K, V> {
716
    private static final long serialVersionUID = 1L;
717

718
    @SuppressWarnings("serial")
719
    final LocalAsyncCache<K, V> asyncCache;
720

721
    CacheView(LocalAsyncCache<K, V> asyncCache) {
1 ✔
722
      this.asyncCache = requireNonNull(asyncCache);
1 ✔
723
    }
1 ✔
724
    @Override LocalAsyncCache<K, V> asyncCache() {
725
      return asyncCache;
1 ✔
726
    }
727
  }
728

729
  abstract class AbstractCacheView<K, V> implements Cache<K, V>, Serializable {
1 ✔
730
    private static final long serialVersionUID = 1L;
731

732
    transient @Nullable ConcurrentMap<K, V> asMapView;
733

734
    abstract LocalAsyncCache<K, V> asyncCache();
735

736
    @Override
737
    public @Nullable V getIfPresent(K key) {
738
      var future = asyncCache().cache().getIfPresent(key, /* recordStats= */ false);
1 ✔
739
      var value = Async.getIfReady(future);
1 ✔
740
      if (value == null) {
1 ✔
741
        asyncCache().cache().statsCounter().recordMisses(1);
1 ✔
742
      } else {
743
        asyncCache().cache().statsCounter().recordHits(1);
1 ✔
744
      }
745
      return value;
1 ✔
746
    }
747

748
    @Override
749
    public Map<K, V> getAllPresent(Iterable<? extends K> keys) {
750
      var result = new LinkedHashMap<K, @Nullable V>(calculateHashMapCapacity(keys));
1 ✔
751
      for (K key : keys) {
1 ✔
752
        result.put(key, null);
1 ✔
753
      }
1 ✔
754

755
      int uniqueKeys = result.size();
1 ✔
756
      for (var iter = result.entrySet().iterator(); iter.hasNext();) {
1 ✔
757
        Map.Entry<K, @Nullable V> entry = iter.next();
1 ✔
758

759
        CompletableFuture<V> future = asyncCache().cache().get(entry.getKey());
1 ✔
760
        V value = Async.getIfReady(future);
1 ✔
761
        if (value == null) {
1 ✔
762
          iter.remove();
1 ✔
763
        } else {
764
          entry.setValue(value);
1 ✔
765
        }
766
      }
1 ✔
767
      asyncCache().cache().statsCounter().recordHits(result.size());
1 ✔
768
      asyncCache().cache().statsCounter().recordMisses(uniqueKeys - result.size());
1 ✔
769

770
      @SuppressWarnings({"NullableProblems", "NullAway"})
771
      Map<K, V> unmodifiable = Collections.unmodifiableMap(result);
1 ✔
772
      return unmodifiable;
1 ✔
773
    }
774

775
    @Override
776
    public V get(K key, Function<? super K, ? extends V> mappingFunction) {
777
      return resolve(asyncCache().get(key, mappingFunction));
1 ✔
778
    }
779

780
    @Override
781
    public Map<K, V> getAll(
782
        Iterable<? extends K> keys,
783
        Function<
784
            ? super Set<? extends K>,
785
            ? extends Map<? extends K, ? extends V>> mappingFunction) {
786
      return resolve(asyncCache().getAll(keys, mappingFunction));
1 ✔
787
    }
788

789
    @SuppressWarnings({"PMD.AvoidThrowingNullPointerException",
790
      "PMD.PreserveStackTrace", "UnusedException"})
791
    protected static <T> T resolve(CompletableFuture<T> future) {
792
      try {
793
        return future.join();
1 ✔
794
      } catch (NullMapCompletionException e) {
1 ✔
795
        throw new NullPointerException("null map");
1 ✔
796
      } catch (CompletionException e) {
1 ✔
797
        var cause = e.getCause();
1 ✔
798
        if (cause instanceof RuntimeException) {
1 ✔
799
          throw (RuntimeException) cause;
1 ✔
800
        } else if (cause instanceof Error) {
1 ✔
801
          throw (Error) cause;
1 ✔
802
        }
803
        throw e;
1 ✔
804
      }
805
    }
806

807
    @Override
808
    public void put(K key, V value) {
809
      requireNonNull(value);
1 ✔
810
      asyncCache().cache().put(key, CompletableFuture.completedFuture(value));
1 ✔
811
    }
1 ✔
812

813
    @Override
814
    public void putAll(Map<? extends K, ? extends V> map) {
815
      map.forEach(this::put);
1 ✔
816
    }
1 ✔
817

818
    @Override
819
    public void invalidate(K key) {
820
      asyncCache().cache().remove(key);
1 ✔
821
    }
1 ✔
822

823
    @Override
824
    public void invalidateAll(Iterable<? extends K> keys) {
825
      asyncCache().cache().invalidateAll(keys);
1 ✔
826
    }
1 ✔
827

828
    @Override
829
    public void invalidateAll() {
830
      asyncCache().cache().clear();
1 ✔
831
    }
1 ✔
832

833
    @Override
834
    public long estimatedSize() {
835
      return asyncCache().cache().estimatedSize();
1 ✔
836
    }
837

838
    @Override
839
    public CacheStats stats() {
840
      return asyncCache().cache().statsCounter().snapshot();
1 ✔
841
    }
842

843
    @Override
844
    public void cleanUp() {
845
      asyncCache().cache().cleanUp();
1 ✔
846
    }
1 ✔
847

848
    @Override
849
    public Policy<K, V> policy() {
850
      return asyncCache().policy();
1 ✔
851
    }
852

853
    @Override
854
    public ConcurrentMap<K, V> asMap() {
855
      var mv = asMapView;
1 ✔
856
      return (mv == null) ? (asMapView = new AsMapView<>(asyncCache().cache())) : mv;
1 ✔
857
    }
858

859
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
860
      throw new InvalidObjectException("Proxy required");
1 ✔
861
    }
862

863
    @SuppressWarnings("unused")
864
    private void readObjectNoData() throws InvalidObjectException {
865
      throw new InvalidObjectException("Proxy required");
1 ✔
866
    }
867

868
    Object writeReplace() {
869
      return new SyncViewProxy<>(asyncCache());
1 ✔
870
    }
871
  }
872

873
  final class AsMapView<K, V> implements ConcurrentMap<K, V> {
874
    final LocalCache<K, CompletableFuture<V>> delegate;
875

876
    @Nullable Set<K> keys;
877
    @Nullable Collection<V> values;
878
    @Nullable Set<Entry<K, V>> entries;
879

880
    AsMapView(LocalCache<K, CompletableFuture<V>> delegate) {
1 ✔
881
      this.delegate = delegate;
1 ✔
882
    }
1 ✔
883

884
    @Override
885
    public boolean isEmpty() {
886
      return delegate.isEmpty();
1 ✔
887
    }
888

889
    @Override
890
    public int size() {
891
      return delegate.size();
1 ✔
892
    }
893

894
    @Override
895
    public void clear() {
896
      delegate.clear();
1 ✔
897
    }
1 ✔
898

899
    @Override
900
    public boolean containsKey(@Nullable Object key) {
901
      requireNonNull(key);
1 ✔
902
      return Async.isReady(delegate.getIfPresentQuietly(key));
1 ✔
903
    }
904

905
    @Override
906
    public boolean containsValue(@Nullable Object value) {
907
      requireNonNull(value);
1 ✔
908

909
      for (CompletableFuture<V> valueFuture : delegate.values()) {
1 ✔
910
        if (value.equals(Async.getIfReady(valueFuture))) {
1 ✔
911
          return true;
1 ✔
912
        }
913
      }
1 ✔
914
      return false;
1 ✔
915
    }
916

917
    @Override
918
    public @Nullable V get(@Nullable Object key) {
919
      return Async.getIfReady(delegate.get(key));
1 ✔
920
    }
921

922
    @Override
923
    @SuppressWarnings("ResultOfMethodCallIgnored")
924
    public @Nullable V putIfAbsent(K key, V value) {
925
      requireNonNull(value);
1 ✔
926

927
      // Keep in sync with BoundedVarExpiration.putIfAbsentAsync(key, value, duration, unit)
928
      var probe = delegate.putIfAbsent(key, CompletableFuture.completedFuture(value));
1 ✔
929
      V present = Async.getIfReady(probe);
1 ✔
930
      if ((probe == null) || (present != null)) {
1 ✔
931
        return present;
1 ✔
932
      }
933

934
      for (;;) {
935
        var priorFuture = delegate.getIfPresentQuietly(key);
1 ✔
936
        if (priorFuture != null) {
1 ✔
937
          if (!priorFuture.isDone()) {
1 ✔
938
            Async.getWhenSuccessful(priorFuture);
1 ✔
939
            continue;
1 ✔
940
          }
941

942
          V prior = Async.getWhenSuccessful(priorFuture);
1 ✔
943
          if (prior != null) {
1 !
UNCOV
944
            return prior;
×
945
          }
946
        }
947

948
        boolean[] added = { false };
1 ✔
949
        var hints = new LocalCache.RemapHints();
1 ✔
950
        CompletableFuture<V> computed = delegate.compute(
1 ✔
951
            key, (K k, @Nullable CompletableFuture<V> valueFuture) -> {
952
              added[0] = (valueFuture == null)
1 ✔
953
                  || (valueFuture.isDone() && (Async.getIfReady(valueFuture) == null));
1 ✔
954
              if (added[0]) {
1 ✔
955
                return CompletableFuture.completedFuture(value);
1 ✔
956
              }
957
              hints.preserveEntry();
1 ✔
958
              return valueFuture;
1 ✔
959
            }, delegate.expiry(), /* recordLoad= */ false, hints);
1 ✔
960

961
        if (added[0]) {
1 ✔
962
          return null;
1 ✔
963
        } else {
964
          V prior = Async.getWhenSuccessful(computed);
1 ✔
965
          if (prior != null) {
1 ✔
966
            return prior;
1 ✔
967
          }
968
        }
969
      }
1 ✔
970
    }
971

972
    @Override
973
    @SuppressWarnings("ResultOfMethodCallIgnored")
974
    public void putAll(Map<? extends K, ? extends V> map) {
975
      map.forEach(this::put);
1 ✔
976
    }
1 ✔
977

978
    @Override
979
    public @Nullable V put(K key, V value) {
980
      requireNonNull(value);
1 ✔
981
      CompletableFuture<V> oldValueFuture =
1 ✔
982
          delegate.put(key, CompletableFuture.completedFuture(value));
1 ✔
983
      return Async.getWhenSuccessful(oldValueFuture);
1 ✔
984
    }
985

986
    @Override
987
    public @Nullable V remove(@Nullable Object key) {
988
      CompletableFuture<V> oldValueFuture = delegate.remove(key);
1 ✔
989
      return Async.getWhenSuccessful(oldValueFuture);
1 ✔
990
    }
991

992
    @Override
993
    public boolean remove(@Nullable Object key, @Nullable Object value) {
994
      requireNonNull(key);
1 ✔
995
      if (value == null) {
1 ✔
996
        return false;
1 ✔
997
      }
998

999
      @SuppressWarnings("unchecked")
1000
      var castedKey = (K) key;
1 ✔
1001
      boolean[] removed = { false };
1 ✔
1002
      remapWhenSettled(castedKey, (oldValue, oldValueFuture) -> {
1 ✔
1003
        removed[0] = Objects.equals(value, oldValue);
1 ✔
1004
        return ((oldValue == null) || removed[0]) ? null : oldValueFuture;
1 ✔
1005
      });
1006
      return removed[0];
1 ✔
1007
    }
1008

1009
    @Override
1010
    public @Nullable V replace(K key, V value) {
1011
      requireNonNull(value);
1 ✔
1012

1013
      @SuppressWarnings({"unchecked", "Varifier"})
1014
      @Nullable V[] oldValue = (V[]) new Object[1];
1 ✔
1015
      remapWhenSettled(key, (current, oldValueFuture) -> {
1 ✔
1016
        oldValue[0] = current;
1 ✔
1017
        return (current == null) ? null : CompletableFuture.completedFuture(value);
1 ✔
1018
      });
1019
      return oldValue[0];
1 ✔
1020
    }
1021

1022
    @Override
1023
    public boolean replace(K key, V oldValue, V newValue) {
1024
      requireNonNull(oldValue);
1 ✔
1025
      requireNonNull(newValue);
1 ✔
1026

1027
      boolean[] replaced = { false };
1 ✔
1028
      remapWhenSettled(key, (current, oldValueFuture) -> {
1 ✔
1029
        replaced[0] = Objects.equals(oldValue, current);
1 ✔
1030
        return replaced[0] ? CompletableFuture.completedFuture(newValue) : oldValueFuture;
1 ✔
1031
      });
1032
      return replaced[0];
1 ✔
1033
    }
1034

1035
    @Override
1036
    @SuppressWarnings("ResultOfMethodCallIgnored")
1037
    public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
1038
      requireNonNull(function);
1 ✔
1039
      for (K key : keySet()) {
1 ✔
1040
        computeIfPresent(key, (k, oldValue) ->
1 ✔
1041
            requireNonNull(function.apply(k, oldValue)), /* recordLoad= */ false);
1 ✔
1042
      }
1 ✔
1043
    }
1 ✔
1044

1045
    /**
1046
     * Applies the remapping to the entry once its value has settled, retrying while a load is in
1047
     * flight. A remapping that returns the future it was given is a no-op, leaving the entry's
1048
     * metadata and any in-flight refresh intact.
1049
     *
1050
     * @param key the key whose entry is to be remapped
1051
     * @param remapping computes the future to store from the settled value, or {@code null} to
1052
     *        remove the entry
1053
     */
1054
    @SuppressWarnings("ResultOfMethodCallIgnored")
1055
    private void remapWhenSettled(K key,
1056
        BiFunction<@Nullable V, CompletableFuture<V>, @Nullable CompletableFuture<V>> remapping) {
1057
      boolean[] done = { false };
1 ✔
1058
      for (;;) {
1059
        CompletableFuture<V> future = delegate.getIfPresentQuietly(key);
1 ✔
1060
        if ((future == null) || future.isCompletedExceptionally()) {
1 ✔
1061
          return;
1 ✔
1062
        }
1063

1064
        Async.getWhenSuccessful(future);
1 ✔
1065
        var hints = new LocalCache.RemapHints();
1 ✔
1066
        delegate.compute(key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1 ✔
1067
          if (oldValueFuture == null) {
1 ✔
1068
            done[0] = true;
1 ✔
1069
            return null;
1 ✔
1070
          } else if (!oldValueFuture.isDone()) {
1 ✔
1071
            hints.preserveEntry();
1 ✔
1072
            return oldValueFuture;
1 ✔
1073
          }
1074

1075
          done[0] = true;
1 ✔
1076
          var remapped = remapping.apply(Async.getIfReady(oldValueFuture), oldValueFuture);
1 ✔
1077
          if (remapped == oldValueFuture) {
1 ✔
1078
            hints.preserveEntry();
1 ✔
1079
          }
1080
          return remapped;
1 ✔
1081
        }, delegate.expiry(), /* recordLoad= */ false, hints);
1 ✔
1082

1083
        if (done[0]) {
1 ✔
1084
          return;
1 ✔
1085
        }
1086
      }
1 ✔
1087
    }
1088

1089
    @Override
1090
    @SuppressWarnings("ResultOfMethodCallIgnored")
1091
    public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
1092
      requireNonNull(mappingFunction);
1 ✔
1093

1094
      @Var CompletableFuture<V> priorFuture = null;
1 ✔
1095
      for (;;) {
1096
        priorFuture = (priorFuture == null)
1 ✔
1097
            ? delegate.get(key)
1 ✔
1098
            : delegate.getIfPresentQuietly(key);
1 ✔
1099
        if (priorFuture != null) {
1 ✔
1100
          if (!priorFuture.isDone()) {
1 ✔
1101
            Async.getWhenSuccessful(priorFuture);
1 ✔
1102
            continue;
1 ✔
1103
          }
1104

1105
          V prior = Async.getWhenSuccessful(priorFuture);
1 ✔
1106
          if (prior != null) {
1 ✔
1107
            delegate.statsCounter().recordHits(1);
1 ✔
1108
            return prior;
1 ✔
1109
          }
1110
        }
1111

1112
        @SuppressWarnings({"rawtypes", "unchecked"})
1113
        CompletableFuture<V>[] future = new CompletableFuture[1];
1 ✔
1114
        var hints = new LocalCache.RemapHints();
1 ✔
1115
        CompletableFuture<V> computed = delegate.compute(
1 ✔
1116
            key, (K k, @Nullable CompletableFuture<V> valueFuture) -> {
1117
              if ((valueFuture != null)
1 ✔
1118
                  && (!valueFuture.isDone() || (Async.getIfReady(valueFuture) != null))) {
1 ✔
1119
                hints.preserveEntry();
1 ✔
1120
                return valueFuture;
1 ✔
1121
              }
1122

1123
              V newValue = delegate.statsAware(mappingFunction, /* recordLoad= */ true).apply(key);
1 ✔
1124
              if (newValue == null) {
1 ✔
1125
                return null;
1 ✔
1126
              }
1127
              future[0] = CompletableFuture.completedFuture(newValue);
1 ✔
1128
              return future[0];
1 ✔
1129
            }, delegate.expiry(), /* recordLoad= */ false, hints);
1 ✔
1130

1131
        V result = Async.getWhenSuccessful(computed);
1 ✔
1132
        if ((computed == future[0]) || (result != null)) {
1 ✔
1133
          return castNonNull(result);
1 ✔
1134
        }
1135
      }
1 ✔
1136
    }
1137

1138
    @Override
1139
    public @Nullable V computeIfPresent(K key,
1140
        BiFunction<? super K, ? super V, ? extends @Nullable V> remappingFunction) {
1141
      return computeIfPresent(key, remappingFunction, /* recordLoad= */ true);
1 ✔
1142
    }
1143

1144
    @SuppressWarnings("ResultOfMethodCallIgnored")
1145
    private @Nullable V computeIfPresent(K key,
1146
        BiFunction<? super K, ? super V, ? extends @Nullable V> remappingFunction,
1147
        boolean recordLoad) {
1148
      requireNonNull(remappingFunction);
1 ✔
1149

1150
      @SuppressWarnings({"unchecked", "Varifier"})
1151
      @Nullable V[] newValue = (@Nullable V[]) new Object[1];
1 ✔
1152
      for (;;) {
1153
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
1 ✔
1154

1155
        var hints = new LocalCache.RemapHints();
1 ✔
1156
        CompletableFuture<V> valueFuture = delegate.compute(
1 ✔
1157
            key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1158
              if (oldValueFuture == null) {
1 ✔
1159
                return null;
1 ✔
1160
              } else if (!oldValueFuture.isDone()) {
1 ✔
1161
                hints.preserveEntry();
1 ✔
1162
                return oldValueFuture;
1 ✔
1163
              }
1164

1165
              V oldValue = Async.getIfReady(oldValueFuture);
1 ✔
1166
              if (oldValue == null) {
1 ✔
1167
                return null;
1 ✔
1168
              }
1169

1170
              BiFunction<? super K, ? super V, ? extends @Nullable V> function =
1 ✔
1171
                  delegate.statsAware(remappingFunction, recordLoad);
1 ✔
1172
              newValue[0] = function.apply(key, oldValue);
1 ✔
1173
              return (newValue[0] == null) ? null : CompletableFuture.completedFuture(newValue[0]);
1 ✔
1174
            }, delegate.expiry(), /* recordLoad= */ false, hints);
1 ✔
1175

1176
        if (newValue[0] != null) {
1 ✔
1177
          return newValue[0];
1 ✔
1178
        } else if (valueFuture == null) {
1 ✔
1179
          return null;
1 ✔
1180
        }
1181
      }
1 ✔
1182
    }
1183

1184
    @Override
1185
    @SuppressWarnings("ResultOfMethodCallIgnored")
1186
    public @Nullable V compute(K key,
1187
        BiFunction<? super K, ? super @Nullable V, ? extends @Nullable V> remappingFunction) {
1188
      // Keep in sync with BoundedVarExpiration.computeAsync(key, remappingFunction, expiry)
1189
      requireNonNull(remappingFunction);
1 ✔
1190

1191
      @SuppressWarnings({"unchecked", "Varifier"})
1192
      @Nullable V[] newValue = (@Nullable V[]) new Object[1];
1 ✔
1193
      for (;;) {
1194
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
1 ✔
1195

1196
        var hints = new LocalCache.RemapHints();
1 ✔
1197
        CompletableFuture<V> valueFuture = delegate.compute(
1 ✔
1198
            key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1199
              if ((oldValueFuture != null) && !oldValueFuture.isDone()) {
1 ✔
1200
                hints.preserveEntry();
1 ✔
1201
                return oldValueFuture;
1 ✔
1202
              }
1203

1204
              V oldValue = Async.getIfReady(oldValueFuture);
1 ✔
1205
              BiFunction<? super K, ? super @Nullable V, ? extends @Nullable V> function =
1 ✔
1206
                  delegate.statsAware(remappingFunction, /* recordLoad= */ true);
1 ✔
1207
              newValue[0] = function.apply(key, oldValue);
1 ✔
1208
              return (newValue[0] == null) ? null : CompletableFuture.completedFuture(newValue[0]);
1 ✔
1209
            }, delegate.expiry(), /* recordLoad= */ false, hints);
1 ✔
1210

1211
        if (newValue[0] != null) {
1 ✔
1212
          return newValue[0];
1 ✔
1213
        } else if (valueFuture == null) {
1 ✔
1214
          return null;
1 ✔
1215
        }
1216
      }
1 ✔
1217
    }
1218

1219
    @Override
1220
    @SuppressWarnings("ResultOfMethodCallIgnored")
1221
    public @Nullable V merge(K key, V value,
1222
        BiFunction<? super V, ? super V, ? extends @Nullable V> remappingFunction) {
1223
      requireNonNull(value);
1 ✔
1224
      requireNonNull(remappingFunction);
1 ✔
1225

1226
      CompletableFuture<V> newValueFuture = CompletableFuture.completedFuture(value);
1 ✔
1227
      boolean[] merged = { false };
1 ✔
1228
      for (;;) {
1229
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
1 ✔
1230

1231
        var hints = new LocalCache.RemapHints();
1 ✔
1232
        CompletableFuture<V> mergedValueFuture = delegate.compute(
1 ✔
1233
            key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1234
              if (oldValueFuture == null) {
1 ✔
1235
                merged[0] = true;
1 ✔
1236
                return newValueFuture;
1 ✔
1237
              } else if (!oldValueFuture.isDone()) {
1 ✔
1238
                hints.preserveEntry();
1 ✔
1239
                return oldValueFuture;
1 ✔
1240
              }
1241

1242
              merged[0] = true;
1 ✔
1243
              V oldValue = Async.getIfReady(oldValueFuture);
1 ✔
1244
              if (oldValue == null) {
1 ✔
1245
                return newValueFuture;
1 ✔
1246
              }
1247
              V mergedValue = delegate.statsAware(remappingFunction).apply(oldValue, value);
1 ✔
1248
              if (mergedValue == null) {
1 ✔
1249
                return null;
1 ✔
1250
              } else if (mergedValue == oldValue) {
1 ✔
1251
                return oldValueFuture;
1 ✔
1252
              } else if (mergedValue == value) {
1 ✔
1253
                return newValueFuture;
1 ✔
1254
              }
1255
              return CompletableFuture.completedFuture(mergedValue);
1 ✔
1256
            }, delegate.expiry(), /* recordLoad= */ false, hints);
1 ✔
1257

1258
        if (merged[0]) {
1 ✔
1259
          return Async.getWhenSuccessful(mergedValueFuture);
1 ✔
1260
        }
1261
      }
1 ✔
1262
    }
1263

1264
    @Override
1265
    public Set<K> keySet() {
1266
      var ks = keys;
1 ✔
1267
      return (ks == null) ? (keys = new KeySet()) : ks;
1 ✔
1268
    }
1269

1270
    @Override
1271
    public Collection<V> values() {
1272
      var vs = values;
1 ✔
1273
      return (vs == null) ? (values = new Values()) : vs;
1 ✔
1274
    }
1275

1276
    @Override
1277
    public Set<Entry<K, V>> entrySet() {
1278
      var es = entries;
1 ✔
1279
      return (es == null) ? (entries = new EntrySet()) : es;
1 ✔
1280
    }
1281

1282
    /** See {@link BoundedLocalCache#equals(Object)} for semantics. */
1283
    @Override
1284
    public boolean equals(@Nullable Object o) {
1285
      if (o == this) {
1 ✔
1286
        return true;
1 ✔
1287
      } else if (!(o instanceof Map)) {
1 ✔
1288
        return false;
1 ✔
1289
      }
1290

1291
      var map = (Map<?, ?>) o;
1 ✔
1292
      int expectedSize = size();
1 ✔
1293
      if (map.size() != expectedSize) {
1 ✔
1294
        return false;
1 ✔
1295
      }
1296

1297
      @Var int count = 0;
1 ✔
1298
      try {
1299
        for (var iterator = new EntryIterator(); iterator.hasNext();) {
1 ✔
1300
          var entry = iterator.next();
1 ✔
1301
          var value = map.get(entry.getKey());
1 ✔
1302
          if ((value == null) || ((value != entry.getValue()) && !value.equals(entry.getValue()))) {
1 ✔
1303
            return false;
1 ✔
1304
          }
1305
          count++;
1 ✔
1306
        }
1 ✔
1307
      } catch (ClassCastException | NullPointerException ignored) {
1 ✔
1308
        return false;
1 ✔
1309
      }
1 ✔
1310
      return (count == expectedSize);
1 ✔
1311
    }
1312

1313
    @Override
1314
    public int hashCode() {
1315
      @Var int hash = 0;
1 ✔
1316
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
1 ✔
1317
        var entry = iterator.next();
1 ✔
1318
        hash += entry.hashCode();
1 ✔
1319
      }
1 ✔
1320
      return hash;
1 ✔
1321
    }
1322

1323
    @Override
1324
    public String toString() {
1325
      var result = new StringBuilder(50).append('{');
1 ✔
1326
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
1 ✔
1327
        var entry = iterator.next();
1 ✔
1328
        result.append((entry.getKey() == this) ? "(this Map)" : entry.getKey())
1 ✔
1329
            .append('=')
1 ✔
1330
            .append((entry.getValue() == this) ? "(this Map)" : entry.getValue());
1 ✔
1331

1332
        if (iterator.hasNext()) {
1 ✔
1333
          result.append(", ");
1 ✔
1334
        }
1335
      }
1 ✔
1336
      return result.append('}').toString();
1 ✔
1337
    }
1338

1339
    private final class KeySet extends AbstractSet<K> {
1 ✔
1340

1341
      @Override
1342
      public boolean isEmpty() {
1343
        return AsMapView.this.isEmpty();
1 ✔
1344
      }
1345

1346
      @Override
1347
      public int size() {
1348
        return AsMapView.this.size();
1 ✔
1349
      }
1350

1351
      @Override
1352
      public void clear() {
1353
        AsMapView.this.clear();
1 ✔
1354
      }
1 ✔
1355

1356
      @Override
1357
      public boolean contains(@Nullable Object o) {
1358
        return AsMapView.this.containsKey(o);
1 ✔
1359
      }
1360

1361
      @Override
1362
      public boolean containsAll(Collection<?> collection) {
1363
        requireNonNull(collection);
1 ✔
1364
        if (collection != this) {
1 ✔
1365
          for (Object o : collection) {
1 ✔
1366
            if ((o == null) || !contains(o)) {
1 ✔
1367
              return false;
1 ✔
1368
            }
1369
          }
1 ✔
1370
        }
1371
        return true;
1 ✔
1372
      }
1373

1374
      @Override
1375
      public boolean removeAll(Collection<?> collection) {
1376
        return delegate.keySet().removeAll(collection);
1 ✔
1377
      }
1378

1379
      @Override
1380
      public boolean remove(@Nullable Object o) {
1381
        return (delegate.remove(o) != null);
1 ✔
1382
      }
1383

1384
      @Override
1385
      public boolean removeIf(Predicate<? super K> filter) {
1386
        return delegate.keySet().removeIf(filter);
1 ✔
1387
      }
1388

1389
      @Override
1390
      public boolean retainAll(Collection<?> collection) {
1391
        return delegate.keySet().retainAll(collection);
1 ✔
1392
      }
1393

1394
      @Override
1395
      public Iterator<K> iterator() {
1396
        return new KeyIterator<>(new EntryIterator());
1 ✔
1397
      }
1398

1399
      @Override
1400
      public Spliterator<K> spliterator() {
1401
        return new KeySpliterator<>(new EntrySpliterator());
1 ✔
1402
      }
1403
    }
1404

1405
    private static final class KeyIterator<K, V> implements Iterator<K> {
1406
      private final Iterator<Entry<K, V>> iterator;
1407

1408
      KeyIterator(Iterator<Entry<K, V>> iterator) {
1 ✔
1409
        this.iterator = requireNonNull(iterator);
1 ✔
1410
      }
1 ✔
1411

1412
      @Override
1413
      public boolean hasNext() {
1414
        return iterator.hasNext();
1 ✔
1415
      }
1416

1417
      @Override
1418
      public K next() {
1419
        return iterator.next().getKey();
1 ✔
1420
      }
1421

1422
      @Override
1423
      public void remove() {
1424
        iterator.remove();
1 ✔
1425
      }
1 ✔
1426
    }
1427

1428
    private static final class KeySpliterator<K, V> implements Spliterator<K> {
1429
      private final Spliterator<Entry<K, V>> spliterator;
1430

1431
      KeySpliterator(Spliterator<Entry<K, V>> iterator) {
1 ✔
1432
        this.spliterator = requireNonNull(iterator);
1 ✔
1433
      }
1 ✔
1434

1435
      @Override
1436
      public boolean tryAdvance(Consumer<? super K> action) {
1437
        requireNonNull(action);
1 ✔
1438
        return spliterator.tryAdvance(entry -> {
1 ✔
1439
          action.accept(entry.getKey());
1 ✔
1440
        });
1 ✔
1441
      }
1442

1443
      @Override
1444
      public @Nullable Spliterator<K> trySplit() {
1445
        Spliterator<Entry<K, V>> split = spliterator.trySplit();
1 ✔
1446
        return (split == null) ? null : new KeySpliterator<>(split);
1 ✔
1447
      }
1448

1449
      @Override
1450
      public long estimateSize() {
1451
        return spliterator.estimateSize();
1 ✔
1452
      }
1453

1454
      @Override
1455
      public int characteristics() {
1456
        return DISTINCT | NONNULL | CONCURRENT;
1 ✔
1457
      }
1458
    }
1459

1460
    private final class Values extends AbstractCollection<V> {
1 ✔
1461

1462
      @Override
1463
      public boolean isEmpty() {
1464
        return AsMapView.this.isEmpty();
1 ✔
1465
      }
1466

1467
      @Override
1468
      public int size() {
1469
        return AsMapView.this.size();
1 ✔
1470
      }
1471

1472
      @Override
1473
      public void clear() {
1474
        AsMapView.this.clear();
1 ✔
1475
      }
1 ✔
1476

1477
      @Override
1478
      public boolean contains(@Nullable Object o) {
1479
        return AsMapView.this.containsValue(o);
1 ✔
1480
      }
1481

1482
      @Override
1483
      public boolean containsAll(Collection<?> collection) {
1484
        requireNonNull(collection);
1 ✔
1485
        if (collection != this) {
1 ✔
1486
          for (Object o : collection) {
1 ✔
1487
            if ((o == null) || !contains(o)) {
1 ✔
1488
              return false;
1 ✔
1489
            }
1490
          }
1 ✔
1491
        }
1492
        return true;
1 ✔
1493
      }
1494

1495
      @Override
1496
      public boolean removeAll(Collection<?> collection) {
1497
        requireNonNull(collection);
1 ✔
1498
        @Var boolean modified = false;
1 ✔
1499
        for (var entry : delegate.entrySet()) {
1 ✔
1500
          V value = Async.getIfReady(entry.getValue());
1 ✔
1501
          if ((value != null) && collection.contains(value)
1 ✔
1502
              && AsMapView.this.remove(entry.getKey(), value)) {
1 ✔
1503
            modified = true;
1 ✔
1504
          }
1505
        }
1 ✔
1506
        return modified;
1 ✔
1507
      }
1508

1509
      @Override
1510
      public boolean remove(@Nullable Object o) {
1511
        if (o == null) {
1 ✔
1512
          return false;
1 ✔
1513
        }
1514
        for (var entry : delegate.entrySet()) {
1 ✔
1515
          V value = Async.getIfReady(entry.getValue());
1 ✔
1516
          if (o.equals(value) && AsMapView.this.remove(entry.getKey(), value)) {
1 ✔
1517
            return true;
1 ✔
1518
          }
1519
        }
1 ✔
1520
        return false;
1 ✔
1521
      }
1522

1523
      @Override
1524
      public boolean removeIf(Predicate<? super V> filter) {
1525
        requireNonNull(filter);
1 ✔
1526
        return delegate.values().removeIf(future -> {
1 ✔
1527
          V value = Async.getIfReady(future);
1 ✔
1528
          return (value != null) && filter.test(value);
1 ✔
1529
        });
1530
      }
1531

1532
      @Override
1533
      public boolean retainAll(Collection<?> collection) {
1534
        requireNonNull(collection);
1 ✔
1535
        @Var boolean modified = false;
1 ✔
1536
        for (var entry : delegate.entrySet()) {
1 ✔
1537
          V value = Async.getIfReady(entry.getValue());
1 ✔
1538
          if ((value != null) && !collection.contains(value)
1 ✔
1539
              && AsMapView.this.remove(entry.getKey(), value)) {
1 ✔
1540
            modified = true;
1 ✔
1541
          }
1542
        }
1 ✔
1543
        return modified;
1 ✔
1544
      }
1545

1546
      @Override
1547
      public void forEach(Consumer<? super V> action) {
1548
        requireNonNull(action);
1 ✔
1549
        delegate.values().forEach(future -> {
1 ✔
1550
          V value = Async.getIfReady(future);
1 ✔
1551
          if (value != null) {
1 ✔
1552
            action.accept(value);
1 ✔
1553
          }
1554
        });
1 ✔
1555
      }
1 ✔
1556

1557
      @Override
1558
      public Iterator<V> iterator() {
1559
        return new ValueIterator<>(new EntryIterator());
1 ✔
1560
      }
1561

1562
      @Override
1563
      public Spliterator<V> spliterator() {
1564
        return new ValueSpliterator<>(new EntrySpliterator());
1 ✔
1565
      }
1566
    }
1567

1568
    private static final class ValueIterator<K, V> implements Iterator<V> {
1569
      private final Iterator<Entry<K, V>> iterator;
1570

1571
      ValueIterator(Iterator<Entry<K, V>> iterator) {
1 ✔
1572
        this.iterator = requireNonNull(iterator);
1 ✔
1573
      }
1 ✔
1574

1575
      @Override
1576
      public boolean hasNext() {
1577
        return iterator.hasNext();
1 ✔
1578
      }
1579

1580
      @Override
1581
      public V next() {
1582
        return iterator.next().getValue();
1 ✔
1583
      }
1584

1585
      @Override
1586
      public void remove() {
1587
        iterator.remove();
1 ✔
1588
      }
1 ✔
1589
    }
1590

1591
    private static final class ValueSpliterator<K, V> implements Spliterator<V> {
1592
      private final Spliterator<Entry<K, V>> spliterator;
1593

1594
      ValueSpliterator(Spliterator<Entry<K, V>> iterator) {
1 ✔
1595
        this.spliterator = requireNonNull(iterator);
1 ✔
1596
      }
1 ✔
1597

1598
      @Override
1599
      public boolean tryAdvance(Consumer<? super V> action) {
1600
        requireNonNull(action);
1 ✔
1601
        return spliterator.tryAdvance(entry -> {
1 ✔
1602
          action.accept(entry.getValue());
1 ✔
1603
        });
1 ✔
1604
      }
1605

1606
      @Override
1607
      public @Nullable Spliterator<V> trySplit() {
1608
        Spliterator<Entry<K, V>> split = spliterator.trySplit();
1 ✔
1609
        return (split == null) ? null : new ValueSpliterator<>(split);
1 ✔
1610
      }
1611

1612
      @Override
1613
      public long estimateSize() {
1614
        return spliterator.estimateSize();
1 ✔
1615
      }
1616

1617
      @Override
1618
      public int characteristics() {
1619
        return NONNULL | CONCURRENT;
1 ✔
1620
      }
1621
    }
1622

1623
    private final class EntrySet extends AbstractSet<Entry<K, V>> {
1 ✔
1624

1625
      @Override
1626
      public boolean isEmpty() {
1627
        return AsMapView.this.isEmpty();
1 ✔
1628
      }
1629

1630
      @Override
1631
      public int size() {
1632
        return AsMapView.this.size();
1 ✔
1633
      }
1634

1635
      @Override
1636
      public void clear() {
1637
        AsMapView.this.clear();
1 ✔
1638
      }
1 ✔
1639

1640
      @Override
1641
      public boolean contains(@Nullable Object o) {
1642
        if (!(o instanceof Entry<?, ?>)) {
1 ✔
1643
          return false;
1 ✔
1644
        }
1645
        var entry = (Entry<?, ?>) o;
1 ✔
1646
        var key = entry.getKey();
1 ✔
1647
        var value = entry.getValue();
1 ✔
1648
        if ((key == null) || (value == null)) {
1 ✔
1649
          return false;
1 ✔
1650
        }
1651
        V cachedValue = Async.getIfReady(delegate.getIfPresentQuietly(key));
1 ✔
1652
        return value.equals(cachedValue);
1 ✔
1653
      }
1654

1655
      @Override
1656
      public boolean containsAll(Collection<?> collection) {
1657
        requireNonNull(collection);
1 ✔
1658
        if (collection != this) {
1 ✔
1659
          for (Object o : collection) {
1 ✔
1660
            if ((o == null) || !contains(o)) {
1 ✔
1661
              return false;
1 ✔
1662
            }
1663
          }
1 ✔
1664
        }
1665
        return true;
1 ✔
1666
      }
1667

1668
      @Override
1669
      public boolean removeAll(Collection<?> collection) {
1670
        requireNonNull(collection);
1 ✔
1671
        @Var boolean modified = false;
1 ✔
1672
        if (delegate.collectKeys()
1 ✔
1673
            || ((collection instanceof Set<?>) && (collection.size() > size()))) {
1 ✔
1674
          for (var entry : this) {
1 ✔
1675
            if (collection.contains(entry)) {
1 ✔
1676
              modified |= remove(entry);
1 ✔
1677
            }
1678
          }
1 ✔
1679
        } else {
1680
          for (var o : collection) {
1 ✔
1681
            modified |= remove(o);
1 ✔
1682
          }
1 ✔
1683
        }
1684
        return modified;
1 ✔
1685
      }
1686

1687
      @Override
1688
      public boolean remove(@Nullable Object obj) {
1689
        if (!(obj instanceof Entry<?, ?>)) {
1 ✔
1690
          return false;
1 ✔
1691
        }
1692
        var entry = (Entry<?, ?>) obj;
1 ✔
1693
        var key = entry.getKey();
1 ✔
1694
        return (key != null) && AsMapView.this.remove(key, entry.getValue());
1 ✔
1695
      }
1696

1697
      @Override
1698
      public boolean removeIf(Predicate<? super Entry<K, V>> filter) {
1699
        requireNonNull(filter);
1 ✔
1700
        return delegate.entrySet().removeIf(entry -> {
1 ✔
1701
          V value = Async.getIfReady(entry.getValue());
1 ✔
1702
          return (value != null) && filter.test(Map.entry(entry.getKey(), value));
1 ✔
1703
        });
1704
      }
1705

1706
      @Override
1707
      public boolean retainAll(Collection<?> collection) {
1708
        requireNonNull(collection);
1 ✔
1709
        @Var boolean modified = false;
1 ✔
1710
        for (var entry : this) {
1 ✔
1711
          if (!collection.contains(entry) && remove(entry)) {
1 ✔
1712
            modified = true;
1 ✔
1713
          }
1714
        }
1 ✔
1715
        return modified;
1 ✔
1716
      }
1717

1718
      @Override
1719
      public Iterator<Entry<K, V>> iterator() {
1720
        return new EntryIterator();
1 ✔
1721
      }
1722

1723
      @Override
1724
      public Spliterator<Entry<K, V>> spliterator() {
1725
        return new EntrySpliterator();
1 ✔
1726
      }
1727
    }
1728

1729
    private final class EntryIterator implements Iterator<Entry<K, V>> {
1730
      final Iterator<Entry<K, CompletableFuture<V>>> iterator;
1731
      @Nullable Entry<K, V> cursor;
1732
      @Nullable K removalKey;
1733

1734
      EntryIterator() {
1 ✔
1735
        this.iterator = delegate.entrySet().iterator();
1 ✔
1736
      }
1 ✔
1737

1738
      @Override
1739
      public boolean hasNext() {
1740
        while ((cursor == null) && iterator.hasNext()) {
1 ✔
1741
          Entry<K, CompletableFuture<V>> entry = iterator.next();
1 ✔
1742
          V value = Async.getIfReady(entry.getValue());
1 ✔
1743
          if (value != null) {
1 ✔
1744
            cursor = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
1 ✔
1745
          }
1746
        }
1 ✔
1747
        return (cursor != null);
1 ✔
1748
      }
1749

1750
      @Override
1751
      public Entry<K, V> next() {
1752
        if (!hasNext()) {
1 ✔
1753
          throw new NoSuchElementException();
1 ✔
1754
        }
1755
        K key = requireNonNull(cursor).getKey();
1 ✔
1756
        Entry<K, V> entry = cursor;
1 ✔
1757
        removalKey = key;
1 ✔
1758
        cursor = null;
1 ✔
1759
        return entry;
1 ✔
1760
      }
1761

1762
      @Override
1763
      public void remove() {
1764
        requireState(removalKey != null);
1 ✔
1765
        delegate.remove(removalKey);
1 ✔
1766
        removalKey = null;
1 ✔
1767
      }
1 ✔
1768
    }
1769

1770
    private final class EntrySpliterator implements Spliterator<Entry<K, V>> {
1771
      final Spliterator<Entry<K, CompletableFuture<V>>> spliterator;
1772

1773
      EntrySpliterator() {
1774
        this(delegate.entrySet().spliterator());
1 ✔
1775
      }
1 ✔
1776

1777
      EntrySpliterator(Spliterator<Entry<K, CompletableFuture<V>>> spliterator) {
1 ✔
1778
        this.spliterator = requireNonNull(spliterator);
1 ✔
1779
      }
1 ✔
1780

1781
      @Override
1782
      public void forEachRemaining(Consumer<? super Entry<K, V>> action) {
1783
        requireNonNull(action);
1 ✔
1784
        spliterator.forEachRemaining(entry -> {
1 ✔
1785
          V value = Async.getIfReady(entry.getValue());
1 ✔
1786
          if (value != null) {
1 ✔
1787
            var e = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
1 ✔
1788
            action.accept(e);
1 ✔
1789
          }
1790
        });
1 ✔
1791
      }
1 ✔
1792

1793
      @Override
1794
      public boolean tryAdvance(Consumer<? super Entry<K, V>> action) {
1795
        requireNonNull(action);
1 ✔
1796
        boolean[] advanced = { false };
1 ✔
1797
        Consumer<? super Entry<K, CompletableFuture<V>>> consumer = entry -> {
1 ✔
1798
          V value = Async.getIfReady(entry.getValue());
1 ✔
1799
          if (value != null) {
1 ✔
1800
            var e = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
1 ✔
1801
            action.accept(e);
1 ✔
1802
            advanced[0] = true;
1 ✔
1803
          }
1804
        };
1 ✔
1805
        while (spliterator.tryAdvance(consumer)) {
1 ✔
1806
          if (advanced[0]) {
1 ✔
1807
            return true;
1 ✔
1808
          }
1809
        }
1810
        return false;
1 ✔
1811
      }
1812

1813
      @Override
1814
      public @Nullable Spliterator<Entry<K, V>> trySplit() {
1815
        Spliterator<Entry<K, CompletableFuture<V>>> split = spliterator.trySplit();
1 ✔
1816
        return (split == null) ? null : new EntrySpliterator(split);
1 ✔
1817
      }
1818

1819
      @Override
1820
      public long estimateSize() {
1821
        return spliterator.estimateSize();
1 ✔
1822
      }
1823

1824
      @Override
1825
      public int characteristics() {
1826
        return DISTINCT | CONCURRENT | NONNULL;
1 ✔
1827
      }
1828
    }
1829
  }
1830

1831
  @SuppressWarnings("serial")
1832
  final class SyncViewProxy<K, V> implements Serializable {
1833
    private static final long serialVersionUID = 1;
1834

1835
    final AsyncCache<K, V> asyncCache;
1836

1837
    SyncViewProxy(AsyncCache<K, V> asyncCache) {
1 ✔
1838
      this.asyncCache = requireNonNull(asyncCache);
1 ✔
1839
    }
1 ✔
1840

1841
    @SuppressWarnings("unused")
1842
    private void readObject(ObjectInputStream stream)
1843
        throws IOException, ClassNotFoundException {
1844
      stream.defaultReadObject();
1 ✔
1845
      if (asyncCache == null) {
1 ✔
1846
        throw new InvalidObjectException("asyncCache required");
1 ✔
1847
      }
1848
    }
1 ✔
1849

1850
    Object readResolve() {
1851
      return asyncCache.synchronous();
1 ✔
1852
    }
1853
  }
1854
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc