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

ben-manes / caffeine / #5826

14 Oct 2026 08:43PM UTC coverage: 99.978% (-0.02%) from 100.0%
#5826

push

github

ben-manes
remove the JDK's timer when the pacer cancels

Scheduler.systemScheduler() went through delayedExecutor, which keeps
no handle to its delayed task, so the pacer's cancel left that task
queued until its original fire time. A cache that emptied and refilled
stranded one task per cycle, about 900 bytes each, for up to its
expiration duration.

The system scheduler now arms an internal future with orTimeout, whose
timeout completes the returned future and submits the task. Completing
the returned future, as a cancel does, completes the timer normally,
the only completion on which JDK 11, and 17 before 17.0.14, remove it.

The JDK-seam audit's findings that need no change are recorded as
rulings, and the skill is kept for manual runs.

4826 of 4834 branches covered (99.83%)

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

2 existing lines in 2 files now uncovered.

9277 of 9279 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.requireState;
20
import static com.github.benmanes.caffeine.cache.Caffeine.toUnchecked;
21
import static com.github.benmanes.caffeine.cache.LocalCache.castNonNull;
22
import static java.util.Locale.US;
23
import static java.util.Objects.requireNonNull;
24

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

54
import org.jspecify.annotations.Nullable;
55

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

288
  /** A function executed asynchronously after a bulk load completes. */
289
  final class AsyncBulkCompleter<K, V> implements BiFunction<@Nullable Map<? extends K, ? extends V>,
290
      @Nullable Throwable, @Nullable Map<? extends K, ? extends V>> {
291
    @SuppressWarnings("ImmutableMemberCollection")
292
    private final Map<K, CompletableFuture<@Nullable V>> proxies;
293
    private final LocalCache<K, CompletableFuture<V>> cache;
294
    private final long startTime;
295

296
    AsyncBulkCompleter(LocalCache<K, CompletableFuture<V>> cache,
297
        Map<K, CompletableFuture<@Nullable V>> proxies) {
1 ✔
298
      this.startTime = cache.statsTicker().read();
1 ✔
299
      this.proxies = proxies;
1 ✔
300
      this.cache = cache;
1 ✔
301
    }
1 ✔
302

303
    @Override
304
    @CanIgnoreReturnValue
305
    public @Nullable Map<? extends K, ? extends V> apply(
306
        @Nullable Map<? extends K, ? extends V> result, @Nullable Throwable error) {
307
      long loadTime = cache.statsTicker().read() - startTime;
1 ✔
308
      var failure = handleResponse(result, error);
1 ✔
309

310
      if (failure == null) {
1 ✔
311
        if (requireNonNull(result).isEmpty()) {
1 ✔
312
          cache.statsCounter().recordLoadFailure(loadTime);
1 ✔
313
        } else {
314
          cache.statsCounter().recordLoadSuccess(loadTime);
1 ✔
315
        }
316
        return result;
1 ✔
317
      }
318

319
      cache.statsCounter().recordLoadFailure(loadTime);
1 ✔
320
      throw toUnchecked(failure);
1 ✔
321
    }
322

323
    public RuntimeException error(Throwable error) {
324
      long loadTime = cache.statsTicker().read() - startTime;
1 ✔
325
      var failure = requireNonNull(handleResponse(/* result= */ null, error));
1 ✔
326
      cache.statsCounter().recordLoadFailure(loadTime);
1 ✔
327
      return toUnchecked(failure);
1 ✔
328
    }
329

330
    @SuppressWarnings("ImmutableMapCopyOf")
331
    private @Nullable Throwable handleResponse(
332
        @Nullable Map<? extends K, ? extends V> result, @Nullable Throwable error) {
333
      if (result == null) {
1 ✔
334
        var failure = (error == null) ? new NullMapCompletionException() : error;
1 ✔
335
        failProxies(failure);
1 ✔
336
        return failure;
1 ✔
337
      }
338

339
      Map<K, V> snapshot;
340
      try {
341
        snapshot = Map.copyOf(result);
1 ✔
342
      } catch (Throwable t) {
1 ✔
343
        failProxies(t);
1 ✔
344
        return t;
1 ✔
345
      }
1 ✔
346

347
      var failure = fillProxies(snapshot);
1 ✔
348
      return addNewEntries(snapshot, failure);
1 ✔
349
    }
350

351
    /** Removes and exceptionally completes every requested proxy. */
352
    private void failProxies(Throwable failure) {
353
      for (var entry : proxies.entrySet()) {
1 ✔
354
        cache.remove(entry.getKey(), entry.getValue());
1 ✔
355
        entry.getValue().obtrudeException(failure);
1 ✔
356
      }
1 ✔
357
      if (!(failure instanceof CancellationException) && !(failure instanceof TimeoutException)) {
1 ✔
358
        logger.log(Level.WARNING, "Exception thrown during asynchronous load", failure);
1 ✔
359
      }
360
    }
1 ✔
361

362
    /** Populates the proxies with the computed result. */
363
    private @Nullable Throwable fillProxies(Map<? extends K, ? extends V> result) {
364
      @Var Throwable error = null;
1 ✔
365
      for (var entry : proxies.entrySet()) {
1 ✔
366
        var key = entry.getKey();
1 ✔
367
        var value = result.get(key);
1 ✔
368
        var future = entry.getValue();
1 ✔
369
        future.obtrudeValue(value);
1 ✔
370

371
        if (value == null) {
1 ✔
372
          cache.remove(key, future);
1 ✔
373
        } else {
374
          @SuppressWarnings("NullAway")
375
          CompletableFuture<V> castedFuture = future;
1 ✔
376

377
          try {
378
            // update the weight and expiration timestamps; a same-instance finalization is not a
379
            // mutation, so an in-flight refresh is preserved (mirrors handleCompletion)
380
            cache.replace(key, castedFuture, castedFuture,
1 ✔
381
                /* shouldDiscardRefresh= */ false, /* quietly= */ true);
382
          } catch (Throwable t) {
1 ✔
383
            logger.log(Level.WARNING, "Exception thrown during asynchronous load", t);
1 ✔
384
            cache.remove(key, future);
1 ✔
385
            if (error == null) {
1 ✔
386
              error = t;
1 ✔
387
            } else if (error != t) {
1 ✔
388
              error.addSuppressed(t);
1 ✔
389
            }
390
          }
1 ✔
391
        }
392
      }
1 ✔
393
      return error;
1 ✔
394
    }
395

396
    /** Adds to the cache any extra entries computed that were not requested. */
397
    private @Nullable Throwable addNewEntries(
398
        Map<? extends K, ? extends V> result, @Nullable Throwable failure) {
399
      @Var Throwable error = failure;
1 ✔
400
      for (var entry : result.entrySet()) {
1 ✔
401
        var key = entry.getKey();
1 ✔
402
        if (!proxies.containsKey(key)) {
1 ✔
403
          V value = entry.getValue();
1 ✔
404
          try {
405
            cache.put(key, CompletableFuture.completedFuture(value));
1 ✔
406
          } catch (Throwable t) {
1 ✔
407
            logger.log(Level.WARNING, "Exception thrown during asynchronous load", t);
1 ✔
408
            if (error == null) {
1 ✔
409
              error = t;
1 ✔
410
            } else if (error != t) {
1 ✔
411
              error.addSuppressed(t);
1 ✔
412
            }
413
          }
1 ✔
414
        }
415
      }
1 ✔
416
      return error;
1 ✔
417
    }
418

419
    static final class NullMapCompletionException extends CompletionException {
1 ✔
420
      private static final long serialVersionUID = 1L;
421
    }
422
  }
423

424
  /* --------------- Asynchronous view --------------- */
