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

ben-manes / caffeine / #5557

29 Jun 2026 01:54AM UTC coverage: 99.976% (-0.02%) from 100.0%
#5557

push

github

ben-manes
Align async asMap().computeIfAbsent stats with get

AsyncAsMapView.computeIfAbsent hand-rolled its hit/miss stats and
diverged from AsyncCache.get and the sync asMap().computeIfAbsent:
it deferred a conditional hit and recorded nothing on a throwing or
null-returning mapping function. Mirror get's mechanism
(recordStats=true + handleCompletion(recordMiss=false)) so hit/miss
flow through the same statsAware/afterRead path.

It can't delegate to get directly: get's requireNonNull on the
mapping result and the returned future would break the
Map.computeIfAbsent null-return contract, so this inlines get's body
without those checks.

Stats now match get: present->hit, absent+value->miss+success,
absent+throw->miss+failure, absent+null->miss. The
computeIfAbsent_error/_nullValue/_present_failed assertions pinned
the prior incidental behavior and are corrected to the sync sibling.

4045 of 4054 branches covered (99.78%)

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

2 existing lines in 2 files now uncovered.

8243 of 8245 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 java.util.Locale.US;
21
import static java.util.Objects.requireNonNull;
22

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

52
import org.jspecify.annotations.Nullable;
53

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

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

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

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

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

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

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

92
  default CompletableFuture<V> get(K key, BiFunction<? super K, ? super Executor,
93
      ? extends CompletableFuture<? extends V>> mappingFunction, boolean recordStats) {
94
    long startTime = cache().statsTicker().read();
1✔
95
    @SuppressWarnings({"rawtypes", "unchecked"})
96
    @Nullable CompletableFuture<? extends V>[] result = new CompletableFuture[1];
1✔
97
    CompletableFuture<V> future = cache().computeIfAbsent(key, k -> {
1✔
98
      @SuppressWarnings("unchecked")
99
      var castedResult = (CompletableFuture<V>) mappingFunction.apply(key, cache().executor());
1✔
100
      result[0] = castedResult;
1✔
101
      return requireNonNull(castedResult);
1✔
102
    }, recordStats, /* recordLoad= */ false);
103
    if (result[0] != null) {
1✔
104
      handleCompletion(key, result[0], startTime, /* recordMiss= */ false);
1✔
105
    }
106
    return requireNonNull(future);
1✔
107
  }
108

109
  @Override
110
  default CompletableFuture<Map<K, V>> getAll(Iterable<? extends K> keys,
111
      Function<? super Set<? extends K>, ? extends Map<? extends K, ? extends V>> mappingFunction) {
112
    requireNonNull(mappingFunction);
1✔
113
    return getAll(keys, (keysToLoad, executor) ->
1✔
114
        CompletableFuture.supplyAsync(() -> mappingFunction.apply(keysToLoad), executor));
1✔
115
  }
116

117
  @Override
118
  @SuppressWarnings("FutureReturnValueIgnored")
119
  default CompletableFuture<Map<K, V>> getAll(Iterable<? extends K> keys,
120
      BiFunction<? super Set<? extends K>, ? super Executor,
121
          ? extends CompletableFuture<? extends Map<? extends K, ? extends V>>> mappingFunction) {
122
    requireNonNull(mappingFunction);
1✔
123
    requireNonNull(keys);
1✔
124

125
    int initialCapacity = calculateHashMapCapacity(keys);
1✔
126
    var futures = new LinkedHashMap<K, CompletableFuture<@Nullable V>>(initialCapacity);
1✔
127
    var proxies = new LinkedHashMap<K, CompletableFuture<@Nullable V>>(initialCapacity);
1✔
128
    for (K key : keys) {
1✔
129
      if (futures.containsKey(key)) {
1✔
130
        continue;
1✔
131
      }
132
      @Var CompletableFuture<V> future = cache().getIfPresent(key, /* recordStats= */ false);
1✔
133
      if (future == null) {
1✔
134
        var proxy = new CompletableFuture<V>();
1✔
135
        future = cache().putIfAbsent(key, proxy);
1✔
136
        if (future == null) {
1✔
137
          future = proxy;
1✔
138
          proxies.put(key, proxy);
1✔
139
        }
140
      }
141
      futures.put(key, future);
1✔
142
    }
1✔
143
    cache().statsCounter().recordMisses(proxies.size());
1✔
144
    cache().statsCounter().recordHits(futures.size() - proxies.size());
1✔
145
    if (proxies.isEmpty()) {
1✔
146
      return composeResult(futures);
1✔
147
    }
148

149
    var completer = new AsyncBulkCompleter<>(cache(), proxies);
1✔
150
    try {
151
      var loader = mappingFunction.apply(
1✔
152
          Collections.unmodifiableSet(proxies.keySet()), cache().executor());
1✔
153
      return loader.handle(completer).thenCompose(ignored -> composeResult(futures));
1✔
154
    } catch (Throwable t) {
1✔
155
      throw completer.error(t);
1✔
156
    }
157
  }
158

159
  /**
160
   * Returns a future that waits for all of the dependent futures to complete and returns the
161
   * combined mapping if successful. If any future fails then it is automatically removed from
162
   * the cache if still present.
163
   */
164
  static <K, V> CompletableFuture<Map<K, V>> composeResult(
165
      Map<K, CompletableFuture<@Nullable V>> futures) {
166
    if (futures.isEmpty()) {
1✔
167
      @SuppressWarnings({"ImmutableMapOf", "RedundantUnmodifiable"})
168
      Map<K, V> emptyMap = Collections.unmodifiableMap(Collections.emptyMap());
1✔
169
      return CompletableFuture.completedFuture(emptyMap);
1✔
170
    }
171
    @SuppressWarnings("rawtypes")
172
    CompletableFuture<?>[] array = futures.values().toArray(new CompletableFuture[0]);
1✔
173
    return CompletableFuture.allOf(array).thenApply(ignored -> {
1✔
174
      var result = new LinkedHashMap<K, V>(calculateHashMapCapacity(futures.size()));
1✔
175
      futures.forEach((key, future) -> {
1✔
176
        @Nullable V value = future.getNow(null);
1✔
177
        if (value != null) {
1✔
178
          result.put(key, value);
1✔
179
        }
180
      });
1✔
181
      return Collections.unmodifiableMap(result);
1✔
182
    });
183
  }
184

185
  @Override
186
  @SuppressWarnings("FutureReturnValueIgnored")
187
  default void put(K key, CompletableFuture<? extends @Nullable V> valueFuture) {
188
    if (valueFuture.isDone() && (Async.getWhenSuccessful(valueFuture) == null)) {
1✔
189
      cache().statsCounter().recordLoadFailure(0L);
1✔
190
      cache().remove(key);
1✔
191
      return;
1✔
192
    }
193
    long startTime = cache().statsTicker().read();
1✔
194

195
    @SuppressWarnings("unchecked")
196
    var castedFuture = (CompletableFuture<V>) valueFuture;
1✔
197
    CompletableFuture<V> prior = cache().put(key, castedFuture);
1✔
198
    if (prior != castedFuture) {
1✔
199
      handleCompletion(key, valueFuture, startTime, /* recordMiss= */ false);
1✔
200
    }
201
  }
1✔
202

203
  @SuppressWarnings({"FutureReturnValueIgnored", "ResultOfMethodCallIgnored"})
204
  default void handleCompletion(K key, CompletableFuture<? extends V> valueFuture,
205
      long startTime, boolean recordMiss) {
206
    valueFuture.whenComplete((value, error) -> {
1✔
207
      long loadTime = cache().statsTicker().read() - startTime;
1✔
208
      if (value == null) {
1✔
209
        if ((error != null) && !(error instanceof CancellationException)
1✔
210
            && !(error instanceof TimeoutException)) {
211
          logger.log(Level.WARNING, "Exception thrown during asynchronous load", error);
1✔
212
        }
213
        cache().statsCounter().recordLoadFailure(loadTime);
1✔
214
        cache().remove(key, valueFuture);
1✔
215
      } else if (!Async.isReady(valueFuture)) {
1✔
216
        logger.log(Level.ERROR, String.format(US, "An invalid state was detected, occurring when "
1✔
217
            + "the future's dependent action has completed successfully with a value, but the "
218
            + "future remains either in-flight or in a failed completion state. This may occur "
219
            + "when using a custom future that does not abide by the CompletableFuture contract "
220
            + "(key: %s, key type: %s, value type: %s, future: %s, cache type: %s).",
221
            key, key.getClass().getName(), value.getClass().getName(), valueFuture,
1✔
222
            cache().getClass().getSimpleName()), new IllegalStateException());
1✔
223
        cache().statsCounter().recordLoadFailure(loadTime);
1✔
224
        cache().remove(key, valueFuture);
1✔
225
      } else {
226
        @SuppressWarnings("unchecked")
227
        var castedFuture = (CompletableFuture<V>) valueFuture;
1✔
228

229
        try {
230
          // update the weight and expiration timestamps
231
          cache().replace(key, castedFuture, castedFuture, /* shouldDiscardRefresh= */ false);
1✔
232
          cache().statsCounter().recordLoadSuccess(loadTime);
1✔
233
        } catch (Throwable t) {
1✔
234
          logger.log(Level.WARNING, "Exception thrown during asynchronous load", t);
1✔
235
          cache().statsCounter().recordLoadFailure(loadTime);
1✔
236
          cache().remove(key, valueFuture);
1✔
237
        }
1✔
238
      }
239
      if (recordMiss) {
1!
UNCOV
240
        cache().statsCounter().recordMisses(1);
×
241
      }
242
    });
1✔
243
  }
