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

ben-manes / caffeine / #5836

20 Oct 2026 07:25AM UTC coverage: 99.989% (-0.01%) from 100.0%
#5836

push

github

ben-manes
join an in-flight refresh of an expired entry

A refresh of an expired or collected entry replaced any registration,
so while another refresh's loader was still running, each caller
replaced the last and loaded again: 8 refreshes made 8 loads and kept
only the last value. Such a refresh now joins the placeholder and
takes the linearization point of the refresh it joined, so a reload
whose entry expired during it is discarded at commit as it is when the
entry expires after the join. This also rejects a loader refreshing its
own key without a separate owner check.

4872 of 4882 branches covered (99.8%)

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

1 existing line in 1 file now uncovered.

9348 of 9349 relevant lines covered (99.99%)

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> implements BiFunction<@Nullable Map<? extends K, ? extends V>,
315
      @Nullable Throwable, @Nullable Map<? extends K, ? extends V>> {
316
    @SuppressWarnings("ImmutableMemberCollection")
317
    private final Map<K, CompletableFuture<@Nullable V>> proxies;
318
    private final LocalCache<K, CompletableFuture<V>> cache;
319
    private final long startTime;
320

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1149
      @SuppressWarnings({"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) {
1 ✔
1158
                return null;
1 ✔
1159
              } else if (!oldValueFuture.isDone()) {
1 ✔
1160
                hints.preserveEntry();
1 ✔
1161
                return oldValueFuture;
1 ✔
1162
              }
1163

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1834
    final AsyncCache<K, V> asyncCache;
1835

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

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

1849
    Object readResolve() {
1850
      return asyncCache.synchronous();
1 ✔
1851
    }
1852
  }
1853
}
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