425
  final class AsyncAsMapView<K, V> implements ConcurrentMap<K, CompletableFuture<V>> {
426
    private @Nullable Set<Entry<K, CompletableFuture<V>>> entrySet;
427
    private final LocalAsyncCache<K, V> asyncCache;
428

429
    AsyncAsMapView(LocalAsyncCache<K, V> asyncCache) {
1 ✔
430
      this.asyncCache = requireNonNull(asyncCache);
1 ✔
431
    }
1 ✔
432
    @Override public boolean isEmpty() {
433
      return asyncCache.cache().isEmpty();
1 ✔
434
    }
435
    @Override public int size() {
436
      return asyncCache.cache().size();
1 ✔
437
    }
438
    @Override public void clear() {
439
      asyncCache.cache().clear();
1 ✔
440
    }
1 ✔
441
    @Override public boolean containsKey(@Nullable Object key) {
442
      return asyncCache.cache().containsKey(key);
1 ✔
443
    }
444
    @SuppressWarnings("CollectionUndefinedEquality")
445
    @Override public boolean containsValue(@Nullable Object value) {
446
      return asyncCache.cache().containsValue(value);
1 ✔
447
    }
448
    @Override public @Nullable CompletableFuture<V> get(@Nullable Object key) {
449
      return asyncCache.cache().get(key);
1 ✔
450
    }
451
    @Override public @Nullable CompletableFuture<V> putIfAbsent(K key, CompletableFuture<V> value) {
452
      boolean deferred = !Async.isReady(value);
1 ✔
453
      CompletableFuture<V> prior = asyncCache.cache().putIfAbsent(key, value);
1 ✔
454
      if (prior == null) {
1 ✔
455
        long startTime = asyncCache.cache().statsTicker().read();
1 ✔
456
        asyncCache.handleCompletion(key, value, startTime, deferred, /* computed= */ false);
1 ✔
457
      }
458
      return prior;
1 ✔
459
    }
460
    @Override public @Nullable CompletableFuture<V> put(K key, CompletableFuture<V> value) {
461
      boolean deferred = !Async.isReady(value);
1 ✔
462
      CompletableFuture<V> prior = asyncCache.cache().put(key, value);
1 ✔
463
      if (prior != value) {
1 ✔
464
        long startTime = asyncCache.cache().statsTicker().read();
1 ✔
465
        asyncCache.handleCompletion(key, value, startTime, deferred, /* computed= */ false);
1 ✔
466
      }
467
      return prior;
1 ✔
468
    }
469
    @SuppressWarnings({"FutureReturnValueIgnored", "ResultOfMethodCallIgnored"})
470
    @Override public void putAll(Map<? extends K, ? extends CompletableFuture<V>> map) {
471
      map.forEach(this::put);
1 ✔
472
    }
1 ✔
473
    @Override public @Nullable CompletableFuture<V> replace(K key, CompletableFuture<V> value) {
474
      boolean deferred = !Async.isReady(value);
1 ✔
475
      CompletableFuture<V> prior = asyncCache.cache().replace(key, value);
1 ✔
476
      if ((prior != null) && (prior != value)) {
1 ✔
477
        long startTime = asyncCache.cache().statsTicker().read();
1 ✔
478
        asyncCache.handleCompletion(key, value, startTime, deferred, /* computed= */ false);
1 ✔
479
      }
480
      return prior;
1 ✔
481
    }
482
    @Override
483
    public boolean replace(K key, CompletableFuture<V> oldValue, CompletableFuture<V> newValue) {
484
      boolean deferred = !Async.isReady(newValue);
1 ✔
485
      boolean replaced = asyncCache.cache().replace(key, oldValue, newValue);
1 ✔
486
      if (replaced && (newValue != oldValue)) {
1 ✔
487
        long startTime = asyncCache.cache().statsTicker().read();
1 ✔
488
        asyncCache.handleCompletion(key, newValue, startTime, deferred, /* computed= */ false);
1 ✔
489
      }
490
      return replaced;
1 ✔
491
    }
492
    @SuppressWarnings({"CheckReturnValue", "FutureReturnValueIgnored"})
493
    @Override public void replaceAll(BiFunction<? super K, ? super CompletableFuture<V>,
494
        ? extends CompletableFuture<V>> function) {
495
      requireNonNull(function);
1 ✔
496
      for (K key : keySet()) {
1 ✔
497
        computeIfPresent(key, (k, oldValue) -> requireNonNull(function.apply(k, oldValue)));
1 ✔
498
      }
1 ✔
499
    }
1 ✔
500
    @Override public @Nullable CompletableFuture<V> remove(@Nullable Object key) {
501
      return asyncCache.cache().remove(key);
1 ✔
502
    }
503
    @Override public boolean remove(@Nullable Object key, @Nullable Object value) {
504
      return asyncCache.cache().remove(key, value);
1 ✔
505
    }
506
    @Override public CompletableFuture<V> computeIfAbsent(K key,
507
        Function<? super K, ? extends @Nullable CompletableFuture<V>> mappingFunction) {
508
      requireNonNull(mappingFunction);
1 ✔
509
      var deferred = new boolean[1];
1 ✔
510
      @SuppressWarnings({"rawtypes", "unchecked"})
511
      @Nullable CompletableFuture<V>[] result = new CompletableFuture[1];
1 ✔
512
      long startTime = asyncCache.cache().statsTicker().read();
1 ✔
513
      Function<K, @Nullable CompletableFuture<V>> function = k -> {
1 ✔
514
        result[0] = mappingFunction.apply(k);
1 ✔
515
        deferred[0] = !Async.isReady(result[0]);
1 ✔
516
        return result[0];
1 ✔
517
      };
518
      CompletableFuture<V> future = asyncCache.cache().computeIfAbsent(
1 ✔
519
          key, function, /* recordStats= */ true, /* recordLoad= */ false);
520
      if (result[0] != null) {
1 ✔
521
        asyncCache.handleCompletion(key, result[0], startTime, deferred[0], /* computed= */ true);
1 ✔
522
      }
523
      return castNonNull(future);
1 ✔
524
    }
525
    @Override public @Nullable CompletableFuture<V> computeIfPresent(K key,
526
        BiFunction<? super K, ? super CompletableFuture<V>,
527
            ? extends @Nullable CompletableFuture<V>> remappingFunction) {
528
      requireNonNull(remappingFunction);
1 ✔
529
      return compute(key, (k, oldValue) ->
1 ✔
530
          (oldValue == null) ? null : remappingFunction.apply(k, oldValue));
1 ✔
531
    }
532
    @Override public @Nullable CompletableFuture<V> compute(K key,
533
        BiFunction<? super K, ? super @Nullable CompletableFuture<V>,
534
            ? extends @Nullable CompletableFuture<V>> remappingFunction) {
535
      requireNonNull(remappingFunction);
1 ✔
536

537
      var deferred = new boolean[1];
1 ✔
538
      @SuppressWarnings({"rawtypes", "unchecked"})
539
      @Nullable CompletableFuture<V>[] prior = new CompletableFuture[1];
1 ✔
540
      long startTime = asyncCache.cache().statsTicker().read();
1 ✔
541
      CompletableFuture<V> result = asyncCache.cache().compute(
1 ✔
542
          key, (K k, @Nullable CompletableFuture<V> oldValue) -> {
543
            var computed = remappingFunction.apply(k, oldValue);
1 ✔
544
            deferred[0] = !Async.isReady(computed);
1 ✔
545
            prior[0] = oldValue;
1 ✔
546
            return computed;
1 ✔
547
          }, asyncCache.cache().expiry(), /* recordLoad= */ false);
1 ✔
548

549
      if ((result != null) && (result != prior[0])) {
1 ✔
550
        asyncCache.handleCompletion(key, result, startTime, deferred[0], /* computed= */ true);
1 ✔
551
      }
552
      return result;
1 ✔
553
    }
554
    @Override public @Nullable CompletableFuture<V> merge(K key, CompletableFuture<V> value,
555
        BiFunction<? super CompletableFuture<V>, ? super CompletableFuture<V>,
556
            ? extends @Nullable CompletableFuture<V>> remappingFunction) {
557
      requireNonNull(value);
1 ✔
558
      requireNonNull(remappingFunction);
1 ✔
559
      return compute(key, (k, oldValue) ->
1 ✔
560
          (oldValue == null) ? value : remappingFunction.apply(oldValue, value));
1 ✔
561
    }
562
    @Override public void forEach(BiConsumer<? super K, ? super CompletableFuture<V>> action) {
563
      asyncCache.cache().forEach(action);
1 ✔
564
    }
1 ✔
565
    @Override public Set<K> keySet() {
566
      return asyncCache.cache().keySet();
1 ✔
567
    }
568
    @Override public Collection<CompletableFuture<V>> values() {
569
      return asyncCache.cache().values();
1 ✔
570
    }
571
    @Override public Set<Entry<K, CompletableFuture<V>>> entrySet() {
572
      var es = entrySet;
1 ✔
573
      return (es == null) ? (entrySet = new AsyncEntrySet()) : es;
1 ✔
574
    }
575
    @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
576
    @Override public boolean equals(@Nullable Object o) {
577
      return (o == this) || asyncCache.cache().equals(o);
1 ✔
578
    }
579
    @Override public int hashCode() {
580
      return asyncCache.cache().hashCode();
1 ✔
581
    }
582
    @Override public String toString() {
583
      return asyncCache.cache().toString();
1 ✔
584
    }
585

586
    private final class AsyncEntrySet extends AbstractSet<Entry<K, CompletableFuture<V>>> {
1 ✔
587
      @Override public int size() {
588
        return AsyncAsMapView.this.size();
1 ✔
589
      }
590
      @Override public boolean isEmpty() {
591
        return AsyncAsMapView.this.isEmpty();
1 ✔
592
      }
593
      @Override public void clear() {
594
        AsyncAsMapView.this.clear();
1 ✔
595
      }
1 ✔
596
      @Override public boolean contains(@Nullable Object o) {
597
        return asyncCache.cache().entrySet().contains(o);
1 ✔
598
      }
599
      @Override public boolean containsAll(Collection<?> collection) {
600
        return (collection == this) || asyncCache.cache().entrySet().containsAll(collection);
1 ✔
601
      }
602
      @Override public boolean remove(@Nullable Object o) {
603
        return asyncCache.cache().entrySet().remove(o);
1 ✔
604
      }
605
      @Override public boolean removeAll(Collection<?> collection) {
606
        return asyncCache.cache().entrySet().removeAll(collection);
1 ✔
607
      }
608
      @Override public boolean retainAll(Collection<?> collection) {
609
        return asyncCache.cache().entrySet().retainAll(collection);
1 ✔
610
      }
611
      @Override public boolean removeIf(Predicate<? super Entry<K, CompletableFuture<V>>> filter) {
612
        return asyncCache.cache().entrySet().removeIf(filter);
1 ✔
613
      }
614
      @Override public Iterator<Entry<K, CompletableFuture<V>>> iterator() {
615
        return new AsyncEntryIterator();
1 ✔
616
      }
617
      @Override public Spliterator<Entry<K, CompletableFuture<V>>> spliterator() {
618
        return new AsyncEntrySpliterator();
1 ✔
619
      }
620
    }
621

622
    private final class AsyncEntryIterator implements Iterator<Entry<K, CompletableFuture<V>>> {
1 ✔
623
      final Iterator<Entry<K, CompletableFuture<V>>> iterator =
1 ✔
624
          asyncCache.cache().entrySet().iterator();
1 ✔
625

626
      @Override public boolean hasNext() {
627
        return iterator.hasNext();
1 ✔
628
      }
629
      @Override public Entry<K, CompletableFuture<V>> next() {
630
        var entry = iterator.next();
1 ✔
631
        return new WriteThroughEntry<>(AsyncAsMapView.this, entry.getKey(), entry.getValue());
1 ✔
632
      }
633
      @Override public void remove() {
634
        iterator.remove();
1 ✔
635
      }
1 ✔
636
    }
637

638
    private final class AsyncEntrySpliterator
639
        implements Spliterator<Entry<K, CompletableFuture<V>>> {
640
      final Spliterator<Entry<K, CompletableFuture<V>>> spliterator;
641

642
      AsyncEntrySpliterator() {
643
        this(asyncCache.cache().entrySet().spliterator());
1 ✔
644
      }
1 ✔
645
      AsyncEntrySpliterator(Spliterator<Entry<K, CompletableFuture<V>>> spliterator) {
1 ✔
646
        this.spliterator = requireNonNull(spliterator);
1 ✔
647
      }
1 ✔
648
      @Override public boolean tryAdvance(Consumer<? super Entry<K, CompletableFuture<V>>> action) {
649
        requireNonNull(action);
1 ✔
650
        return spliterator.tryAdvance(entry -> action.accept(
1 ✔
651
            new WriteThroughEntry<>(AsyncAsMapView.this, entry.getKey(), entry.getValue())));
1 ✔
652
      }
653
      @Override public void forEachRemaining(
654
          Consumer<? super Entry<K, CompletableFuture<V>>> action) {
655
        requireNonNull(action);
1 ✔
656
        spliterator.forEachRemaining(entry -> action.accept(
1 ✔
657
            new WriteThroughEntry<>(AsyncAsMapView.this, entry.getKey(), entry.getValue())));
1 ✔
658
      }
1 ✔
659
      @Override public @Nullable Spliterator<Entry<K, CompletableFuture<V>>> trySplit() {
660
        var split = spliterator.trySplit();
1 ✔
661
        return (split == null) ? null : new AsyncEntrySpliterator(split);
1 ✔
662
      }
663
      @Override public long estimateSize() {
664
        return spliterator.estimateSize();
1 ✔
665
      }
666
      @Override public int characteristics() {
667
        return spliterator.characteristics();
1 ✔
668
      }
669
    }
670
  }