1✔
244

245
  /** A function executed asynchronously after a bulk load completes. */
246
  final class AsyncBulkCompleter<K, V> implements BiFunction<
247
      Map<? extends K, ? extends V>, Throwable, Map<? extends K, ? extends V>> {
248
    @SuppressWarnings("ImmutableMemberCollection")
249
    private final Map<K, CompletableFuture<@Nullable V>> proxies;
250
    private final LocalCache<K, CompletableFuture<V>> cache;
251
    private final long startTime;
252

253
    AsyncBulkCompleter(LocalCache<K, CompletableFuture<V>> cache,
254
        Map<K, CompletableFuture<@Nullable V>> proxies) {
1✔
255
      this.startTime = cache.statsTicker().read();
1✔
256
      this.proxies = proxies;
1✔
257
      this.cache = cache;
1✔
258
    }
1✔
259

260
    @Override
261
    @CanIgnoreReturnValue
262
    public @Nullable Map<? extends K, ? extends V> apply(
263
        @Nullable Map<? extends K, ? extends V> result, @Nullable Throwable error) {
264
      long loadTime = cache.statsTicker().read() - startTime;
1✔
265
      var failure = handleResponse(result, error);
1✔
266

267
      if (failure == null) {
1✔
268
        if (requireNonNull(result).isEmpty()) {
1✔
269
          cache.statsCounter().recordLoadFailure(loadTime);
1✔
270
        } else {
271
          cache.statsCounter().recordLoadSuccess(loadTime);
1✔
272
        }
273
        return result;
1✔
274
      }
275

276
      cache.statsCounter().recordLoadFailure(loadTime);
1✔
277
      if (failure instanceof RuntimeException) {
1✔
278
        throw (RuntimeException) failure;
1✔
279
      } else if (failure instanceof Error) {
1✔
280
        throw (Error) failure;
1✔
281
      }
282
      throw new CompletionException(failure);
1✔
283
    }
284

285
    public CompletionException error(Throwable error) {
286
      long loadTime = cache.statsTicker().read() - startTime;
1✔
287
      var failure = handleResponse(/* result= */ null, error);
1✔
288
      cache.statsCounter().recordLoadFailure(loadTime);
1✔
289
      if (failure instanceof RuntimeException) {
1✔
290
        throw (RuntimeException) failure;
1✔
291
      } else if (failure instanceof Error) {
1✔
292
        throw (Error) failure;
1✔
293
      }
294
      return new CompletionException(failure);
1✔
295
    }
296

297
    private @Nullable Throwable handleResponse(
298
        @Nullable Map<? extends K, ? extends V> result, @Nullable Throwable error) {
299
      if (result == null) {
1✔
300
        var failure = (error == null) ? new NullMapCompletionException() : error;
1✔
301
        for (var entry : proxies.entrySet()) {
1✔
302
          cache.remove(entry.getKey(), entry.getValue());
1✔
303
          entry.getValue().obtrudeException(failure);
1✔
304
        }
1✔
305
        if (!(failure instanceof CancellationException) && !(failure instanceof TimeoutException)) {
1✔
306
          logger.log(Level.WARNING, "Exception thrown during asynchronous load", failure);
1✔
307
        }
308
        return failure;
1✔
309
      }
310
      var failure = fillProxies(result);
1✔
311
      return addNewEntries(result, failure);
1✔
312
    }
313

314
    /** Populates the proxies with the computed result. */
315
    private @Nullable Throwable fillProxies(Map<? extends K, ? extends V> result) {
316
      @Var Throwable error = null;
1✔
317
      for (var entry : proxies.entrySet()) {
1✔
318
        var key = entry.getKey();
1✔
319
        var value = result.get(key);
1✔
320
        var future = entry.getValue();
1✔
321
        future.obtrudeValue(value);
1✔
322

323
        if (value == null) {
1✔
324
          cache.remove(key, future);
1✔
325
        } else {
326
          try {
327
            // update the weight and expiration timestamps
328
            cache.replace(key, future, future);
1✔
329
          } catch (Throwable t) {
1✔
330
            logger.log(Level.WARNING, "Exception thrown during asynchronous load", t);
1✔
331
            cache.remove(key, future);
1✔
332
            if (error == null) {
1✔
333
              error = t;
1✔
334
            } else if (error != t) {
1✔
335
              error.addSuppressed(t);
1✔
336
            }
337
          }
1✔
338
        }
339
      }
1✔
340
      return error;
1✔
341
    }
342

343
    /** Adds to the cache any extra entries computed that were not requested. */
344
    private @Nullable Throwable addNewEntries(
345
        Map<? extends K, ? extends @Nullable V> result, @Nullable Throwable failure) {
346
      @Var Throwable error = failure;
1✔
347
      for (Map.Entry<? extends K, ? extends @Nullable V> entry : result.entrySet()) {
1✔
348
        var key = entry.getKey();
1✔
349
        @Nullable V value = entry.getValue();
1✔
350
        if (!proxies.containsKey(key)) {
1✔
351
          if (value == null) {
1✔
352
            continue;
1✔
353
          }
354
          try {
355
            cache.put(key, CompletableFuture.completedFuture(value));
1✔
356
          } catch (Throwable t) {
1✔
357
            logger.log(Level.WARNING, "Exception thrown during asynchronous load", t);
1✔
358
            if (error == null) {
1✔
359
              error = t;
1✔
360
            } else if (error != t) {
1✔
361
              error.addSuppressed(t);
1✔
362
            }
363
          }
1✔
364
        }
365
      }
1✔
366
      return error;
1✔
367
    }
368

369
    static final class NullMapCompletionException extends CompletionException {
1✔
370
      private static final long serialVersionUID = 1L;
371
    }
372
  }
373

374
  /* --------------- Asynchronous view --------------- */