671

672
  /* --------------- Synchronous view --------------- */
673
  final class CacheView<K, V> extends AbstractCacheView<K, V> {
674
    private static final long serialVersionUID = 1L;
675

676
    @SuppressWarnings("serial")
677
    final LocalAsyncCache<K, V> asyncCache;
678

679
    CacheView(LocalAsyncCache<K, V> asyncCache) {
1 ✔
680
      this.asyncCache = requireNonNull(asyncCache);
1 ✔
681
    }
1 ✔
682
    @Override LocalAsyncCache<K, V> asyncCache() {
683
      return asyncCache;
1 ✔
684
    }
685
  }
686

687
  abstract class AbstractCacheView<K, V> implements Cache<K, V>, Serializable {
1 ✔
688
    private static final long serialVersionUID = 1L;
689

690
    transient @Nullable ConcurrentMap<K, V> asMapView;
691

692
    abstract LocalAsyncCache<K, V> asyncCache();
693

694
    @Override
695
    public @Nullable V getIfPresent(K key) {
696
      var future = asyncCache().cache().getIfPresent(key, /* recordStats= */ false);
1 ✔
697
      var value = Async.getIfReady(future);
1 ✔
698
      if (value == null) {
1 ✔
699
        asyncCache().cache().statsCounter().recordMisses(1);
1 ✔
700
      } else {
701
        asyncCache().cache().statsCounter().recordHits(1);
1 ✔
702
      }
703
      return value;
1 ✔
704
    }
705

706
    @Override
707
    public Map<K, V> getAllPresent(Iterable<? extends K> keys) {
708
      var result = new LinkedHashMap<K, @Nullable V>(calculateHashMapCapacity(keys));
1 ✔
709
      for (K key : keys) {
1 ✔
710
        result.put(key, null);
1 ✔
711
      }
1 ✔
712

713
      int uniqueKeys = result.size();
1 ✔
714
      for (var iter = result.entrySet().iterator(); iter.hasNext();) {
1 ✔
715
        Map.Entry<K, @Nullable V> entry = iter.next();
1 ✔
716

717
        CompletableFuture<V> future = asyncCache().cache().get(entry.getKey());
1 ✔
718
        V value = Async.getIfReady(future);
1 ✔
719
        if (value == null) {
1 ✔
720
          iter.remove();
1 ✔
721
        } else {
722
          entry.setValue(value);
1 ✔
723
        }
724
      }
1 ✔
725
      asyncCache().cache().statsCounter().recordHits(result.size());
1 ✔
726
      asyncCache().cache().statsCounter().recordMisses(uniqueKeys - result.size());
1 ✔
727

728
      @SuppressWarnings({"NullableProblems", "NullAway"})
729
      Map<K, V> unmodifiable = Collections.unmodifiableMap(result);
1 ✔
730
      return unmodifiable;
1 ✔
731
    }
732

733
    @Override
734
    public V get(K key, Function<? super K, ? extends V> mappingFunction) {
735
      return resolve(asyncCache().get(key, mappingFunction));
1 ✔
736
    }
737

738
    @Override
739
    public Map<K, V> getAll(
740
        Iterable<? extends K> keys,
741
        Function<
742
            ? super Set<? extends K>,
743
            ? extends Map<? extends K, ? extends V>> mappingFunction) {
744
      return resolve(asyncCache().getAll(keys, mappingFunction));
1 ✔
745
    }
746

747
    @SuppressWarnings({"PMD.AvoidThrowingNullPointerException",
748
      "PMD.PreserveStackTrace", "UnusedException"})
749
    protected static <T> T resolve(CompletableFuture<T> future) {
750
      try {
751
        return future.join();
1 ✔
752
      } catch (NullMapCompletionException e) {
1 ✔
753
        throw new NullPointerException("null map");
1 ✔
754
      } catch (CompletionException e) {
1 ✔
755
        var cause = e.getCause();
1 ✔
756
        if (cause instanceof RuntimeException) {
1 ✔
757
          throw (RuntimeException) cause;
1 ✔
758
        } else if (cause instanceof Error) {
1 ✔
759
          throw (Error) cause;
1 ✔
760
        }
761
        throw e;
1 ✔
762
      }
763
    }
764

765
    @Override
766
    public void put(K key, V value) {
767
      requireNonNull(value);
1 ✔
768
      asyncCache().cache().put(key, CompletableFuture.completedFuture(value));
1 ✔
769
    }
1 ✔
770

771
    @Override
772
    public void putAll(Map<? extends K, ? extends V> map) {
773
      map.forEach(this::put);
1 ✔
774
    }
1 ✔
775

776
    @Override
777
    public void invalidate(K key) {
778
      asyncCache().cache().remove(key);
1 ✔
779
    }
1 ✔
780

781
    @Override
782
    public void invalidateAll(Iterable<? extends K> keys) {
783
      asyncCache().cache().invalidateAll(keys);
1 ✔
784
    }
1 ✔
785

786
    @Override
787
    public void invalidateAll() {
788
      asyncCache().cache().clear();
1 ✔
789
    }
1 ✔
790

791
    @Override
792
    public long estimatedSize() {
793
      return asyncCache().cache().estimatedSize();
1 ✔
794
    }
795

796
    @Override
797
    public CacheStats stats() {
798
      return asyncCache().cache().statsCounter().snapshot();
1 ✔
799
    }
800

801
    @Override
802
    public void cleanUp() {
803
      asyncCache().cache().cleanUp();
1 ✔
804
    }
1 ✔
805

806
    @Override
807
    public Policy<K, V> policy() {
808
      return asyncCache().policy();
1 ✔
809
    }
810

811
    @Override
812
    public ConcurrentMap<K, V> asMap() {
813
      var mv = asMapView;
1 ✔
814
      return (mv == null) ? (asMapView = new AsMapView<>(asyncCache().cache())) : mv;
1 ✔
815
    }
816

817
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
818
      throw new InvalidObjectException("Proxy required");
1 ✔
819
    }
820

821
    @SuppressWarnings("unused")
822
    private void readObjectNoData() throws InvalidObjectException {
823
      throw new InvalidObjectException("Proxy required");
1 ✔
824
    }
825

826
    Object writeReplace() {
827
      return new SyncViewProxy<>(asyncCache());
1 ✔
828
    }
829
  }
830

831
  final class AsMapView<K, V> implements ConcurrentMap<K, V> {
832
    final LocalCache<K, CompletableFuture<V>> delegate;
833

834
    @Nullable Set<K> keys;
835
    @Nullable Collection<V> values;
836
    @Nullable Set<Entry<K, V>> entries;
837

838
    AsMapView(LocalCache<K, CompletableFuture<V>> delegate) {
1 ✔
839
      this.delegate = delegate;
1 ✔
840
    }
1 ✔
841

842
    @Override
843
    public boolean isEmpty() {
844
      return delegate.isEmpty();
1 ✔
845
    }
846

847
    @Override
848
    public int size() {
849
      return delegate.size();
1 ✔
850
    }
851

852
    @Override
853
    public void clear() {
854
      delegate.clear();
1 ✔
855
    }
1 ✔
856

857
    @Override
858
    public boolean containsKey(@Nullable Object key) {
859
      requireNonNull(key);
1 ✔
860
      return Async.isReady(delegate.getIfPresentQuietly(key));
1 ✔
861
    }
862

863
    @Override
864
    public boolean containsValue(@Nullable Object value) {
865
      requireNonNull(value);
1 ✔
866

867
      for (CompletableFuture<V> valueFuture : delegate.values()) {
1 ✔
868
        if (value.equals(Async.getIfReady(valueFuture))) {
1 ✔
869
          return true;
1 ✔
870
        }
871
      }
1 ✔
872
      return false;
1 ✔
873
    }
874

875
    @Override
876
    public @Nullable V get(@Nullable Object key) {
877
      return Async.getIfReady(delegate.get(key));
1 ✔
878
    }
879

880
    @Override
881
    @SuppressWarnings("ResultOfMethodCallIgnored")
882
    public @Nullable V putIfAbsent(K key, V value) {
883
      requireNonNull(value);
1 ✔
884

885
      // Keep in sync with BoundedVarExpiration.putIfAbsentAsync(key, value, duration, unit)
886
      var probe = delegate.putIfAbsent(key, CompletableFuture.completedFuture(value));
1 ✔
887
      V present = Async.getIfReady(probe);
1 ✔
888
      if ((probe == null) || (present != null)) {
1 ✔
889
        return present;
1 ✔
890
      }
891

892
      for (;;) {
893
        var priorFuture = delegate.getIfPresentQuietly(key);
1 ✔
894
        if (priorFuture != null) {
1 ✔
895
          if (!priorFuture.isDone()) {
1 ✔
896
            Async.getWhenSuccessful(priorFuture);
1 ✔
897
            continue;
1 ✔
898
          }
899

900
          V prior = Async.getWhenSuccessful(priorFuture);
1 ✔
901
          if (prior != null) {
1 !
UNCOV
902
            return prior;
×
903
          }
904
        }
905

906
        boolean[] added = { false };
1 ✔
907
        var hints = new LocalCache.RemapHints();
1 ✔
908
        CompletableFuture<V> computed = delegate.compute(
1 ✔
909
            key, (K k, @Nullable CompletableFuture<V> valueFuture) -> {
910
              added[0] = (valueFuture == null)
1 ✔
911
                  || (valueFuture.isDone() && (Async.getIfReady(valueFuture) == null));
1 ✔
912
              if (added[0]) {
1 ✔
913
                return CompletableFuture.completedFuture(value);
1 ✔
914
              }
915
              hints.preserveEntry();
1 ✔
916
              return valueFuture;
1 ✔
917
            }, delegate.expiry(), /* recordLoad= */ false, hints);
1 ✔
918

919
        if (added[0]) {
1 ✔
920
          return null;
1 ✔
921
        } else {
922
          V prior = Async.getWhenSuccessful(computed);
1 ✔
923
          if (prior != null) {
1 ✔
924
            return prior;
1 ✔
925
          }
926
        }
927
      }
1 ✔
928
    }
929

930
    @Override
931
    @SuppressWarnings("ResultOfMethodCallIgnored")
932
    public void putAll(Map<? extends K, ? extends V> map) {
933
      map.forEach(this::put);
1 ✔
934
    }
1 ✔
935

936
    @Override
937
    public @Nullable V put(K key, V value) {
938
      requireNonNull(value);
1 ✔
939
      CompletableFuture<V> oldValueFuture =
1 ✔
940
          delegate.put(key, CompletableFuture.completedFuture(value));
1 ✔
941
      return Async.getWhenSuccessful(oldValueFuture);
1 ✔
942
    }
943

944
    @Override
945
    public @Nullable V remove(@Nullable Object key) {
946
      CompletableFuture<V> oldValueFuture = delegate.remove(key);
1 ✔
947
      return Async.getWhenSuccessful(oldValueFuture);
1 ✔
948
    }
949

950
    @Override
951
    public boolean remove(@Nullable Object key, @Nullable Object value) {
952
      requireNonNull(key);
1 ✔
953
      if (value == null) {
1 ✔
954
        return false;
1 ✔
955
      }
956

957
      @SuppressWarnings("unchecked")
958
      var castedKey = (K) key;
1 ✔
959
      boolean[] removed = { false };
1 ✔
960
      remapWhenSettled(castedKey, (oldValue, oldValueFuture) -> {
1 ✔
961
        removed[0] = Objects.equals(value, oldValue);
1 ✔
962
        return ((oldValue == null) || removed[0]) ? null : oldValueFuture;
1 ✔
963
      });
964
      return removed[0];
1 ✔
965
    }
966

967
    @Override
968
    public @Nullable V replace(K key, V value) {
969
      requireNonNull(value);
1 ✔
970

971
      @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
972
      @Nullable V[] oldValue = (V[]) new Object[1];
1 ✔
973
      remapWhenSettled(key, (current, oldValueFuture) -> {
1 ✔
974
        oldValue[0] = current;
1 ✔
975
        return (current == null) ? null : CompletableFuture.completedFuture(value);
1 ✔
976
      });
977
      return oldValue[0];
1 ✔
978
    }
979

980
    @Override
981
    public boolean replace(K key, V oldValue, V newValue) {
982
      requireNonNull(oldValue);
1 ✔
983
      requireNonNull(newValue);
1 ✔
984

985
      boolean[] replaced = { false };
1 ✔
986
      remapWhenSettled(key, (current, oldValueFuture) -> {
1 ✔
987
        replaced[0] = Objects.equals(oldValue, current);
1 ✔
988
        return replaced[0] ? CompletableFuture.completedFuture(newValue) : oldValueFuture;
1 ✔
989
      });
990
      return replaced[0];
1 ✔
991
    }
992

993
    @Override
994
    @SuppressWarnings("ResultOfMethodCallIgnored")
995
    public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
996
      requireNonNull(function);
1 ✔
997
      for (K key : keySet()) {
1 ✔
998
        computeIfPresent(key, (k, oldValue) ->
1 ✔
999
            requireNonNull(function.apply(k, oldValue)), /* recordLoad= */ false);
1 ✔
1000
      }
1 ✔
1001
    }
1 ✔
1002

1003
    /**
1004
     * Applies the remapping to the entry once its value has settled, retrying while a load is in
1005
     * flight. A remapping that returns the future it was given is a no-op, leaving the entry's
1006
     * metadata and any in-flight refresh intact.
1007
     *
1008
     * @param key the key whose entry is to be remapped
1009
     * @param remapping computes the future to store from the settled value, or {@code null} to
1010
     *        remove the entry
1011
     */
1012
    @SuppressWarnings("ResultOfMethodCallIgnored")
1013
    private void remapWhenSettled(K key,
1014
        BiFunction<@Nullable V, CompletableFuture<V>, @Nullable CompletableFuture<V>> remapping) {
1015
      boolean[] done = { false };
1 ✔
1016
      for (;;) {
1017
        CompletableFuture<V> future = delegate.getIfPresentQuietly(key);
1 ✔
1018
        if ((future == null) || future.isCompletedExceptionally()) {
1 ✔
1019
          return;
1 ✔
1020
        }
1021

1022
        Async.getWhenSuccessful(future);
1 ✔
1023
        var hints = new LocalCache.RemapHints();
1 ✔
1024
        delegate.compute(key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1 ✔
1025
          if (oldValueFuture == null) {
1 ✔
1026
            done[0] = true;
1 ✔
1027
            return null;
1 ✔
1028
          } else if (!oldValueFuture.isDone()) {
1 ✔
1029
            hints.preserveEntry();
1 ✔
1030
            return oldValueFuture;
1 ✔
1031
          }
1032

1033
          done[0] = true;
1 ✔
1034
          var remapped = remapping.apply(Async.getIfReady(oldValueFuture), oldValueFuture);
1 ✔
1035
          if (remapped == oldValueFuture) {
1 ✔
1036
            hints.preserveEntry();
1 ✔
1037
          }
1038
          return remapped;
1 ✔
1039
        }, delegate.expiry(), /* recordLoad= */ false, hints);
1 ✔
1040

1041
        if (done[0]) {
1 ✔
1042
          return;
1 ✔
1043
        }
1044
      }
1 ✔
1045
    }
1046

1047
    @Override
1048
    @SuppressWarnings("ResultOfMethodCallIgnored")
1049
    public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
1050
      requireNonNull(mappingFunction);
1 ✔
1051

1052
      @Var CompletableFuture<V> priorFuture = null;
1 ✔
1053
      for (;;) {
1054
        priorFuture = (priorFuture == null)
1 ✔
1055
            ? delegate.get(key)
1 ✔
1056
            : delegate.getIfPresentQuietly(key);
1 ✔
1057
        if (priorFuture != null) {
1 ✔
1058
          if (!priorFuture.isDone()) {
1 ✔
1059
            Async.getWhenSuccessful(priorFuture);
1 ✔
1060
            continue;
1 ✔
1061
          }
1062

1063
          V prior = Async.getWhenSuccessful(priorFuture);
1 ✔
1064
          if (prior != null) {
1 ✔
1065
            delegate.statsCounter().recordHits(1);
1 ✔
1066
            return prior;
1 ✔
1067
          }
1068
        }
1069

1070
        @SuppressWarnings({"rawtypes", "unchecked"})
1071
        CompletableFuture<V>[] future = new CompletableFuture[1];
1 ✔
1072
        var hints = new LocalCache.RemapHints();
1 ✔
1073
        CompletableFuture<V> computed = delegate.compute(
1 ✔
1074
            key, (K k, @Nullable CompletableFuture<V> valueFuture) -> {
1075
              if ((valueFuture != null)
1 ✔
1076
                  && (!valueFuture.isDone() || (Async.getIfReady(valueFuture) != null))) {
1 ✔
1077
                hints.preserveEntry();
1 ✔
1078
                return valueFuture;
1 ✔
1079
              }
1080

1081
              V newValue = delegate.statsAware(mappingFunction, /* recordLoad= */ true).apply(key);
1 ✔
1082
              if (newValue == null) {
1 ✔
1083
                return null;
1 ✔
1084
              }
1085
              future[0] = CompletableFuture.completedFuture(newValue);
1 ✔
1086
              return future[0];
1 ✔
1087
            }, delegate.expiry(), /* recordLoad= */ false, hints);
1 ✔
1088

1089
        V result = Async.getWhenSuccessful(computed);
1 ✔
1090
        if ((computed == future[0]) || (result != null)) {
1 ✔
1091
          return castNonNull(result);
1 ✔
1092
        }
1093
      }
1 ✔
1094
    }
1095

1096
    @Override
1097
    public @Nullable V computeIfPresent(K key,
1098
        BiFunction<? super K, ? super V, ? extends @Nullable V> remappingFunction) {
1099
      return computeIfPresent(key, remappingFunction, /* recordLoad= */ true);
1 ✔
1100
    }
1101

1102
    @SuppressWarnings("ResultOfMethodCallIgnored")
1103
    private @Nullable V computeIfPresent(K key,
1104
        BiFunction<? super K, ? super V, ? extends @Nullable V> remappingFunction,
1105
        boolean recordLoad) {
1106
      requireNonNull(remappingFunction);
1 ✔
1107

1108
      @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
1109
      @Nullable V[] newValue = (@Nullable V[]) new Object[1];
1 ✔
1110
      for (;;) {
1111
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
1 ✔
1112

1113
        var hints = new LocalCache.RemapHints();
1 ✔
1114
        CompletableFuture<V> valueFuture = delegate.compute(
1 ✔
1115
            key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1116
              if (oldValueFuture == null) {
1 ✔
1117
                return null;
1 ✔
1118
              } else if (!oldValueFuture.isDone()) {
1 ✔
1119
                hints.preserveEntry();
1 ✔
1120
                return oldValueFuture;
1 ✔
1121
              }
1122

1123
              V oldValue = Async.getIfReady(oldValueFuture);
1 ✔
1124
              if (oldValue == null) {
1 ✔
1125
                return null;
1 ✔
1126
              }
1127

1128
              BiFunction<? super K, ? super V, ? extends @Nullable V> function =
1 ✔
1129
                  delegate.statsAware(remappingFunction, recordLoad);
1 ✔
1130
              newValue[0] = function.apply(key, oldValue);
1 ✔
1131
              return (newValue[0] == null) ? null : CompletableFuture.completedFuture(newValue[0]);
1 ✔
1132
            }, delegate.expiry(), /* recordLoad= */ false, hints);
1 ✔
1133

1134
        if (newValue[0] != null) {
1 ✔
1135
          return newValue[0];
1 ✔
1136
        } else if (valueFuture == null) {
1 ✔
1137
          return null;
1 ✔
1138
        }
1139
      }
1 ✔
1140
    }
1141

1142
    @Override
1143
    @SuppressWarnings("ResultOfMethodCallIgnored")
1144
    public @Nullable V compute(K key,
1145
        BiFunction<? super K, ? super @Nullable V, ? extends @Nullable V> remappingFunction) {
1146
      // Keep in sync with BoundedVarExpiration.computeAsync(key, remappingFunction, expiry)
1147
      requireNonNull(remappingFunction);
1 ✔
1148

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

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

1162
              V oldValue = Async.getIfReady(oldValueFuture);
1 ✔
1163
              BiFunction<? super K, ? super @Nullable V, ? extends @Nullable V> function =
1 ✔
1164
                  delegate.statsAware(remappingFunction, /* recordLoad= */ true);
1 ✔
1165
              newValue[0] = function.apply(key, oldValue);
1 ✔
1166
              return (newValue[0] == null) ? null : CompletableFuture.completedFuture(newValue[0]);
1 ✔
1167
            }, delegate.expiry(), /* recordLoad= */ false, hints);
1 ✔
1168

1169
        if (newValue[0] != null) {
1 ✔
1170
          return newValue[0];
1 ✔
1171
        } else if (valueFuture == null) {
1 ✔
1172
          return null;
1 ✔
1173
        }
1174
      }
1 ✔
1175
    }
1176

1177
    @Override
1178
    @SuppressWarnings("ResultOfMethodCallIgnored")
1179
    public @Nullable V merge(K key, V value,
1180
        BiFunction<? super V, ? super V, ? extends @Nullable V> remappingFunction) {
1181
      requireNonNull(value);
1 ✔
1182
      requireNonNull(remappingFunction);
1 ✔
1183

1184
      CompletableFuture<V> newValueFuture = CompletableFuture.completedFuture(value);
1 ✔
1185
      boolean[] merged = { false };
1 ✔
1186
      for (;;) {
1187
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
1 ✔
1188

1189
        var hints = new LocalCache.RemapHints();
1 ✔
1190
        CompletableFuture<V> mergedValueFuture = delegate.compute(
1 ✔
1191
            key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1192
              if (oldValueFuture == null) {
1 ✔
1193
                merged[0] = true;
1 ✔
1194
                return newValueFuture;
1 ✔
1195
              } else if (!oldValueFuture.isDone()) {
1 ✔
1196
                hints.preserveEntry();
1 ✔
1197
                return oldValueFuture;
1 ✔
1198
              }
1199

1200
              merged[0] = true;
1 ✔
1201
              V oldValue = Async.getIfReady(oldValueFuture);
1 ✔
1202
              if (oldValue == null) {
1 ✔
1203
                return newValueFuture;
1 ✔
1204
              }
1205
              V mergedValue = delegate.statsAware(remappingFunction).apply(oldValue, value);
1 ✔
1206
              if (mergedValue == null) {
1 ✔
1207
                return null;
1 ✔
1208
              } else if (mergedValue == oldValue) {
1 ✔
1209
                return oldValueFuture;
1 ✔
1210
              } else if (mergedValue == value) {
1 ✔
1211
                return newValueFuture;
1 ✔
1212
              }
1213
              return CompletableFuture.completedFuture(mergedValue);
1 ✔
1214
            }, delegate.expiry(), /* recordLoad= */ false, hints);
1 ✔
1215

1216
        if (merged[0]) {
1 ✔
1217
          return Async.getWhenSuccessful(mergedValueFuture);
1 ✔
1218
        }
1219
      }
1 ✔
1220
    }
1221

1222
    @Override
1223
    public Set<K> keySet() {
1224
      var ks = keys;
1 ✔
1225
      return (ks == null) ? (keys = new KeySet()) : ks;
1 ✔
1226
    }
1227

1228
    @Override
1229
    public Collection<V> values() {
1230
      var vs = values;
1 ✔
1231
      return (vs == null) ? (values = new Values()) : vs;
1 ✔
1232
    }
1233

1234
    @Override
1235
    public Set<Entry<K, V>> entrySet() {
1236
      var es = entries;
1 ✔
1237
      return (es == null) ? (entries = new EntrySet()) : es;
1 ✔
1238
    }
1239

1240
    /** See {@link BoundedLocalCache#equals(Object)} for semantics. */
1241
    @Override
1242
    public boolean equals(@Nullable Object o) {
1243
      if (o == this) {
1 ✔
1244
        return true;
1 ✔
1245
      } else if (!(o instanceof Map)) {
1 ✔
1246
        return false;
1 ✔
1247
      }
1248

1249
      var map = (Map<?, ?>) o;
1 ✔
1250
      int expectedSize = size();
1 ✔
1251
      if (map.size() != expectedSize) {
1 ✔
1252
        return false;
1 ✔
1253
      }
1254

1255
      @Var int count = 0;
1 ✔
1256
      try {
1257
        for (var iterator = new EntryIterator(); iterator.hasNext();) {
1 ✔
1258
          var entry = iterator.next();
1 ✔
1259
          var value = map.get(entry.getKey());
1 ✔
1260
          if ((value == null) || ((value != entry.getValue()) && !value.equals(entry.getValue()))) {
1 ✔
1261
            return false;
1 ✔
1262
          }
1263
          count++;
1 ✔
1264
        }
1 ✔
1265
      } catch (ClassCastException | NullPointerException ignored) {
1 ✔
1266
        return false;
1 ✔
1267
      }
1 ✔
1268
      return (count == expectedSize);
1 ✔
1269
    }
1270

1271
    @Override
1272
    public int hashCode() {
1273
      @Var int hash = 0;
1 ✔
1274
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
1 ✔
1275
        var entry = iterator.next();
1 ✔
1276
        hash += entry.hashCode();
1 ✔
1277
      }
1 ✔
1278
      return hash;
1 ✔
1279
    }
1280

1281
    @Override
1282
    public String toString() {
1283
      var result = new StringBuilder(50).append('{');
1 ✔
1284
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
1 ✔
1285
        var entry = iterator.next();
1 ✔
1286
        result.append((entry.getKey() == this) ? "(this Map)" : entry.getKey())
1 ✔
1287
            .append('=')
1 ✔
1288
            .append((entry.getValue() == this) ? "(this Map)" : entry.getValue());
1 ✔
1289

1290
        if (iterator.hasNext()) {
1 ✔
1291
          result.append(", ");
1 ✔
1292
        }
1293
      }
1 ✔
1294
      return result.append('}').toString();
1 ✔
1295
    }
1296

1297
    private final class KeySet extends AbstractSet<K> {
1 ✔
1298

1299
      @Override
1300
      public boolean isEmpty() {
1301
        return AsMapView.this.isEmpty();
1 ✔
1302
      }
1303

1304
      @Override
1305
      public int size() {
1306
        return AsMapView.this.size();
1 ✔
1307
      }
1308

1309
      @Override
1310
      public void clear() {
1311
        AsMapView.this.clear();
1 ✔
1312
      }
1 ✔
1313

1314
      @Override
1315
      public boolean contains(@Nullable Object o) {
1316
        return AsMapView.this.containsKey(o);
1 ✔
1317
      }
1318

1319
      @Override
1320
      public boolean containsAll(Collection<?> collection) {
1321
        requireNonNull(collection);
1 ✔
1322
        if (collection != this) {
1 ✔
1323
          for (Object o : collection) {
1 ✔
1324
            if ((o == null) || !contains(o)) {
1 ✔
1325
              return false;
1 ✔
1326
            }
1327
          }
1 ✔
1328
        }
1329
        return true;
1 ✔
1330
      }
1331

1332
      @Override
1333
      public boolean removeAll(Collection<?> collection) {
1334
        return delegate.keySet().removeAll(collection);
1 ✔
1335
      }
1336

1337
      @Override
1338
      public boolean remove(@Nullable Object o) {
1339
        return (delegate.remove(o) != null);
1 ✔
1340
      }
1341

1342
      @Override
1343
      public boolean removeIf(Predicate<? super K> filter) {
1344
        return delegate.keySet().removeIf(filter);
1 ✔
1345
      }
1346

1347
      @Override
1348
      public boolean retainAll(Collection<?> collection) {
1349
        return delegate.keySet().retainAll(collection);
1 ✔
1350
      }
1351

1352
      @Override
1353
      public Iterator<K> iterator() {
1354
        return new KeyIterator<>(new EntryIterator());
1 ✔
1355
      }
1356

1357
      @Override
1358
      public Spliterator<K> spliterator() {
1359
        return new KeySpliterator<>(new EntrySpliterator());
1 ✔
1360
      }
1361
    }
1362

1363
    private static final class KeyIterator<K, V> implements Iterator<K> {
1364
      private final Iterator<Entry<K, V>> iterator;
1365

1366
      KeyIterator(Iterator<Entry<K, V>> iterator) {
1 ✔
1367
        this.iterator = requireNonNull(iterator);
1 ✔
1368
      }
1 ✔
1369

1370
      @Override
1371
      public boolean hasNext() {
1372
        return iterator.hasNext();
1 ✔
1373
      }
1374

1375
      @Override
1376
      public K next() {
1377
        return iterator.next().getKey();
1 ✔
1378
      }
1379

1380
      @Override
1381
      public void remove() {
1382
        iterator.remove();
1 ✔
1383
      }
1 ✔
1384
    }
1385

1386
    private static final class KeySpliterator<K, V> implements Spliterator<K> {
1387
      private final Spliterator<Entry<K, V>> spliterator;
1388

1389
      KeySpliterator(Spliterator<Entry<K, V>> iterator) {
1 ✔
1390
        this.spliterator = requireNonNull(iterator);
1 ✔
1391
      }
1 ✔
1392

1393
      @Override
1394
      public boolean tryAdvance(Consumer<? super K> action) {
1395
        requireNonNull(action);
1 ✔
1396
        return spliterator.tryAdvance(entry -> {
1 ✔
1397
          action.accept(entry.getKey());
1 ✔
1398
        });
1 ✔
1399
      }
1400

1401
      @Override
1402
      public @Nullable Spliterator<K> trySplit() {
1403
        Spliterator<Entry<K, V>> split = spliterator.trySplit();
1 ✔
1404
        return (split == null) ? null : new KeySpliterator<>(split);
1 ✔
1405
      }
1406

1407
      @Override
1408
      public long estimateSize() {
1409
        return spliterator.estimateSize();
1 ✔
1410
      }
1411

1412
      @Override
1413
      public int characteristics() {
1414
        return DISTINCT | NONNULL | CONCURRENT;
1 ✔
1415
      }
1416
    }
1417

1418
    private final class Values extends AbstractCollection<V> {
1 ✔
1419

1420
      @Override
1421
      public boolean isEmpty() {
1422
        return AsMapView.this.isEmpty();
1 ✔
1423
      }
1424

1425
      @Override
1426
      public int size() {
1427
        return AsMapView.this.size();
1 ✔
1428
      }
1429

1430
      @Override
1431
      public void clear() {
1432
        AsMapView.this.clear();
1 ✔
1433
      }
1 ✔
1434

1435
      @Override
1436
      public boolean contains(@Nullable Object o) {
1437
        return AsMapView.this.containsValue(o);
1 ✔
1438
      }
1439

1440
      @Override
1441
      public boolean containsAll(Collection<?> collection) {
1442
        requireNonNull(collection);
1 ✔
1443
        if (collection != this) {
1 ✔
1444
          for (Object o : collection) {
1 ✔
1445
            if ((o == null) || !contains(o)) {
1 ✔
1446
              return false;
1 ✔
1447
            }
1448
          }
1 ✔
1449
        }
1450
        return true;
1 ✔
1451
      }
1452

1453
      @Override
1454
      public boolean removeAll(Collection<?> collection) {
1455
        requireNonNull(collection);
1 ✔
1456
        @Var boolean modified = false;
1 ✔
1457
        for (var entry : delegate.entrySet()) {
1 ✔
1458
          V value = Async.getIfReady(entry.getValue());
1 ✔
1459
          if ((value != null) && collection.contains(value)
1 ✔
1460
              && AsMapView.this.remove(entry.getKey(), value)) {
1 ✔
1461
            modified = true;
1 ✔
1462
          }
1463
        }
1 ✔
1464
        return modified;
1 ✔
1465
      }
1466

1467
      @Override
1468
      public boolean remove(@Nullable Object o) {
1469
        if (o == null) {
1 ✔
1470
          return false;
1 ✔
1471
        }
1472
        for (var entry : delegate.entrySet()) {
1 ✔
1473
          V value = Async.getIfReady(entry.getValue());
1 ✔
1474
          if (o.equals(value) && AsMapView.this.remove(entry.getKey(), value)) {
1 ✔
1475
            return true;
1 ✔
1476
          }
1477
        }
1 ✔
1478
        return false;
1 ✔
1479
      }
1480

1481
      @Override
1482
      public boolean removeIf(Predicate<? super V> filter) {
1483
        requireNonNull(filter);
1 ✔
1484
        return delegate.values().removeIf(future -> {
1 ✔
1485
          V value = Async.getIfReady(future);
1 ✔
1486
          return (value != null) && filter.test(value);
1 ✔
1487
        });
1488
      }
1489

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

1504
      @Override
1505
      public void forEach(Consumer<? super V> action) {
1506
        requireNonNull(action);
1 ✔
1507
        delegate.values().forEach(future -> {
1 ✔
1508
          V value = Async.getIfReady(future);
1 ✔
1509
          if (value != null) {
1 ✔
1510
            action.accept(value);
1 ✔
1511
          }
1512
        });
1 ✔
1513
      }
1 ✔
1514

1515
      @Override
1516
      public Iterator<V> iterator() {
1517
        return new ValueIterator<>(new EntryIterator());
1 ✔
1518
      }
1519

1520
      @Override
1521
      public Spliterator<V> spliterator() {
1522
        return new ValueSpliterator<>(new EntrySpliterator());
1 ✔
1523
      }
1524
    }
1525

1526
    private static final class ValueIterator<K, V> implements Iterator<V> {
1527
      private final Iterator<Entry<K, V>> iterator;
1528

1529
      ValueIterator(Iterator<Entry<K, V>> iterator) {
1 ✔
1530
        this.iterator = requireNonNull(iterator);
1 ✔
1531
      }
1 ✔
1532

1533
      @Override
1534
      public boolean hasNext() {
1535
        return iterator.hasNext();
1 ✔
1536
      }
1537

1538
      @Override
1539
      public V next() {
1540
        return iterator.next().getValue();
1 ✔
1541
      }
1542

1543
      @Override
1544
      public void remove() {
1545
        iterator.remove();
1 ✔
1546
      }
1 ✔
1547
    }
1548

1549
    private static final class ValueSpliterator<K, V> implements Spliterator<V> {
1550
      private final Spliterator<Entry<K, V>> spliterator;
1551

1552
      ValueSpliterator(Spliterator<Entry<K, V>> iterator) {
1 ✔
1553
        this.spliterator = requireNonNull(iterator);
1 ✔
1554
      }
1 ✔
1555

1556
      @Override
1557
      public boolean tryAdvance(Consumer<? super V> action) {
1558
        requireNonNull(action);
1 ✔
1559
        return spliterator.tryAdvance(entry -> {
1 ✔
1560
          action.accept(entry.getValue());
1 ✔
1561
        });
1 ✔
1562
      }
1563

1564
      @Override
1565
      public @Nullable Spliterator<V> trySplit() {
1566
        Spliterator<Entry<K, V>> split = spliterator.trySplit();
1 ✔
1567
        return (split == null) ? null : new ValueSpliterator<>(split);
1 ✔
1568
      }
1569

1570
      @Override
1571
      public long estimateSize() {
1572
        return spliterator.estimateSize();
1 ✔
1573
      }
1574

1575
      @Override
1576
      public int characteristics() {
1577
        return NONNULL | CONCURRENT;
1 ✔
1578
      }
1579
    }
1580

1581
    private final class EntrySet extends AbstractSet<Entry<K, V>> {
1 ✔
1582

1583
      @Override
1584
      public boolean isEmpty() {
1585
        return AsMapView.this.isEmpty();
1 ✔
1586
      }
1587

1588
      @Override
1589
      public int size() {
1590
        return AsMapView.this.size();
1 ✔
1591
      }
1592

1593
      @Override
1594
      public void clear() {
1595
        AsMapView.this.clear();
1 ✔
1596
      }
1 ✔
1597

1598
      @Override
1599
      public boolean contains(@Nullable Object o) {
1600
        if (!(o instanceof Entry<?, ?>)) {
1 ✔
1601
          return false;
1 ✔
1602
        }
1603
        var entry = (Entry<?, ?>) o;
1 ✔
1604
        var key = entry.getKey();
1 ✔
1605
        var value = entry.getValue();
1 ✔
1606
        if ((key == null) || (value == null)) {
1 ✔
1607
          return false;
1 ✔
1608
        }
1609
        V cachedValue = Async.getIfReady(delegate.getIfPresentQuietly(key));
1 ✔
1610
        return value.equals(cachedValue);
1 ✔
1611
      }
1612

1613
      @Override
1614
      public boolean containsAll(Collection<?> collection) {
1615
        requireNonNull(collection);
1 ✔
1616
        if (collection != this) {
1 ✔
1617
          for (Object o : collection) {
1 ✔
1618
            if ((o == null) || !contains(o)) {
1 ✔
1619
              return false;
1 ✔
1620
            }
1621
          }
1 ✔
1622
        }
1623
        return true;
1 ✔
1624
      }
1625

1626
      @Override
1627
      public boolean removeAll(Collection<?> collection) {
1628
        requireNonNull(collection);
1 ✔
1629
        @Var boolean modified = false;
1 ✔
1630
        if (delegate.collectKeys()
1 ✔
1631
            || ((collection instanceof Set<?>) && (collection.size() > size()))) {
1 ✔
1632
          for (var entry : this) {
1 ✔
1633
            if (collection.contains(entry)) {
1 ✔
1634
              modified |= remove(entry);
1 ✔
1635
            }
1636
          }
1 ✔
1637
        } else {
1638
          for (var o : collection) {
1 ✔
1639
            modified |= remove(o);
1 ✔
1640
          }
1 ✔
1641
        }
1642
        return modified;
1 ✔
1643
      }
1644

1645
      @Override
1646
      public boolean remove(@Nullable Object obj) {
1647
        if (!(obj instanceof Entry<?, ?>)) {
1 ✔
1648
          return false;
1 ✔
1649
        }
1650
        var entry = (Entry<?, ?>) obj;
1 ✔
1651
        var key = entry.getKey();
1 ✔
1652
        return (key != null) && AsMapView.this.remove(key, entry.getValue());
1 ✔
1653
      }
1654

1655
      @Override
1656
      public boolean removeIf(Predicate<? super Entry<K, V>> filter) {
1657
        requireNonNull(filter);
1 ✔
1658
        return delegate.entrySet().removeIf(entry -> {
1 ✔
1659
          V value = Async.getIfReady(entry.getValue());
1 ✔
1660
          return (value != null) && filter.test(Map.entry(entry.getKey(), value));
1 ✔
1661
        });
1662
      }
1663

1664
      @Override
1665
      public boolean retainAll(Collection<?> collection) {
1666
        requireNonNull(collection);
1 ✔
1667
        @Var boolean modified = false;
1 ✔
1668
        for (var entry : this) {
1 ✔
1669
          if (!collection.contains(entry) && remove(entry)) {
1 ✔
1670
            modified = true;
1 ✔
1671
          }
1672
        }
1 ✔
1673
        return modified;
1 ✔
1674
      }
1675

1676
      @Override
1677
      public Iterator<Entry<K, V>> iterator() {
1678
        return new EntryIterator();
1 ✔
1679
      }
1680

1681
      @Override
1682
      public Spliterator<Entry<K, V>> spliterator() {
1683
        return new EntrySpliterator();
1 ✔
1684
      }
1685
    }
1686

1687
    private final class EntryIterator implements Iterator<Entry<K, V>> {
1688
      final Iterator<Entry<K, CompletableFuture<V>>> iterator;
1689
      @Nullable Entry<K, V> cursor;
1690
      @Nullable K removalKey;
1691

1692
      EntryIterator() {
1 ✔
1693
        this.iterator = delegate.entrySet().iterator();
1 ✔
1694
      }
1 ✔
1695

1696
      @Override
1697
      public boolean hasNext() {
1698
        while ((cursor == null) && iterator.hasNext()) {
1 ✔
1699
          Entry<K, CompletableFuture<V>> entry = iterator.next();
1 ✔
1700
          V value = Async.getIfReady(entry.getValue());
1 ✔
1701
          if (value != null) {
1 ✔
1702
            cursor = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
1 ✔
1703
          }
1704
        }
1 ✔
1705
        return (cursor != null);
1 ✔
1706
      }
1707

1708
      @Override
1709
      public Entry<K, V> next() {
1710
        if (!hasNext()) {
1 ✔
1711
          throw new NoSuchElementException();
1 ✔
1712
        }
1713
        K key = requireNonNull(cursor).getKey();
1 ✔
1714
        Entry<K, V> entry = cursor;
1 ✔
1715
        removalKey = key;
1 ✔
1716
        cursor = null;
1 ✔
1717
        return entry;
1 ✔
1718
      }
1719

1720
      @Override
1721
      public void remove() {
1722
        requireState(removalKey != null);
1 ✔
1723
        delegate.remove(removalKey);
1 ✔
1724
        removalKey = null;
1 ✔
1725
      }
1 ✔
1726
    }
1727

1728
    private final class EntrySpliterator implements Spliterator<Entry<K, V>> {
1729
      final Spliterator<Entry<K, CompletableFuture<V>>> spliterator;
1730

1731
      EntrySpliterator() {
1732
        this(delegate.entrySet().spliterator());
1 ✔
1733
      }
1 ✔
1734

1735
      EntrySpliterator(Spliterator<Entry<K, CompletableFuture<V>>> spliterator) {
1 ✔
1736
        this.spliterator = requireNonNull(spliterator);
1 ✔
1737
      }
1 ✔
1738

1739
      @Override
1740
      public void forEachRemaining(Consumer<? super Entry<K, V>> action) {
1741
        requireNonNull(action);
1 ✔
1742
        spliterator.forEachRemaining(entry -> {
1 ✔
1743
          V value = Async.getIfReady(entry.getValue());
1 ✔
1744
          if (value != null) {
1 ✔
1745
            var e = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
1 ✔
1746
            action.accept(e);
1 ✔
1747
          }
1748
        });
1 ✔
1749
      }
1 ✔
1750

1751
      @Override
1752
      public boolean tryAdvance(Consumer<? super Entry<K, V>> action) {
1753
        requireNonNull(action);
1 ✔
1754
        boolean[] advanced = { false };
1 ✔
1755
        Consumer<? super Entry<K, CompletableFuture<V>>> consumer = entry -> {
1 ✔
1756
          V value = Async.getIfReady(entry.getValue());
1 ✔
1757
          if (value != null) {
1 ✔
1758
            var e = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
1 ✔
1759
            action.accept(e);
1 ✔
1760
            advanced[0] = true;
1 ✔
1761
          }
1762
        };
1 ✔
1763
        while (spliterator.tryAdvance(consumer)) {
1 ✔
1764
          if (advanced[0]) {
1 ✔
1765
            return true;
1 ✔
1766
          }
1767
        }
1768
        return false;
1 ✔
1769
      }
1770

1771
      @Override
1772
      public @Nullable Spliterator<Entry<K, V>> trySplit() {
1773
        Spliterator<Entry<K, CompletableFuture<V>>> split = spliterator.trySplit();
1 ✔
1774
        return (split == null) ? null : new EntrySpliterator(split);
1 ✔
1775
      }
1776

1777
      @Override
1778
      public long estimateSize() {
1779
        return spliterator.estimateSize();
1 ✔
1780
      }
1781

1782
      @Override
1783
      public int characteristics() {
1784
        return DISTINCT | CONCURRENT | NONNULL;
1 ✔
1785
      }
1786
    }
1787
  }
1788

1789
  @SuppressWarnings("serial")
1790
  final class SyncViewProxy<K, V> implements Serializable {
1791
    private static final long serialVersionUID = 1;
1792

1793
    final AsyncCache<K, V> asyncCache;
1794

1795
    SyncViewProxy(AsyncCache<K, V> asyncCache) {
1 ✔
1796
      this.asyncCache = requireNonNull(asyncCache);
1 ✔
1797
    }
1 ✔
1798

1799
    @SuppressWarnings("unused")
1800
    private void readObject(ObjectInputStream stream)
1801
        throws IOException, ClassNotFoundException {
1802
      stream.defaultReadObject();
1 ✔
1803
      if (asyncCache == null) {
1 ✔
1804
        throw new InvalidObjectException("asyncCache required");
1 ✔
1805
      }
1806
    }
1 ✔
1807

1808
    Object readResolve() {
1809
      return asyncCache.synchronous();
1 ✔
1810
    }
1811
  }
1812
}
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