375
  final class AsyncAsMapView<K, V> implements ConcurrentMap<K, CompletableFuture<V>> {
376
    final LocalAsyncCache<K, V> asyncCache;
377

378
    AsyncAsMapView(LocalAsyncCache<K, V> asyncCache) {
1✔
379
      this.asyncCache = requireNonNull(asyncCache);
1✔
380
    }
1✔
381
    @Override public boolean isEmpty() {
382
      return asyncCache.cache().isEmpty();
1✔
383
    }
384
    @Override public int size() {
385
      return asyncCache.cache().size();
1✔
386
    }
387
    @Override public void clear() {
388
      asyncCache.cache().clear();
1✔
389
    }
1✔
390
    @Override public boolean containsKey(Object key) {
391
      return asyncCache.cache().containsKey(key);
1✔
392
    }
393
    @SuppressWarnings("CollectionUndefinedEquality")
394
    @Override public boolean containsValue(Object value) {
395
      return asyncCache.cache().containsValue(value);
1✔
396
    }
397
    @Override public @Nullable CompletableFuture<V> get(Object key) {
398
      return asyncCache.cache().get(key);
1✔
399
    }
400
    @Override public @Nullable CompletableFuture<V> putIfAbsent(K key, CompletableFuture<V> value) {
401
      CompletableFuture<V> prior = asyncCache.cache().putIfAbsent(key, value);
1✔
402
      long startTime = asyncCache.cache().statsTicker().read();
1✔
403
      if (prior == null) {
1✔
404
        asyncCache.handleCompletion(key, value, startTime, /* recordMiss= */ false);
1✔
405
      }
406
      return prior;
1✔
407
    }
408
    @Override public @Nullable CompletableFuture<V> put(K key, CompletableFuture<V> value) {
409
      CompletableFuture<V> prior = asyncCache.cache().put(key, value);
1✔
410
      long startTime = asyncCache.cache().statsTicker().read();
1✔
411
      if (prior != value) {
1✔
412
        asyncCache.handleCompletion(key, value, startTime, /* recordMiss= */ false);
1✔
413
      }
414
      return prior;
1✔
415
    }
416
    @SuppressWarnings({"FutureReturnValueIgnored", "ResultOfMethodCallIgnored"})
417
    @Override public void putAll(Map<? extends K, ? extends CompletableFuture<V>> map) {
418
      map.forEach(this::put);
1✔
419
    }
1✔
420
    @Override public @Nullable CompletableFuture<V> replace(K key, CompletableFuture<V> value) {
421
      CompletableFuture<V> prior = asyncCache.cache().replace(key, value);
1✔
422
      long startTime = asyncCache.cache().statsTicker().read();
1✔
423
      if ((prior != null) && (prior != value)) {
1✔
424
        asyncCache.handleCompletion(key, value, startTime, /* recordMiss= */ false);
1✔
425
      }
426
      return prior;
1✔
427
    }
428
    @Override
429
    public boolean replace(K key, CompletableFuture<V> oldValue, CompletableFuture<V> newValue) {
430
      boolean replaced = asyncCache.cache().replace(key, oldValue, newValue);
1✔
431
      long startTime = asyncCache.cache().statsTicker().read();
1✔
432
      if (replaced && (newValue != oldValue)) {
1✔
433
        asyncCache.handleCompletion(key, newValue, startTime, /* recordMiss= */ false);
1✔
434
      }
435
      return replaced;
1✔
436
    }
437
    @Override public CompletableFuture<V> remove(Object key) {
438
      return asyncCache.cache().remove(key);
1✔
439
    }
440
    @Override public boolean remove(Object key, Object value) {
441
      return asyncCache.cache().remove(key, value);
1✔
442
    }
443
    @Override public @Nullable CompletableFuture<V> computeIfAbsent(K key,
444
        Function<? super K, ? extends @Nullable CompletableFuture<V>> mappingFunction) {
445
      requireNonNull(mappingFunction);
1✔
446
      @SuppressWarnings({"rawtypes", "unchecked"})
447
      @Nullable CompletableFuture<V>[] result = new CompletableFuture[1];
1✔
448
      long startTime = asyncCache.cache().statsTicker().read();
1✔
449
      Function<K, @Nullable CompletableFuture<V>> function = k -> {
1✔
450
        result[0] = mappingFunction.apply(k);
1✔
451
        return result[0];
1✔
452
      };
453
      @Nullable CompletableFuture<V> future = asyncCache.cache().computeIfAbsent(
1✔
454
          key, function, /* recordStats= */ true, /* recordLoad= */ false);
455
      if (result[0] != null) {
1✔
456
        asyncCache.handleCompletion(key, result[0], startTime, /* recordMiss= */ false);
1✔
457
      }
458
      return future;
1✔
459
    }
460
    @SuppressWarnings("ResultOfMethodCallIgnored")
461
    @Override public @Nullable CompletableFuture<V> computeIfPresent(K key, BiFunction<? super K,
462
        ? super CompletableFuture<V>, ? extends CompletableFuture<V>> remappingFunction) {
463
      requireNonNull(remappingFunction);
1✔
464

465
      @SuppressWarnings({"rawtypes", "unchecked"})
466
      @Nullable CompletableFuture<V>[] result = new CompletableFuture[1];
1✔
467
      @SuppressWarnings({"rawtypes", "unchecked"})
468
      @Nullable CompletableFuture<V>[] prior = new CompletableFuture[1];
1✔
469
      long startTime = asyncCache.cache().statsTicker().read();
1✔
470
      asyncCache.cache().compute(key, (K k, @Nullable CompletableFuture<V> oldValue) -> {
1✔
471
        result[0] = (oldValue == null) ? null : remappingFunction.apply(k, oldValue);
1✔
472
        prior[0] = oldValue;
1✔
473
        return result[0];
1✔
474
      }, asyncCache.cache().expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
1✔
475

476
      if ((result[0] != null) && (result[0] != prior[0])) {
1✔
477
        asyncCache.handleCompletion(key, result[0], startTime, /* recordMiss= */ false);
1✔
478
      }
479
      return result[0];
1✔
480
    }
481
    @SuppressWarnings("ResultOfMethodCallIgnored")
482
    @Override public @Nullable CompletableFuture<V> compute(K key, BiFunction<? super K,
483
        ? super CompletableFuture<V>, ? extends CompletableFuture<V>> remappingFunction) {
484
      requireNonNull(remappingFunction);
1✔
485

486
      @SuppressWarnings({"rawtypes", "unchecked"})
487
      @Nullable CompletableFuture<V>[] result = new CompletableFuture[1];
1✔
488
      @SuppressWarnings({"rawtypes", "unchecked"})
489
      @Nullable CompletableFuture<V>[] prior = new CompletableFuture[1];
1✔
490
      long startTime = asyncCache.cache().statsTicker().read();
1✔
491
      asyncCache.cache().compute(key, (k, oldValue) -> {
1✔
492
        result[0] = remappingFunction.apply(k, oldValue);
1✔
493
        prior[0] = oldValue;
1✔
494
        return result[0];
1✔
495
      }, asyncCache.cache().expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
1✔
496

497
      if ((result[0] != null) && (result[0] != prior[0])) {
1✔
498
        asyncCache.handleCompletion(key, result[0], startTime, /* recordMiss= */ false);
1✔
499
      }
500
      return result[0];
1✔
501
    }
502
    @SuppressWarnings("ResultOfMethodCallIgnored")
503
    @Override public @Nullable CompletableFuture<V> merge(K key, CompletableFuture<V> value,
504
        BiFunction<? super CompletableFuture<V>, ? super CompletableFuture<V>,
505
            ? extends CompletableFuture<V>> remappingFunction) {
506
      requireNonNull(value);
1✔
507
      requireNonNull(remappingFunction);
1✔
508

509
      @SuppressWarnings({"rawtypes", "unchecked"})
510
      @Nullable  CompletableFuture<V>[] result = new CompletableFuture[1];
1✔
511
      @SuppressWarnings({"rawtypes", "unchecked"})
512
      @Nullable  CompletableFuture<V>[] prior = new CompletableFuture[1];
1✔
513
      long startTime = asyncCache.cache().statsTicker().read();
1✔
514
      asyncCache.cache().compute(key, (K k, @Nullable CompletableFuture<V> oldValue) -> {
1✔
515
        result[0] = (oldValue == null) ? value : remappingFunction.apply(oldValue, value);
1✔
516
        prior[0] = oldValue;
1✔
517
        return result[0];
1✔
518
      }, asyncCache.cache().expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
1✔
519

520
      if ((result[0] != null) && (result[0] != prior[0])) {
1✔
521
        asyncCache.handleCompletion(key, result[0], startTime, /* recordMiss= */ false);
1✔
522
      }
523
      return result[0];
1✔
524
    }
525
    @Override public void forEach(BiConsumer<? super K, ? super CompletableFuture<V>> action) {
526
      asyncCache.cache().forEach(action);
1✔
527
    }
1✔
528
    @Override public Set<K> keySet() {
529
      return asyncCache.cache().keySet();
1✔
530
    }
531
    @Override public Collection<CompletableFuture<V>> values() {
532
      return asyncCache.cache().values();
1✔
533
    }
534
    @Override public Set<Entry<K, CompletableFuture<V>>> entrySet() {
535
      return new AsyncEntrySet();
1✔
536
    }
537
    @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
538
    @Override public boolean equals(@Nullable Object o) {
539
      return asyncCache.cache().equals(o);
1✔
540
    }
541
    @Override public int hashCode() {
542
      return asyncCache.cache().hashCode();
1✔
543
    }
544
    @Override public String toString() {
545
      return asyncCache.cache().toString();
1✔
546
    }
547

548
    private final class AsyncEntrySet extends AbstractSet<Entry<K, CompletableFuture<V>>> {
1✔
549
      @Override public int size() {
550
        return AsyncAsMapView.this.size();
1✔
551
      }
552
      @Override public boolean isEmpty() {
553
        return AsyncAsMapView.this.isEmpty();
1✔
554
      }
555
      @Override public void clear() {
556
        AsyncAsMapView.this.clear();
1✔
557
      }
1✔
558
      @Override public boolean contains(Object o) {
559
        return asyncCache.cache().entrySet().contains(o);
1✔
560
      }
561
      @Override public boolean remove(Object o) {
562
        return asyncCache.cache().entrySet().remove(o);
1✔
563
      }
564
      @Override public boolean removeAll(Collection<?> collection) {
565
        return asyncCache.cache().entrySet().removeAll(collection);
1✔
566
      }
567
      @Override public boolean retainAll(Collection<?> collection) {
568
        return asyncCache.cache().entrySet().retainAll(collection);
1✔
569
      }
570
      @Override public Iterator<Entry<K, CompletableFuture<V>>> iterator() {
571
        return new AsyncEntryIterator();
1✔
572
      }
573
      @Override public Spliterator<Entry<K, CompletableFuture<V>>> spliterator() {
574
        return new AsyncEntrySpliterator();
1✔
575
      }
576
    }
577

578
    private final class AsyncEntryIterator implements Iterator<Entry<K, CompletableFuture<V>>> {
1✔
579
      final Iterator<Entry<K, CompletableFuture<V>>> iterator =
1✔
580
          asyncCache.cache().entrySet().iterator();
1✔
581

582
      @Override public boolean hasNext() {
583
        return iterator.hasNext();
1✔
584
      }
585
      @Override public Entry<K, CompletableFuture<V>> next() {
586
        var entry = iterator.next();
1✔
587
        return new WriteThroughEntry<>(AsyncAsMapView.this, entry.getKey(), entry.getValue());
1✔
588
      }
589
      @Override public void remove() {
590
        iterator.remove();
1✔
591
      }
1✔
592
    }
593

594
    private final class AsyncEntrySpliterator
595
        implements Spliterator<Entry<K, CompletableFuture<V>>> {
596
      final Spliterator<Entry<K, CompletableFuture<V>>> spliterator;
597

598
      AsyncEntrySpliterator() {
599
        this(asyncCache.cache().entrySet().spliterator());
1✔
600
      }
1✔
601
      AsyncEntrySpliterator(Spliterator<Entry<K, CompletableFuture<V>>> spliterator) {
1✔
602
        this.spliterator = requireNonNull(spliterator);
1✔
603
      }
1✔
604
      @Override public boolean tryAdvance(Consumer<? super Entry<K, CompletableFuture<V>>> action) {
605
        requireNonNull(action);
1✔
606
        return spliterator.tryAdvance(entry -> action.accept(
1✔
607
            new WriteThroughEntry<>(AsyncAsMapView.this, entry.getKey(), entry.getValue())));
1✔
608
      }
609
      @Override public void forEachRemaining(
610
          Consumer<? super Entry<K, CompletableFuture<V>>> action) {
611
        requireNonNull(action);
1✔
612
        spliterator.forEachRemaining(entry -> action.accept(
1✔
613
            new WriteThroughEntry<>(AsyncAsMapView.this, entry.getKey(), entry.getValue())));
1✔
614
      }
1✔
615
      @Override public @Nullable Spliterator<Entry<K, CompletableFuture<V>>> trySplit() {
616
        var split = spliterator.trySplit();
1✔
617
        return (split == null) ? null : new AsyncEntrySpliterator(split);
1✔
618
      }
619
      @Override public long estimateSize() {
620
        return spliterator.estimateSize();
1✔
621
      }
622
      @Override public int characteristics() {
623
        return spliterator.characteristics();
1✔
624
      }
625
    }
626
  }
627

628
  /* --------------- Synchronous view --------------- */
629
  final class CacheView<K, V> extends AbstractCacheView<K, V> {
630
    private static final long serialVersionUID = 1L;
631

632
    @SuppressWarnings("serial")
633
    final LocalAsyncCache<K, V> asyncCache;
634

635
    CacheView(LocalAsyncCache<K, V> asyncCache) {
1✔
636
      this.asyncCache = requireNonNull(asyncCache);
1✔
637
    }
1✔
638
    @Override LocalAsyncCache<K, V> asyncCache() {
639
      return asyncCache;
1✔
640
    }
641
  }
642

643
  abstract class AbstractCacheView<K, V> implements Cache<K, V>, Serializable {
1✔
644
    private static final long serialVersionUID = 1L;
645

646
    transient @Nullable ConcurrentMap<K, V> asMapView;
647

648
    abstract LocalAsyncCache<K, V> asyncCache();
649

650
    @Override
651
    public @Nullable V getIfPresent(K key) {
652
      var future = asyncCache().cache().getIfPresent(key, /* recordStats= */ false);
1✔
653
      var value = Async.getIfReady(future);
1✔
654
      if (value == null) {
1✔
655
        asyncCache().cache().statsCounter().recordMisses(1);
1✔
656
      } else {
657
        asyncCache().cache().statsCounter().recordHits(1);
1✔
658
      }
659
      return value;
1✔
660
    }
661

662
    @Override
663
    public Map<K, V> getAllPresent(Iterable<? extends K> keys) {
664
      var result = new LinkedHashMap<K, @Nullable V>(calculateHashMapCapacity(keys));
1✔
665
      for (K key : keys) {
1✔
666
        result.put(key, null);
1✔
667
      }
1✔
668

669
      int uniqueKeys = result.size();
1✔
670
      for (var iter = result.entrySet().iterator(); iter.hasNext();) {
1✔
671
        Map.Entry<K, @Nullable V> entry = iter.next();
1✔
672

673
        CompletableFuture<V> future = asyncCache().cache().get(entry.getKey());
1✔
674
        V value = Async.getIfReady(future);
1✔
675
        if (value == null) {
1✔
676
          iter.remove();
1✔
677
        } else {
678
          entry.setValue(value);
1✔
679
        }
680
      }
1✔
681
      asyncCache().cache().statsCounter().recordHits(result.size());
1✔
682
      asyncCache().cache().statsCounter().recordMisses(uniqueKeys - result.size());
1✔
683

684
      @SuppressWarnings("NullableProblems")
685
      Map<K, V> unmodifiable = Collections.unmodifiableMap(result);
1✔
686
      return unmodifiable;
1✔
687
    }
688

689
    @Override
690
    public V get(K key, Function<? super K, ? extends V> mappingFunction) {
691
      return resolve(asyncCache().get(key, mappingFunction));
1✔
692
    }
693

694
    @Override
695
    public Map<K, V> getAll(
696
        Iterable<? extends K> keys,
697
        Function<
698
            ? super Set<? extends K>,
699
            ? extends Map<? extends K, ? extends V>> mappingFunction) {
700
      return resolve(asyncCache().getAll(keys, mappingFunction));
1✔
701
    }
702

703
    @SuppressWarnings({"PMD.AvoidThrowingNullPointerException",
704
      "PMD.PreserveStackTrace", "UnusedException"})
705
    protected static <T> T resolve(CompletableFuture<T> future) {
706
      try {
707
        return future.join();
1✔
708
      } catch (NullMapCompletionException e) {
1✔
709
        throw new NullPointerException("null map");
1✔
710
      } catch (CompletionException e) {
1✔
711
        if (e.getCause() instanceof RuntimeException) {
1✔
712
          throw (RuntimeException) e.getCause();
1✔
713
        } else if (e.getCause() instanceof Error) {
1✔
714
          throw (Error) e.getCause();
1✔
715
        }
716
        throw e;
1✔
717
      }
718
    }
719

720
    @Override
721
    public void put(K key, V value) {
722
      requireNonNull(value);
1✔
723
      asyncCache().cache().put(key, CompletableFuture.completedFuture(value));
1✔
724
    }
1✔
725

726
    @Override
727
    public void putAll(Map<? extends K, ? extends V> map) {
728
      map.forEach(this::put);
1✔
729
    }
1✔
730

731
    @Override
732
    public void invalidate(K key) {
733
      asyncCache().cache().remove(key);
1✔
734
    }
1✔
735

736
    @Override
737
    public void invalidateAll(Iterable<? extends K> keys) {
738
      asyncCache().cache().invalidateAll(keys);
1✔
739
    }
1✔
740

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

746
    @Override
747
    public long estimatedSize() {
748
      return asyncCache().cache().estimatedSize();
1✔
749
    }
750

751
    @Override
752
    public CacheStats stats() {
753
      return asyncCache().cache().statsCounter().snapshot();
1✔
754
    }
755

756
    @Override
757
    public void cleanUp() {
758
      asyncCache().cache().cleanUp();
1✔
759
    }
1✔
760

761
    @Override
762
    public Policy<K, V> policy() {
763
      return asyncCache().policy();
1✔
764
    }
765

766
    @Override
767
    public ConcurrentMap<K, V> asMap() {
768
      return (asMapView == null) ? (asMapView = new AsMapView<>(asyncCache().cache())) : asMapView;
1✔
769
    }
770

771
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
772
      throw new InvalidObjectException("Proxy required");
1✔
773
    }
774

775
    @SuppressWarnings("unused")
776
    private void readObjectNoData() throws InvalidObjectException {
777
      throw new InvalidObjectException("Proxy required");
1✔
778
    }
779

780
    Object writeReplace() {
781
      return new SyncViewProxy<>(asyncCache());
1✔
782
    }
783
  }
784

785
  final class AsMapView<K, V> implements ConcurrentMap<K, V> {
786
    final LocalCache<K, CompletableFuture<V>> delegate;
787

788
    @Nullable Set<K> keys;
789
    @Nullable Collection<V> values;
790
    @Nullable Set<Entry<K, V>> entries;
791

792
    AsMapView(LocalCache<K, CompletableFuture<V>> delegate) {
1✔
793
      this.delegate = delegate;
1✔
794
    }
1✔
795

796
    @Override
797
    public boolean isEmpty() {
798
      return delegate.isEmpty();
1✔
799
    }
800

801
    @Override
802
    public int size() {
803
      return delegate.size();
1✔
804
    }
805

806
    @Override
807
    public void clear() {
808
      delegate.clear();
1✔
809
    }
1✔
810

811
    @Override
812
    public boolean containsKey(Object key) {
813
      return Async.isReady(delegate.getIfPresentQuietly(key));
1✔
814
    }
815

816
    @Override
817
    public boolean containsValue(Object value) {
818
      requireNonNull(value);
1✔
819

820
      for (CompletableFuture<V> valueFuture : delegate.values()) {
1✔
821
        if (value.equals(Async.getIfReady(valueFuture))) {
1✔
822
          return true;
1✔
823
        }
824
      }
1✔
825
      return false;
1✔
826
    }
827

828
    @Override
829
    public @Nullable V get(Object key) {
830
      return Async.getIfReady(delegate.get(key));
1✔
831
    }
832

833
    @Override
834
    @SuppressWarnings("ResultOfMethodCallIgnored")
835
    public @Nullable V putIfAbsent(K key, V value) {
836
      requireNonNull(value);
1✔
837

838
      // Keep in sync with BoundedVarExpiration.putIfAbsentAsync(key, value, duration, unit)
839
      @Var CompletableFuture<V> priorFuture = null;
1✔
840
      for (;;) {
841
        priorFuture = (priorFuture == null)
1✔
842
            ? delegate.get(key)
1✔
843
            : delegate.getIfPresentQuietly(key);
1✔
844
        if (priorFuture != null) {
1✔
845
          if (!priorFuture.isDone()) {
1✔
846
            Async.getWhenSuccessful(priorFuture);
1✔
847
            continue;
1✔
848
          }
849

850
          V prior = Async.getWhenSuccessful(priorFuture);
1✔
851
          if (prior != null) {
1✔
852
            return prior;
1✔
853
          }
854
        }
855

856
        boolean[] added = { false };
1✔
857
        var hints = new LocalCache.RemapHints();
1✔
858
        CompletableFuture<V> computed = delegate.compute(
1✔
859
            key, (K k, @Nullable CompletableFuture<V> valueFuture) -> {
860
              added[0] = (valueFuture == null)
1✔
861
                  || (valueFuture.isDone() && (Async.getIfReady(valueFuture) == null));
1✔
862
              if (added[0]) {
1✔
863
                return CompletableFuture.completedFuture(value);
1✔
864
              }
865
              hints.preserveTimestamps = true;
1✔
866
              hints.preserveRefresh = true;
1✔
867
              return valueFuture;
1✔
868
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
869

870
        if (added[0]) {
1✔
871
          return null;
1✔
872
        } else {
873
          V prior = Async.getWhenSuccessful(computed);
1✔
874
          if (prior != null) {
1✔
875
            return prior;
1✔
876
          }
877
        }
878
      }
1✔
879
    }
880

881
    @Override
882
    @SuppressWarnings("ResultOfMethodCallIgnored")
883
    public void putAll(Map<? extends K, ? extends V> map) {
884
      map.forEach(this::put);
1✔
885
    }
1✔
886

887
    @Override
888
    public @Nullable V put(K key, V value) {
889
      requireNonNull(value);
1✔
890
      CompletableFuture<V> oldValueFuture =
1✔
891
          delegate.put(key, CompletableFuture.completedFuture(value));
1✔
892
      return Async.getWhenSuccessful(oldValueFuture);
1✔
893
    }
894

895
    @Override
896
    public @Nullable V remove(Object key) {
897
      CompletableFuture<V> oldValueFuture = delegate.remove(key);
1✔
898
      return Async.getWhenSuccessful(oldValueFuture);
1✔
899
    }
900

901
    @Override
902
    @SuppressWarnings("ResultOfMethodCallIgnored")
903
    public boolean remove(Object key, @Nullable Object value) {
904
      requireNonNull(key);
1✔
905
      if (value == null) {
1✔
906
        return false;
1✔
907
      }
908

909
      @SuppressWarnings("unchecked")
910
      var castedKey = (K) key;
1✔
911
      boolean[] done = { false };
1✔
912
      boolean[] removed = { false };
1✔
913
      @Var CompletableFuture<V> future = null;
1✔
914
      for (;;) {
915
        future = (future == null)
1✔
916
            ? delegate.get(castedKey)
1✔
917
            : delegate.getIfPresentQuietly(castedKey);
1✔
918
        if (!Async.isReady(future)) {
1✔
919
          return false;
1✔
920
        }
921

922
        Async.getWhenSuccessful(future);
1✔
923
        var hints = new LocalCache.RemapHints();
1✔
924
        delegate.compute(castedKey, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1✔
925
          if (oldValueFuture == null) {
1✔
926
            done[0] = true;
1✔
927
            return null;
1✔
928
          } else if (!oldValueFuture.isDone()) {
1✔
929
            hints.preserveTimestamps = true;
1✔
930
            hints.preserveRefresh = true;
1✔
931
            return oldValueFuture;
1✔
932
          }
933

934
          done[0] = true;
1✔
935
          V oldValue = Async.getIfReady(oldValueFuture);
1✔
936
          removed[0] = Objects.equals(value, oldValue);
1✔
937
          if (!removed[0] && (oldValue != null)) {
1✔
938
            hints.preserveTimestamps = true;
1✔
939
            hints.preserveRefresh = true;
1✔
940
          }
941
          return (oldValue == null) || removed[0] ? null : oldValueFuture;
1✔
942
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ true, hints);
1✔
943

944
        if (done[0]) {
1✔
945
          return removed[0];
1✔
946
        }
947
      }
1✔
948
    }
949

950
    @Override
951
    @SuppressWarnings("ResultOfMethodCallIgnored")
952
    public @Nullable V replace(K key, V value) {
953
      requireNonNull(value);
1✔
954

955
      @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
956
      @Nullable V[] oldValue = (V[]) new Object[1];
1✔
957
      boolean[] done = { false };
1✔
958
      for (;;) {
959
        CompletableFuture<V> future = delegate.getIfPresentQuietly(key);
1✔
960
        if ((future == null) || future.isCompletedExceptionally()) {
1✔
961
          return null;
1✔
962
        }
963

964
        Async.getWhenSuccessful(future);
1✔
965
        var hints = new LocalCache.RemapHints();
1✔
966
        delegate.compute(key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1✔
967
          if (oldValueFuture == null) {
1✔
968
            done[0] = true;
1✔
969
            return null;
1✔
970
          } else if (!oldValueFuture.isDone()) {
1✔
971
            hints.preserveTimestamps = true;
1✔
972
            hints.preserveRefresh = true;
1✔
973
            return oldValueFuture;
1✔
974
          }
975

976
          done[0] = true;
1✔
977
          oldValue[0] = Async.getIfReady(oldValueFuture);
1✔
978
          return (oldValue[0] == null) ? null : CompletableFuture.completedFuture(value);
1✔
979
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
980

981
        if (done[0]) {
1✔
982
          return oldValue[0];
1✔
983
        }
984
      }
1✔
985
    }
986

987
    @Override
988
    @SuppressWarnings("ResultOfMethodCallIgnored")
989
    public boolean replace(K key, V oldValue, V newValue) {
990
      requireNonNull(oldValue);
1✔
991
      requireNonNull(newValue);
1✔
992

993
      boolean[] done = { false };
1✔
994
      boolean[] replaced = { false };
1✔
995
      for (;;) {
996
        CompletableFuture<V> future = delegate.getIfPresentQuietly(key);
1✔
997
        if ((future == null) || future.isCompletedExceptionally()) {
1✔
998
          return false;
1✔
999
        }
1000

1001
        Async.getWhenSuccessful(future);
1✔
1002
        var hints = new LocalCache.RemapHints();
1✔
1003
        delegate.compute(key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1✔
1004
          if (oldValueFuture == null) {
1✔
1005
            done[0] = true;
1✔
1006
            return null;
1✔
1007
          } else if (!oldValueFuture.isDone()) {
1✔
1008
            hints.preserveTimestamps = true;
1✔
1009
            hints.preserveRefresh = true;
1✔
1010
            return oldValueFuture;
1✔
1011
          }
1012

1013
          done[0] = true;
1✔
1014
          replaced[0] = Objects.equals(oldValue, Async.getIfReady(oldValueFuture));
1✔
1015
          if (!replaced[0]) {
1✔
1016
            hints.preserveTimestamps = true;
1✔
1017
            hints.preserveRefresh = true;
1✔
1018
          }
1019
          return replaced[0] ? CompletableFuture.completedFuture(newValue) : oldValueFuture;
1✔
1020
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
1021

1022
        if (done[0]) {
1✔
1023
          return replaced[0];
1✔
1024
        }
1025
      }
1✔
1026
    }
1027

1028
    @Override
1029
    @SuppressWarnings("ResultOfMethodCallIgnored")
1030
    public @Nullable V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
1031
      requireNonNull(mappingFunction);
1✔
1032

1033
      @Var CompletableFuture<V> priorFuture = null;
1✔
1034
      for (;;) {
1035
        priorFuture = (priorFuture == null)
1✔
1036
            ? delegate.get(key)
1✔
1037
            : delegate.getIfPresentQuietly(key);
1✔
1038
        if (priorFuture != null) {
1✔
1039
          if (!priorFuture.isDone()) {
1✔
1040
            Async.getWhenSuccessful(priorFuture);
1✔
1041
            continue;
1✔
1042
          }
1043

1044
          V prior = Async.getWhenSuccessful(priorFuture);
1✔
1045
          if (prior != null) {
1✔
1046
            delegate.statsCounter().recordHits(1);
1✔
1047
            return prior;
1✔
1048
          }
1049
        }
1050

1051
        @SuppressWarnings({"rawtypes", "unchecked"})
1052
        CompletableFuture<V>[] future = new CompletableFuture[1];
1✔
1053
        var hints = new LocalCache.RemapHints();
1✔
1054
        CompletableFuture<V> computed = delegate.compute(
1✔
1055
            key, (K k, @Nullable CompletableFuture<V> valueFuture) -> {
1056
              if ((valueFuture != null)
1✔
1057
                  && (!valueFuture.isDone() || (Async.getIfReady(valueFuture) != null))) {
1✔
1058
                hints.preserveTimestamps = true;
1✔
1059
                hints.preserveRefresh = true;
1✔
1060
                return valueFuture;
1✔
1061
              }
1062

1063
              V newValue = delegate.statsAware(mappingFunction, /* recordLoad= */ true).apply(key);
1✔
1064
              if (newValue == null) {
1✔
1065
                return null;
1✔
1066
              }
1067
              future[0] = CompletableFuture.completedFuture(newValue);
1✔
1068
              return future[0];
1✔
1069
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
1070

1071
        V result = Async.getWhenSuccessful(computed);
1✔
1072
        if ((computed == future[0]) || (result != null)) {
1✔
1073
          return result;
1✔
1074
        }
1075
      }
1✔
1076
    }
1077

1078
    @Override
1079
    @SuppressWarnings("ResultOfMethodCallIgnored")
1080
    public @Nullable V computeIfPresent(K key,
1081
        BiFunction<? super K, ? super V, ? extends @Nullable V> remappingFunction) {
1082
      requireNonNull(remappingFunction);
1✔
1083

1084
      @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
1085
      @Nullable V[] newValue = (@Nullable V[]) new Object[1];
1✔
1086
      for (;;) {
1087
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
1✔
1088

1089
        var hints = new LocalCache.RemapHints();
1✔
1090
        CompletableFuture<V> valueFuture = delegate.compute(
1✔
1091
            key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1092
              if (oldValueFuture == null) {
1✔
1093
                return null;
1✔
1094
              } else if (!oldValueFuture.isDone()) {
1✔
1095
                hints.preserveTimestamps = true;
1✔
1096
                hints.preserveRefresh = true;
1✔
1097
                return oldValueFuture;
1✔
1098
              }
1099

1100
              V oldValue = Async.getIfReady(oldValueFuture);
1✔
1101
              if (oldValue == null) {
1✔
1102
                return null;
1✔
1103
              }
1104

1105
              BiFunction<? super K, ? super V, ? extends @Nullable V> function =
1✔
1106
                  delegate.statsAware(remappingFunction);
1✔
1107
              newValue[0] = function.apply(key, oldValue);
1✔
1108
              return (newValue[0] == null) ? null : CompletableFuture.completedFuture(newValue[0]);
1✔
1109
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
1110

1111
        if (newValue[0] != null) {
1✔
1112
          return newValue[0];
1✔
1113
        } else if (valueFuture == null) {
1✔
1114
          return null;
1✔
1115
        }
1116
      }
1✔
1117
    }
1118

1119
    @Override
1120
    @SuppressWarnings("ResultOfMethodCallIgnored")
1121
    public @Nullable V compute(K key,
1122
        BiFunction<? super K, ? super @Nullable V, ? extends @Nullable V> remappingFunction) {
1123
      // Keep in sync with BoundedVarExpiration.computeAsync(key, remappingFunction, expiry)
1124
      requireNonNull(remappingFunction);
1✔
1125

1126
      @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
1127
      @Nullable V[] newValue = (@Nullable V[]) new Object[1];
1✔
1128
      for (;;) {
1129
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
1✔
1130

1131
        var hints = new LocalCache.RemapHints();
1✔
1132
        CompletableFuture<V> valueFuture = delegate.compute(
1✔
1133
            key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1134
              if ((oldValueFuture != null) && !oldValueFuture.isDone()) {
1✔
1135
                hints.preserveTimestamps = true;
1✔
1136
                hints.preserveRefresh = true;
1✔
1137
                return oldValueFuture;
1✔
1138
              }
1139

1140
              V oldValue = Async.getIfReady(oldValueFuture);
1✔
1141
              BiFunction<? super K, ? super V, ? extends @Nullable V> function =
1✔
1142
                  delegate.statsAware(remappingFunction,
1✔
1143
                      /* recordLoad= */ true, /* recordLoadFailure= */ true);
1144
              newValue[0] = function.apply(key, oldValue);
1✔
1145
              return (newValue[0] == null) ? null : CompletableFuture.completedFuture(newValue[0]);
1✔
1146
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
1147

1148
        if (newValue[0] != null) {
1✔
1149
          return newValue[0];
1✔
1150
        } else if (valueFuture == null) {
1✔
1151
          return null;
1✔
1152
        }
1153
      }
1✔
1154
    }
1155

1156
    @Override
1157
    @SuppressWarnings("ResultOfMethodCallIgnored")
1158
    public @Nullable V merge(K key, V value,
1159
        BiFunction<? super V, ? super V, ? extends @Nullable V> remappingFunction) {
1160
      requireNonNull(value);
1✔
1161
      requireNonNull(remappingFunction);
1✔
1162

1163
      CompletableFuture<V> newValueFuture = CompletableFuture.completedFuture(value);
1✔
1164
      boolean[] merged = { false };
1✔
1165
      for (;;) {
1166
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
1✔
1167

1168
        var hints = new LocalCache.RemapHints();
1✔
1169
        CompletableFuture<V> mergedValueFuture = delegate.compute(
1✔
1170
            key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1171
              if (oldValueFuture == null) {
1✔
1172
                merged[0] = true;
1✔
1173
                return newValueFuture;
1✔
1174
              } else if (!oldValueFuture.isDone()) {
1✔
1175
                hints.preserveTimestamps = true;
1✔
1176
                hints.preserveRefresh = true;
1✔
1177
                return oldValueFuture;
1✔
1178
              }
1179

1180
              merged[0] = true;
1✔
1181
              V oldValue = Async.getIfReady(oldValueFuture);
1✔
1182
              if (oldValue == null) {
1✔
1183
                return newValueFuture;
1✔
1184
              }
1185
              V mergedValue = delegate.statsAware(remappingFunction).apply(oldValue, value);
1✔
1186
              if (mergedValue == null) {
1✔
1187
                return null;
1✔
1188
              } else if (mergedValue == oldValue) {
1✔
1189
                hints.preserveTimestamps = true;
1✔
1190
                hints.preserveRefresh = true;
1✔
1191
                return oldValueFuture;
1✔
1192
              } else if (mergedValue == value) {
1✔
1193
                return newValueFuture;
1✔
1194
              }
1195
              return CompletableFuture.completedFuture(mergedValue);
1✔
1196
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
1197

1198
        if (merged[0]) {
1✔
1199
          return Async.getWhenSuccessful(mergedValueFuture);
1✔
1200
        }
1201
      }
1✔
1202
    }
1203

1204
    @Override
1205
    public Set<K> keySet() {
1206
      return (keys == null) ? (keys = new KeySet()) : keys;
1✔
1207
    }
1208

1209
    @Override
1210
    public Collection<V> values() {
1211
      return (values == null) ? (values = new Values()) : values;
1✔
1212
    }
1213

1214
    @Override
1215
    public Set<Entry<K, V>> entrySet() {
1216
      return (entries == null) ? (entries = new EntrySet()) : entries;
1✔
1217
    }
1218

1219
    /** See {@link BoundedLocalCache#equals(Object)} for semantics. */
1220
    @Override
1221
    public boolean equals(@Nullable Object o) {
1222
      if (o == this) {
1✔
1223
        return true;
1✔
1224
      } else if (!(o instanceof Map)) {
1✔
1225
        return false;
1✔
1226
      }
1227

1228
      var map = (Map<?, ?>) o;
1✔
1229
      int expectedSize = size();
1✔
1230
      if (map.size() != expectedSize) {
1✔
1231
        return false;
1✔
1232
      }
1233

1234
      @Var int count = 0;
1✔
1235
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
1✔
1236
        var entry = iterator.next();
1✔
1237
        var value = map.get(entry.getKey());
1✔
1238
        if ((value == null) || ((value != entry.getValue()) && !value.equals(entry.getValue()))) {
1✔
1239
          return false;
1✔
1240
        }
1241
        count++;
1✔
1242
      }
1✔
1243
      return (count == expectedSize);
1✔
1244
    }
1245

1246
    @Override
1247
    public int hashCode() {
1248
      @Var int hash = 0;
1✔
1249
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
1✔
1250
        var entry = iterator.next();
1✔
1251
        hash += entry.hashCode();
1✔
1252
      }
1✔
1253
      return hash;
1✔
1254
    }
1255

1256
    @Override
1257
    public String toString() {
1258
      var result = new StringBuilder(50).append('{');
1✔
1259
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
1✔
1260
        var entry = iterator.next();
1✔
1261
        result.append((entry.getKey() == this) ? "(this Map)" : entry.getKey())
1✔
1262
            .append('=')
1✔
1263
            .append((entry.getValue() == this) ? "(this Map)" : entry.getValue());
1✔
1264

1265
        if (iterator.hasNext()) {
1✔
1266
          result.append(", ");
1✔
1267
        }
1268
      }
1✔
1269
      return result.append('}').toString();
1✔
1270
    }
1271

1272
    private final class KeySet extends AbstractSet<K> {
1✔
1273

1274
      @Override
1275
      public boolean isEmpty() {
1276
        return AsMapView.this.isEmpty();
1✔
1277
      }
1278

1279
      @Override
1280
      public int size() {
1281
        return AsMapView.this.size();
1✔
1282
      }
1283

1284
      @Override
1285
      public void clear() {
1286
        AsMapView.this.clear();
1✔
1287
      }
1✔
1288

1289
      @Override
1290
      public boolean contains(Object o) {
1291
        return AsMapView.this.containsKey(o);
1✔
1292
      }
1293

1294
      @Override
1295
      public boolean removeAll(Collection<?> collection) {
1296
        return delegate.keySet().removeAll(collection);
1✔
1297
      }
1298

1299
      @Override
1300
      @SuppressWarnings("RedundantCollectionOperation")
1301
      public boolean remove(Object o) {
1302
        return delegate.keySet().remove(o);
1✔
1303
      }
1304

1305
      @Override
1306
      public boolean removeIf(Predicate<? super K> filter) {
1307
        return delegate.keySet().removeIf(filter);
1✔
1308
      }
1309

1310
      @Override
1311
      public boolean retainAll(Collection<?> collection) {
1312
        return delegate.keySet().retainAll(collection);
1✔
1313
      }
1314

1315
      @Override
1316
      public Iterator<K> iterator() {
1317
        return new KeyIterator<>(new EntryIterator());
1✔
1318
      }
1319

1320
      @Override
1321
      public Spliterator<K> spliterator() {
1322
        return new KeySpliterator<>(new EntrySpliterator());
1✔
1323
      }
1324
    }
1325

1326
    private static final class KeyIterator<K, V> implements Iterator<K> {
1327
      private final Iterator<Entry<K, V>> iterator;
1328

1329
      KeyIterator(Iterator<Entry<K, V>> iterator) {
1✔
1330
        this.iterator = requireNonNull(iterator);
1✔
1331
      }
1✔
1332

1333
      @Override
1334
      public boolean hasNext() {
1335
        return iterator.hasNext();
1✔
1336
      }
1337

1338
      @Override
1339
      public K next() {
1340
        return iterator.next().getKey();
1✔
1341
      }
1342

1343
      @Override
1344
      public void remove() {
1345
        iterator.remove();
1✔
1346
      }
1✔
1347
    }
1348

1349
    private static final class KeySpliterator<K, V> implements Spliterator<K> {
1350
      private final Spliterator<Entry<K, V>> spliterator;
1351

1352
      KeySpliterator(Spliterator<Entry<K, V>> iterator) {
1✔
1353
        this.spliterator = requireNonNull(iterator);
1✔
1354
      }
1✔
1355

1356
      @Override
1357
      public boolean tryAdvance(Consumer<? super K> action) {
1358
        requireNonNull(action);
1✔
1359
        return spliterator.tryAdvance(entry -> {
1✔
1360
          action.accept(entry.getKey());
1✔
1361
        });
1✔
1362
      }
1363

1364
      @Override
1365
      public @Nullable Spliterator<K> trySplit() {
1366
        Spliterator<Entry<K, V>> split = spliterator.trySplit();
1✔
1367
        return (split == null) ? null : new KeySpliterator<>(split);
1✔
1368
      }
1369

1370
      @Override
1371
      public long estimateSize() {
1372
        return spliterator.estimateSize();
1✔
1373
      }
1374

1375
      @Override
1376
      public int characteristics() {
1377
        return DISTINCT | NONNULL | CONCURRENT;
1✔
1378
      }
1379
    }
1380

1381
    private final class Values extends AbstractCollection<V> {
1✔
1382

1383
      @Override
1384
      public boolean isEmpty() {
1385
        return AsMapView.this.isEmpty();
1✔
1386
      }
1387

1388
      @Override
1389
      public int size() {
1390
        return AsMapView.this.size();
1✔
1391
      }
1392

1393
      @Override
1394
      public void clear() {
1395
        AsMapView.this.clear();
1✔
1396
      }
1✔
1397

1398
      @Override
1399
      public boolean contains(Object o) {
1400
        return AsMapView.this.containsValue(o);
1✔
1401
      }
1402

1403
      @Override
1404
      public boolean removeAll(Collection<?> collection) {
1405
        requireNonNull(collection);
1✔
1406
        @Var boolean modified = false;
1✔
1407
        for (var entry : delegate.entrySet()) {
1✔
1408
          V value = Async.getIfReady(entry.getValue());
1✔
1409
          if ((value != null) && collection.contains(value)
1✔
1410
              && AsMapView.this.remove(entry.getKey(), value)) {
1✔
1411
            modified = true;
1✔
1412
          }
1413
        }
1✔
1414
        return modified;
1✔
1415
      }
1416

1417
      @Override
1418
      public boolean remove(@Nullable Object o) {
1419
        if (o == null) {
1✔
1420
          return false;
1✔
1421
        }
1422
        for (var entry : delegate.entrySet()) {
1✔
1423
          V value = Async.getIfReady(entry.getValue());
1✔
1424
          if ((value != null) && value.equals(o) && AsMapView.this.remove(entry.getKey(), value)) {
1✔
1425
            return true;
1✔
1426
          }
1427
        }
1✔
1428
        return false;
1✔
1429
      }
1430

1431
      @Override
1432
      public boolean removeIf(Predicate<? super V> filter) {
1433
        requireNonNull(filter);
1✔
1434
        return delegate.values().removeIf(future -> {
1✔
1435
          V value = Async.getIfReady(future);
1✔
1436
          return (value != null) && filter.test(value);
1✔
1437
        });
1438
      }
1439

1440
      @Override
1441
      public boolean retainAll(Collection<?> collection) {
1442
        requireNonNull(collection);
1✔
1443
        @Var boolean modified = false;
1✔
1444
        for (var entry : delegate.entrySet()) {
1✔
1445
          V value = Async.getIfReady(entry.getValue());
1✔
1446
          if ((value != null) && !collection.contains(value)
1✔
1447
              && AsMapView.this.remove(entry.getKey(), value)) {
1✔
1448
            modified = true;
1✔
1449
          }
1450
        }
1✔
1451
        return modified;
1✔
1452
      }
1453

1454
      @Override
1455
      public void forEach(Consumer<? super V> action) {
1456
        requireNonNull(action);
1✔
1457
        delegate.values().forEach(future -> {
1✔
1458
          V value = Async.getIfReady(future);
1✔
1459
          if (value != null) {
1✔
1460
            action.accept(value);
1✔
1461
          }
1462
        });
1✔
1463
      }
1✔
1464

1465
      @Override
1466
      public Iterator<V> iterator() {
1467
        return new ValueIterator<>(new EntryIterator());
1✔
1468
      }
1469

1470
      @Override
1471
      public Spliterator<V> spliterator() {
1472
        return new ValueSpliterator<>(new EntrySpliterator());
1✔
1473
      }
1474
    }
1475

1476
    private static final class ValueIterator<K, V> implements Iterator<V> {
1477
      private final Iterator<Entry<K, V>> iterator;
1478

1479
      ValueIterator(Iterator<Entry<K, V>> iterator) {
1✔
1480
        this.iterator = requireNonNull(iterator);
1✔
1481
      }
1✔
1482

1483
      @Override
1484
      public boolean hasNext() {
1485
        return iterator.hasNext();
1✔
1486
      }
1487

1488
      @Override
1489
      public V next() {
1490
        return iterator.next().getValue();
1✔
1491
      }
1492

1493
      @Override
1494
      public void remove() {
1495
        iterator.remove();
1✔
1496
      }
1✔
1497
    }
1498

1499
    private static final class ValueSpliterator<K, V> implements Spliterator<V> {
1500
      private final Spliterator<Entry<K, V>> spliterator;
1501

1502
      ValueSpliterator(Spliterator<Entry<K, V>> iterator) {
1✔
1503
        this.spliterator = requireNonNull(iterator);
1✔
1504
      }
1✔
1505

1506
      @Override
1507
      public boolean tryAdvance(Consumer<? super V> action) {
1508
        requireNonNull(action);
1✔
1509
        return spliterator.tryAdvance(entry -> {
1✔
1510
          action.accept(entry.getValue());
1✔
1511
        });
1✔
1512
      }
1513

1514
      @Override
1515
      public @Nullable Spliterator<V> trySplit() {
1516
        Spliterator<Entry<K, V>> split = spliterator.trySplit();
1✔
1517
        return (split == null) ? null : new ValueSpliterator<>(split);
1✔
1518
      }
1519

1520
      @Override
1521
      public long estimateSize() {
1522
        return spliterator.estimateSize();
1✔
1523
      }
1524

1525
      @Override
1526
      public int characteristics() {
1527
        return NONNULL | CONCURRENT;
1✔
1528
      }
1529
    }
1530

1531
    private final class EntrySet extends AbstractSet<Entry<K, V>> {
1✔
1532

1533
      @Override
1534
      public boolean isEmpty() {
1535
        return AsMapView.this.isEmpty();
1✔
1536
      }
1537

1538
      @Override
1539
      public int size() {
1540
        return AsMapView.this.size();
1✔
1541
      }
1542

1543
      @Override
1544
      public void clear() {
1545
        AsMapView.this.clear();
1✔
1546
      }
1✔
1547

1548
      @Override
1549
      public boolean contains(Object o) {
1550
        if (!(o instanceof Entry<?, ?>)) {
1✔
1551
          return false;
1✔
1552
        }
1553
        var entry = (Entry<?, ?>) o;
1✔
1554
        var key = entry.getKey();
1✔
1555
        var value = entry.getValue();
1✔
1556
        if ((key == null) || (value == null)) {
1✔
1557
          return false;
1✔
1558
        }
1559
        V cachedValue = AsMapView.this.get(key);
1✔
1560
        return (cachedValue != null) && cachedValue.equals(value);
1✔
1561
      }
1562

1563
      @Override
1564
      public boolean removeAll(Collection<?> collection) {
1565
        requireNonNull(collection);
1✔
1566
        @Var boolean modified = false;
1✔
1567
        if (delegate.collectKeys()
1✔
1568
            || ((collection instanceof Set<?>) && (collection.size() > size()))) {
1✔
1569
          for (var entry : this) {
1✔
1570
            if (collection.contains(entry)) {
1✔
1571
              modified |= remove(entry);
1✔
1572
            }
1573
          }
1✔
1574
        } else {
1575
          for (var o : collection) {
1✔
1576
            modified |= remove(o);
1✔
1577
          }
1✔
1578
        }
1579
        return modified;
1✔
1580
      }
1581

1582
      @Override
1583
      public boolean remove(Object obj) {
1584
        if (!(obj instanceof Entry<?, ?>)) {
1✔
1585
          return false;
1✔
1586
        }
1587
        var entry = (Entry<?, ?>) obj;
1✔
1588
        var key = entry.getKey();
1✔
1589
        return (key != null) && AsMapView.this.remove(key, entry.getValue());
1✔
1590
      }
1591

1592
      @Override
1593
      public boolean removeIf(Predicate<? super Entry<K, V>> filter) {
1594
        requireNonNull(filter);
1✔
1595
        @Var boolean modified = false;
1✔
1596
        for (Entry<K, V> entry : this) {
1✔
1597
          if (filter.test(entry)) {
1✔
1598
            modified |= AsMapView.this.remove(entry.getKey(), entry.getValue());
1✔
1599
          }
1600
        }
1✔
1601
        return modified;
1✔
1602
      }
1603

1604
      @Override
1605
      public boolean retainAll(Collection<?> collection) {
1606
        requireNonNull(collection);
1✔
1607
        @Var boolean modified = false;
1✔
1608
        for (var entry : this) {
1✔
1609
          if (!collection.contains(entry) && remove(entry)) {
1✔
1610
            modified = true;
1✔
1611
          }
1612
        }
1✔
1613
        return modified;
1✔
1614
      }
1615

1616
      @Override
1617
      public Iterator<Entry<K, V>> iterator() {
1618
        return new EntryIterator();
1✔
1619
      }
1620

1621
      @Override
1622
      public Spliterator<Entry<K, V>> spliterator() {
1623
        return new EntrySpliterator();
1✔
1624
      }
1625
    }
1626

1627
    private final class EntryIterator implements Iterator<Entry<K, V>> {
1628
      final Iterator<Entry<K, CompletableFuture<V>>> iterator;
1629
      @Nullable Entry<K, V> cursor;
1630
      @Nullable K removalKey;
1631

1632
      EntryIterator() {
1✔
1633
        this.iterator = delegate.entrySet().iterator();
1✔
1634
      }
1✔
1635

1636
      @Override
1637
      public boolean hasNext() {
1638
        while ((cursor == null) && iterator.hasNext()) {
1✔
1639
          Entry<K, CompletableFuture<V>> entry = iterator.next();
1✔
1640
          V value = Async.getIfReady(entry.getValue());
1✔
1641
          if (value != null) {
1✔
1642
            cursor = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
1✔
1643
          }
1644
        }
1✔
1645
        return (cursor != null);
1✔
1646
      }
1647

1648
      @Override
1649
      public Entry<K, V> next() {
1650
        if (!hasNext()) {
1✔
1651
          throw new NoSuchElementException();
1✔
1652
        }
1653
        K key = requireNonNull(cursor).getKey();
1✔
1654
        Entry<K, V> entry = cursor;
1✔
1655
        removalKey = key;
1✔
1656
        cursor = null;
1✔
1657
        return entry;
1✔
1658
      }
1659

1660
      @Override
1661
      public void remove() {
1662
        requireState(removalKey != null);
1✔
1663
        delegate.remove(removalKey);
1✔
1664
        removalKey = null;
1✔
1665
      }
1✔
1666
    }
1667

1668
    private final class EntrySpliterator implements Spliterator<Entry<K, V>> {
1669
      final Spliterator<Entry<K, CompletableFuture<V>>> spliterator;
1670

1671
      EntrySpliterator() {
1672
        this(delegate.entrySet().spliterator());
1✔
1673
      }
1✔
1674

1675
      EntrySpliterator(Spliterator<Entry<K, CompletableFuture<V>>> spliterator) {
1✔
1676
        this.spliterator = requireNonNull(spliterator);
1✔
1677
      }
1✔
1678

1679
      @Override
1680
      public void forEachRemaining(Consumer<? super Entry<K, V>> action) {
1681
        requireNonNull(action);
1✔
1682
        spliterator.forEachRemaining(entry -> {
1✔
1683
          V value = Async.getIfReady(entry.getValue());
1✔
1684
          if (value != null) {
1✔
1685
            var e = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
1✔
1686
            action.accept(e);
1✔
1687
          }
1688
        });
1✔
1689
      }
1✔
1690

1691
      @Override
1692
      public boolean tryAdvance(Consumer<? super Entry<K, V>> action) {
1693
        requireNonNull(action);
1✔
1694
        boolean[] advanced = { false };
1✔
1695
        Consumer<? super Entry<K, CompletableFuture<V>>> consumer = entry -> {
1✔
1696
          V value = Async.getIfReady(entry.getValue());
1✔
1697
          if (value != null) {
1✔
1698
            var e = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
1✔
1699
            action.accept(e);
1✔
1700
            advanced[0] = true;
1✔
1701
          }
1702
        };
1✔
1703
        while (spliterator.tryAdvance(consumer)) {
1✔
1704
          if (advanced[0]) {
1✔
1705
            return true;
1✔
1706
          }
1707
        }
1708
        return false;
1✔
1709
      }
1710

1711
      @Override
1712
      public @Nullable Spliterator<Entry<K, V>> trySplit() {
1713
        Spliterator<Entry<K, CompletableFuture<V>>> split = spliterator.trySplit();
1✔
1714
        return (split == null) ? null : new EntrySpliterator(split);
1✔
1715
      }
1716

1717
      @Override
1718
      public long estimateSize() {
1719
        return spliterator.estimateSize();
1✔
1720
      }
1721

1722
      @Override
1723
      public int characteristics() {
1724
        return DISTINCT | CONCURRENT | NONNULL;
1✔
1725
      }
1726
    }
1727
  }
1728

1729
  @SuppressWarnings("serial")
1730
  final class SyncViewProxy<K, V> implements Serializable {
1731
    private static final long serialVersionUID = 1;
1732

1733
    final AsyncCache<K, V> asyncCache;
1734

1735
    SyncViewProxy(AsyncCache<K, V> asyncCache) {
1✔
1736
      this.asyncCache = requireNonNull(asyncCache);
1✔
1737
    }
1✔
1738

1739
    @SuppressWarnings("unused")
1740
    private void readObject(ObjectInputStream stream)
1741
        throws IOException, ClassNotFoundException {
1742
      stream.defaultReadObject();
1✔
1743
      if (asyncCache == null) {
1✔
1744
        throw new InvalidObjectException("asyncCache required");
1✔
1745
      }
1746
    }
1✔
1747

1748
    Object readResolve() {
1749
      return asyncCache.synchronous();
1✔
1750
    }
1751
  }
1752
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc