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

ben-manes / caffeine / #5707

02 Aug 2026 12:45AM UTC coverage: 0.106% (-99.9%) from 100.0%
#5707

push

github

ben-manes
fix wikibench trace reader after overly strict audit fixes

0 of 4235 branches covered (0.0%)

9 of 8528 relevant lines covered (0.11%)

0.0 hits per line

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

0.0
/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());
×
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);
×
77
  }
78

79
  @Override
80
  default CompletableFuture<V> get(K key, Function<? super K, ? extends V> mappingFunction) {
81
    requireNonNull(mappingFunction);
×
82
    return get(key, (k, executor) -> CompletableFuture.supplyAsync(
×
83
        () -> mappingFunction.apply(k), executor));
×
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);
×
90
  }
91

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

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

117
  @Override
118
  default CompletableFuture<Map<K, V>> getAll(Iterable<? extends K> keys,
119
      Function<? super Set<? extends K>, ? extends Map<? extends K, ? extends V>> mappingFunction) {
120
    requireNonNull(mappingFunction);
×
121
    return getAll(keys, (keysToLoad, executor) ->
×
122
        CompletableFuture.supplyAsync(() -> mappingFunction.apply(keysToLoad), executor));
×
123
  }
124

125
  @Override
126
  @SuppressWarnings("FutureReturnValueIgnored")
127
  default CompletableFuture<Map<K, V>> getAll(Iterable<? extends K> keys,
128
      BiFunction<? super Set<? extends K>, ? super Executor,
129
          ? extends CompletableFuture<? extends Map<? extends K, ? extends V>>> mappingFunction) {
130
    requireNonNull(mappingFunction);
×
131
    requireNonNull(keys);
×
132

133
    int initialCapacity = calculateHashMapCapacity(keys);
×
134
    var futures = new LinkedHashMap<K, @Nullable CompletableFuture<@Nullable V>>(initialCapacity);
×
135
    for (K key : keys) {
×
136
      futures.put(requireNonNull(key), null);
×
137
    }
×
138

139
    var proxies = new LinkedHashMap<K, CompletableFuture<@Nullable V>>(initialCapacity);
×
140
    for (var entry : futures.entrySet()) {
×
141
      K key = entry.getKey();
×
142
      @Var CompletableFuture<V> future = cache().getIfPresent(key, /* recordStats= */ false);
×
143
      if (future == null) {
×
144
        var proxy = new CompletableFuture<V>();
×
145
        future = cache().putIfAbsent(key, proxy);
×
146
        if (future == null) {
×
147
          future = proxy;
×
148
          proxies.put(key, proxy);
×
149
        }
150
      }
151
      entry.setValue(future);
×
152
    }
×
153
    cache().statsCounter().recordMisses(proxies.size());
×
154
    cache().statsCounter().recordHits(futures.size() - proxies.size());
×
155

156
    @SuppressWarnings("NullAway")
157
    Map<K, CompletableFuture<@Nullable V>> results = futures;
×
158
    if (proxies.isEmpty()) {
×
159
      return composeResult(results);
×
160
    }
161

162
    var completer = new AsyncBulkCompleter<>(cache(), proxies);
×
163
    try {
164
      var loader = mappingFunction.apply(
×
165
          Collections.unmodifiableSet(proxies.keySet()), cache().executor());
×
166
      return loader.handle(completer).thenCompose(ignored -> composeResult(results));
×
167
    } catch (Throwable t) {
×
168
      throw completer.error(t);
×
169
    }
170
  }
171

172
  /**
173
   * Returns a future that waits for all of the dependent futures to complete and returns the
174
   * combined mapping if successful. If any future fails then it is automatically removed from
175
   * the cache if still present.
176
   */
177
  static <K, V> CompletableFuture<Map<K, V>> composeResult(
178
      Map<K, CompletableFuture<@Nullable V>> futures) {
179
    if (futures.isEmpty()) {
×
180
      @SuppressWarnings({"ImmutableMapOf", "RedundantUnmodifiable"})
181
      Map<K, V> emptyMap = Collections.unmodifiableMap(Collections.emptyMap());
×
182
      return CompletableFuture.completedFuture(emptyMap);
×
183
    }
184
    @SuppressWarnings("rawtypes")
185
    CompletableFuture<?>[] array = futures.values().toArray(new CompletableFuture[0]);
×
186
    return CompletableFuture.allOf(array).thenApply(ignored -> {
×
187
      var result = new LinkedHashMap<K, V>(calculateHashMapCapacity(futures.size()));
×
188
      futures.forEach((key, future) -> {
×
189
        @Nullable V value = future.getNow(null);
×
190
        if (value != null) {
×
191
          result.put(key, value);
×
192
        }
193
      });
×
194
      return Collections.unmodifiableMap(result);
×
195
    });
196
  }
197

198
  @Override
199
  @SuppressWarnings("FutureReturnValueIgnored")
200
  default void put(K key, CompletableFuture<? extends @Nullable V> valueFuture) {
201
    long startTime = cache().statsTicker().read();
×
202

203
    @SuppressWarnings("unchecked")
204
    var castedFuture = (CompletableFuture<V>) valueFuture;
×
205
    CompletableFuture<V> prior = cache().put(key, castedFuture);
×
206
    if (prior != castedFuture) {
×
207
      handleCompletion(key, valueFuture, startTime);
×
208
    }
209
  }
×
210

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

237
        try {
238
          // update the weight and expiration timestamps
239
          cache().replace(key, castedFuture, castedFuture,
×
240
              /* shouldDiscardRefresh= */ false, /* quietly= */ true);
241
          cache().statsCounter().recordLoadSuccess(loadTime);
×
242
        } catch (Throwable t) {
×
243
          logger.log(Level.WARNING, "Exception thrown during asynchronous load", t);
×
244
          cache().statsCounter().recordLoadFailure(loadTime);
×
245
          cache().remove(key, valueFuture);
×
246
        }
×
247
      }
248
    });
×
249
  }
×
250

251
  /** A function executed asynchronously after a bulk load completes. */
252
  final class AsyncBulkCompleter<K, V> implements BiFunction<
253
      Map<? extends K, ? extends V>, Throwable, Map<? extends K, ? extends V>> {
254
    @SuppressWarnings("ImmutableMemberCollection")
255
    private final Map<K, CompletableFuture<@Nullable V>> proxies;
256
    private final LocalCache<K, CompletableFuture<V>> cache;
257
    private final long startTime;
258

259
    AsyncBulkCompleter(LocalCache<K, CompletableFuture<V>> cache,
260
        Map<K, CompletableFuture<@Nullable V>> proxies) {
×
261
      this.startTime = cache.statsTicker().read();
×
262
      this.proxies = proxies;
×
263
      this.cache = cache;
×
264
    }
×
265

266
    @Override
267
    @CanIgnoreReturnValue
268
    public @Nullable Map<? extends K, ? extends V> apply(
269
        @Nullable Map<? extends K, ? extends V> result, @Nullable Throwable error) {
270
      long loadTime = cache.statsTicker().read() - startTime;
×
271
      var failure = handleResponse(result, error);
×
272

273
      if (failure == null) {
×
274
        if (requireNonNull(result).isEmpty()) {
×
275
          cache.statsCounter().recordLoadFailure(loadTime);
×
276
        } else {
277
          cache.statsCounter().recordLoadSuccess(loadTime);
×
278
        }
279
        return result;
×
280
      }
281

282
      cache.statsCounter().recordLoadFailure(loadTime);
×
283
      if (failure instanceof RuntimeException) {
×
284
        throw (RuntimeException) failure;
×
285
      } else if (failure instanceof Error) {
×
286
        throw (Error) failure;
×
287
      }
288
      throw new CompletionException(failure);
×
289
    }
290

291
    public CompletionException error(Throwable error) {
292
      long loadTime = cache.statsTicker().read() - startTime;
×
293
      var failure = handleResponse(/* result= */ null, error);
×
294
      cache.statsCounter().recordLoadFailure(loadTime);
×
295
      if (failure instanceof RuntimeException) {
×
296
        throw (RuntimeException) failure;
×
297
      } else if (failure instanceof Error) {
×
298
        throw (Error) failure;
×
299
      }
300
      return new CompletionException(failure);
×
301
    }
302

303
    @SuppressWarnings("ImmutableMapCopyOf")
304
    private @Nullable Throwable handleResponse(
305
        @Nullable Map<? extends K, ? extends V> result, @Nullable Throwable error) {
306
      if (result == null) {
×
307
        var failure = (error == null) ? new NullMapCompletionException() : error;
×
308
        failProxies(failure);
×
309
        return failure;
×
310
      }
311

312
      Map<K, V> snapshot;
313
      try {
314
        snapshot = Map.copyOf(result);
×
315
      } catch (Throwable t) {
×
316
        failProxies(t);
×
317
        return t;
×
318
      }
×
319

320
      var failure = fillProxies(snapshot);
×
321
      return addNewEntries(snapshot, failure);
×
322
    }
323

324
    /** Removes and exceptionally completes every requested proxy. */
325
    private void failProxies(Throwable failure) {
326
      for (var entry : proxies.entrySet()) {
×
327
        cache.remove(entry.getKey(), entry.getValue());
×
328
        entry.getValue().obtrudeException(failure);
×
329
      }
×
330
      if (!(failure instanceof CancellationException) && !(failure instanceof TimeoutException)) {
×
331
        logger.log(Level.WARNING, "Exception thrown during asynchronous load", failure);
×
332
      }
333
    }
×
334

335
    /** Populates the proxies with the computed result. */
336
    private @Nullable Throwable fillProxies(Map<? extends K, ? extends V> result) {
337
      @Var Throwable error = null;
×
338
      for (var entry : proxies.entrySet()) {
×
339
        var key = entry.getKey();
×
340
        var value = result.get(key);
×
341
        var future = entry.getValue();
×
342
        future.obtrudeValue(value);
×
343

344
        if (value == null) {
×
345
          cache.remove(key, future);
×
346
        } else {
347
          try {
348
            // update the weight and expiration timestamps; a same-instance finalization is not a
349
            // mutation, so an in-flight refresh is preserved (mirrors handleCompletion)
350
            cache.replace(key, future, future,
×
351
                /* shouldDiscardRefresh= */ false, /* quietly= */ true);
352
          } catch (Throwable t) {
×
353
            logger.log(Level.WARNING, "Exception thrown during asynchronous load", t);
×
354
            cache.remove(key, future);
×
355
            if (error == null) {
×
356
              error = t;
×
357
            } else if (error != t) {
×
358
              error.addSuppressed(t);
×
359
            }
360
          }
×
361
        }
362
      }
×
363
      return error;
×
364
    }
365

366
    /** Adds to the cache any extra entries computed that were not requested. */
367
    private @Nullable Throwable addNewEntries(
368
        Map<? extends K, ? extends V> result, @Nullable Throwable failure) {
369
      @Var Throwable error = failure;
×
370
      for (var entry : result.entrySet()) {
×
371
        var key = entry.getKey();
×
372
        if (!proxies.containsKey(key)) {
×
373
          V value = entry.getValue();
×
374
          try {
375
            cache.put(key, CompletableFuture.completedFuture(value));
×
376
          } catch (Throwable t) {
×
377
            logger.log(Level.WARNING, "Exception thrown during asynchronous load", t);
×
378
            if (error == null) {
×
379
              error = t;
×
380
            } else if (error != t) {
×
381
              error.addSuppressed(t);
×
382
            }
383
          }
×
384
        }
385
      }
×
386
      return error;
×
387
    }
388

389
    static final class NullMapCompletionException extends CompletionException {
×
390
      private static final long serialVersionUID = 1L;
391
    }
392
  }
393

394
  /* --------------- Asynchronous view --------------- */
395
  final class AsyncAsMapView<K, V> implements ConcurrentMap<K, CompletableFuture<V>> {
396
    final LocalAsyncCache<K, V> asyncCache;
397

398
    AsyncAsMapView(LocalAsyncCache<K, V> asyncCache) {
×
399
      this.asyncCache = requireNonNull(asyncCache);
×
400
    }
×
401
    @Override public boolean isEmpty() {
402
      return asyncCache.cache().isEmpty();
×
403
    }
404
    @Override public int size() {
405
      return asyncCache.cache().size();
×
406
    }
407
    @Override public void clear() {
408
      asyncCache.cache().clear();
×
409
    }
×
410
    @Override public boolean containsKey(Object key) {
411
      return asyncCache.cache().containsKey(key);
×
412
    }
413
    @SuppressWarnings("CollectionUndefinedEquality")
414
    @Override public boolean containsValue(Object value) {
415
      return asyncCache.cache().containsValue(value);
×
416
    }
417
    @Override public @Nullable CompletableFuture<V> get(Object key) {
418
      return asyncCache.cache().get(key);
×
419
    }
420
    @Override public @Nullable CompletableFuture<V> putIfAbsent(K key, CompletableFuture<V> value) {
421
      CompletableFuture<V> prior = asyncCache.cache().putIfAbsent(key, value);
×
422
      long startTime = asyncCache.cache().statsTicker().read();
×
423
      if (prior == null) {
×
424
        asyncCache.handleCompletion(key, value, startTime);
×
425
      }
426
      return prior;
×
427
    }
428
    @Override public @Nullable CompletableFuture<V> put(K key, CompletableFuture<V> value) {
429
      CompletableFuture<V> prior = asyncCache.cache().put(key, value);
×
430
      long startTime = asyncCache.cache().statsTicker().read();
×
431
      if (prior != value) {
×
432
        asyncCache.handleCompletion(key, value, startTime);
×
433
      }
434
      return prior;
×
435
    }
436
    @SuppressWarnings({"FutureReturnValueIgnored", "ResultOfMethodCallIgnored"})
437
    @Override public void putAll(Map<? extends K, ? extends CompletableFuture<V>> map) {
438
      map.forEach(this::put);
×
439
    }
×
440
    @Override public @Nullable CompletableFuture<V> replace(K key, CompletableFuture<V> value) {
441
      CompletableFuture<V> prior = asyncCache.cache().replace(key, value);
×
442
      long startTime = asyncCache.cache().statsTicker().read();
×
443
      if ((prior != null) && (prior != value)) {
×
444
        asyncCache.handleCompletion(key, value, startTime);
×
445
      }
446
      return prior;
×
447
    }
448
    @Override
449
    public boolean replace(K key, CompletableFuture<V> oldValue, CompletableFuture<V> newValue) {
450
      boolean replaced = asyncCache.cache().replace(key, oldValue, newValue);
×
451
      long startTime = asyncCache.cache().statsTicker().read();
×
452
      if (replaced && (newValue != oldValue)) {
×
453
        asyncCache.handleCompletion(key, newValue, startTime);
×
454
      }
455
      return replaced;
×
456
    }
457
    @Override public @Nullable CompletableFuture<V> remove(Object key) {
458
      return asyncCache.cache().remove(key);
×
459
    }
460
    @Override public boolean remove(Object key, Object value) {
461
      return asyncCache.cache().remove(key, value);
×
462
    }
463
    @Override public @Nullable CompletableFuture<V> computeIfAbsent(K key,
464
        Function<? super K, ? extends @Nullable CompletableFuture<V>> mappingFunction) {
465
      requireNonNull(mappingFunction);
×
466
      @SuppressWarnings({"rawtypes", "unchecked"})
467
      @Nullable CompletableFuture<V>[] result = new CompletableFuture[1];
×
468
      long startTime = asyncCache.cache().statsTicker().read();
×
469
      Function<K, @Nullable CompletableFuture<V>> function = k -> {
×
470
        result[0] = mappingFunction.apply(k);
×
471
        return result[0];
×
472
      };
473
      @Nullable CompletableFuture<V> future = asyncCache.cache().computeIfAbsent(
×
474
          key, function, /* recordStats= */ true, /* recordLoad= */ false);
475
      if (result[0] != null) {
×
476
        asyncCache.handleCompletion(key, result[0], startTime);
×
477
      }
478
      return future;
×
479
    }
480
    @SuppressWarnings("ResultOfMethodCallIgnored")
481
    @Override public @Nullable CompletableFuture<V> computeIfPresent(K key, BiFunction<? super K,
482
        ? super CompletableFuture<V>, ? extends CompletableFuture<V>> remappingFunction) {
483
      requireNonNull(remappingFunction);
×
484

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

496
      if ((result[0] != null) && (result[0] != prior[0])) {
×
497
        asyncCache.handleCompletion(key, result[0], startTime);
×
498
      }
499
      return result[0];
×
500
    }
501
    @SuppressWarnings("ResultOfMethodCallIgnored")
502
    @Override public @Nullable CompletableFuture<V> compute(K key, BiFunction<? super K,
503
        ? super CompletableFuture<V>, ? extends CompletableFuture<V>> remappingFunction) {
504
      requireNonNull(remappingFunction);
×
505

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

517
      if ((result[0] != null) && (result[0] != prior[0])) {
×
518
        asyncCache.handleCompletion(key, result[0], startTime);
×
519
      }
520
      return result[0];
×
521
    }
522
    @SuppressWarnings("ResultOfMethodCallIgnored")
523
    @Override public @Nullable CompletableFuture<V> merge(K key, CompletableFuture<V> value,
524
        BiFunction<? super CompletableFuture<V>, ? super CompletableFuture<V>,
525
            ? extends CompletableFuture<V>> remappingFunction) {
526
      requireNonNull(value);
×
527
      requireNonNull(remappingFunction);
×
528

529
      @SuppressWarnings({"rawtypes", "unchecked"})
530
      @Nullable  CompletableFuture<V>[] result = new CompletableFuture[1];
×
531
      @SuppressWarnings({"rawtypes", "unchecked"})
532
      @Nullable  CompletableFuture<V>[] prior = new CompletableFuture[1];
×
533
      long startTime = asyncCache.cache().statsTicker().read();
×
534
      asyncCache.cache().compute(key, (K k, @Nullable CompletableFuture<V> oldValue) -> {
×
535
        result[0] = (oldValue == null) ? value : remappingFunction.apply(oldValue, value);
×
536
        prior[0] = oldValue;
×
537
        return result[0];
×
538
      }, asyncCache.cache().expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
×
539

540
      if ((result[0] != null) && (result[0] != prior[0])) {
×
541
        asyncCache.handleCompletion(key, result[0], startTime);
×
542
      }
543
      return result[0];
×
544
    }
545
    @Override public void forEach(BiConsumer<? super K, ? super CompletableFuture<V>> action) {
546
      asyncCache.cache().forEach(action);
×
547
    }
×
548
    @Override public Set<K> keySet() {
549
      return asyncCache.cache().keySet();
×
550
    }
551
    @Override public Collection<CompletableFuture<V>> values() {
552
      return asyncCache.cache().values();
×
553
    }
554
    @Override public Set<Entry<K, CompletableFuture<V>>> entrySet() {
555
      return new AsyncEntrySet();
×
556
    }
557
    @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
558
    @Override public boolean equals(@Nullable Object o) {
559
      return (o == this) || asyncCache.cache().equals(o);
×
560
    }
561
    @Override public int hashCode() {
562
      return asyncCache.cache().hashCode();
×
563
    }
564
    @Override public String toString() {
565
      return asyncCache.cache().toString();
×
566
    }
567

568
    private final class AsyncEntrySet extends AbstractSet<Entry<K, CompletableFuture<V>>> {
×
569
      @Override public int size() {
570
        return AsyncAsMapView.this.size();
×
571
      }
572
      @Override public boolean isEmpty() {
573
        return AsyncAsMapView.this.isEmpty();
×
574
      }
575
      @Override public void clear() {
576
        AsyncAsMapView.this.clear();
×
577
      }
×
578
      @Override public boolean contains(Object o) {
579
        return asyncCache.cache().entrySet().contains(o);
×
580
      }
581
      @Override public boolean remove(Object o) {
582
        return asyncCache.cache().entrySet().remove(o);
×
583
      }
584
      @Override public boolean removeAll(Collection<?> collection) {
585
        return asyncCache.cache().entrySet().removeAll(collection);
×
586
      }
587
      @Override public boolean retainAll(Collection<?> collection) {
588
        return asyncCache.cache().entrySet().retainAll(collection);
×
589
      }
590
      @Override public boolean removeIf(Predicate<? super Entry<K, CompletableFuture<V>>> filter) {
591
        return asyncCache.cache().entrySet().removeIf(filter);
×
592
      }
593
      @Override public Iterator<Entry<K, CompletableFuture<V>>> iterator() {
594
        return new AsyncEntryIterator();
×
595
      }
596
      @Override public Spliterator<Entry<K, CompletableFuture<V>>> spliterator() {
597
        return new AsyncEntrySpliterator();
×
598
      }
599
    }
600

601
    private final class AsyncEntryIterator implements Iterator<Entry<K, CompletableFuture<V>>> {
×
602
      final Iterator<Entry<K, CompletableFuture<V>>> iterator =
×
603
          asyncCache.cache().entrySet().iterator();
×
604

605
      @Override public boolean hasNext() {
606
        return iterator.hasNext();
×
607
      }
608
      @Override public Entry<K, CompletableFuture<V>> next() {
609
        var entry = iterator.next();
×
610
        return new WriteThroughEntry<>(AsyncAsMapView.this, entry.getKey(), entry.getValue());
×
611
      }
612
      @Override public void remove() {
613
        iterator.remove();
×
614
      }
×
615
    }
616

617
    private final class AsyncEntrySpliterator
618
        implements Spliterator<Entry<K, CompletableFuture<V>>> {
619
      final Spliterator<Entry<K, CompletableFuture<V>>> spliterator;
620

621
      AsyncEntrySpliterator() {
622
        this(asyncCache.cache().entrySet().spliterator());
×
623
      }
×
624
      AsyncEntrySpliterator(Spliterator<Entry<K, CompletableFuture<V>>> spliterator) {
×
625
        this.spliterator = requireNonNull(spliterator);
×
626
      }
×
627
      @Override public boolean tryAdvance(Consumer<? super Entry<K, CompletableFuture<V>>> action) {
628
        requireNonNull(action);
×
629
        return spliterator.tryAdvance(entry -> action.accept(
×
630
            new WriteThroughEntry<>(AsyncAsMapView.this, entry.getKey(), entry.getValue())));
×
631
      }
632
      @Override public void forEachRemaining(
633
          Consumer<? super Entry<K, CompletableFuture<V>>> action) {
634
        requireNonNull(action);
×
635
        spliterator.forEachRemaining(entry -> action.accept(
×
636
            new WriteThroughEntry<>(AsyncAsMapView.this, entry.getKey(), entry.getValue())));
×
637
      }
×
638
      @Override public @Nullable Spliterator<Entry<K, CompletableFuture<V>>> trySplit() {
639
        var split = spliterator.trySplit();
×
640
        return (split == null) ? null : new AsyncEntrySpliterator(split);
×
641
      }
642
      @Override public long estimateSize() {
643
        return spliterator.estimateSize();
×
644
      }
645
      @Override public int characteristics() {
646
        return spliterator.characteristics();
×
647
      }
648
    }
649
  }
650

651
  /* --------------- Synchronous view --------------- */
652
  final class CacheView<K, V> extends AbstractCacheView<K, V> {
653
    private static final long serialVersionUID = 1L;
654

655
    @SuppressWarnings("serial")
656
    final LocalAsyncCache<K, V> asyncCache;
657

658
    CacheView(LocalAsyncCache<K, V> asyncCache) {
×
659
      this.asyncCache = requireNonNull(asyncCache);
×
660
    }
×
661
    @Override LocalAsyncCache<K, V> asyncCache() {
662
      return asyncCache;
×
663
    }
664
  }
665

666
  abstract class AbstractCacheView<K, V> implements Cache<K, V>, Serializable {
×
667
    private static final long serialVersionUID = 1L;
668

669
    transient @Nullable ConcurrentMap<K, V> asMapView;
670

671
    abstract LocalAsyncCache<K, V> asyncCache();
672

673
    @Override
674
    public @Nullable V getIfPresent(K key) {
675
      var future = asyncCache().cache().getIfPresent(key, /* recordStats= */ false);
×
676
      var value = Async.getIfReady(future);
×
677
      if (value == null) {
×
678
        asyncCache().cache().statsCounter().recordMisses(1);
×
679
      } else {
680
        asyncCache().cache().statsCounter().recordHits(1);
×
681
      }
682
      return value;
×
683
    }
684

685
    @Override
686
    public Map<K, V> getAllPresent(Iterable<? extends K> keys) {
687
      var result = new LinkedHashMap<K, @Nullable V>(calculateHashMapCapacity(keys));
×
688
      for (K key : keys) {
×
689
        result.put(key, null);
×
690
      }
×
691

692
      int uniqueKeys = result.size();
×
693
      for (var iter = result.entrySet().iterator(); iter.hasNext();) {
×
694
        Map.Entry<K, @Nullable V> entry = iter.next();
×
695

696
        CompletableFuture<V> future = asyncCache().cache().get(entry.getKey());
×
697
        V value = Async.getIfReady(future);
×
698
        if (value == null) {
×
699
          iter.remove();
×
700
        } else {
701
          entry.setValue(value);
×
702
        }
703
      }
×
704
      asyncCache().cache().statsCounter().recordHits(result.size());
×
705
      asyncCache().cache().statsCounter().recordMisses(uniqueKeys - result.size());
×
706

707
      @SuppressWarnings("NullableProblems")
708
      Map<K, V> unmodifiable = Collections.unmodifiableMap(result);
×
709
      return unmodifiable;
×
710
    }
711

712
    @Override
713
    public V get(K key, Function<? super K, ? extends V> mappingFunction) {
714
      return resolve(asyncCache().get(key, mappingFunction));
×
715
    }
716

717
    @Override
718
    public Map<K, V> getAll(
719
        Iterable<? extends K> keys,
720
        Function<
721
            ? super Set<? extends K>,
722
            ? extends Map<? extends K, ? extends V>> mappingFunction) {
723
      return resolve(asyncCache().getAll(keys, mappingFunction));
×
724
    }
725

726
    @SuppressWarnings({"PMD.AvoidThrowingNullPointerException",
727
      "PMD.PreserveStackTrace", "UnusedException"})
728
    protected static <T> T resolve(CompletableFuture<T> future) {
729
      try {
730
        return future.join();
×
731
      } catch (NullMapCompletionException e) {
×
732
        throw new NullPointerException("null map");
×
733
      } catch (CompletionException e) {
×
734
        if (e.getCause() instanceof RuntimeException) {
×
735
          throw (RuntimeException) e.getCause();
×
736
        } else if (e.getCause() instanceof Error) {
×
737
          throw (Error) e.getCause();
×
738
        }
739
        throw e;
×
740
      }
741
    }
742

743
    @Override
744
    public void put(K key, V value) {
745
      requireNonNull(value);
×
746
      asyncCache().cache().put(key, CompletableFuture.completedFuture(value));
×
747
    }
×
748

749
    @Override
750
    public void putAll(Map<? extends K, ? extends V> map) {
751
      map.forEach(this::put);
×
752
    }
×
753

754
    @Override
755
    public void invalidate(K key) {
756
      asyncCache().cache().remove(key);
×
757
    }
×
758

759
    @Override
760
    public void invalidateAll(Iterable<? extends K> keys) {
761
      asyncCache().cache().invalidateAll(keys);
×
762
    }
×
763

764
    @Override
765
    public void invalidateAll() {
766
      asyncCache().cache().clear();
×
767
    }
×
768

769
    @Override
770
    public long estimatedSize() {
771
      return asyncCache().cache().estimatedSize();
×
772
    }
773

774
    @Override
775
    public CacheStats stats() {
776
      return asyncCache().cache().statsCounter().snapshot();
×
777
    }
778

779
    @Override
780
    public void cleanUp() {
781
      asyncCache().cache().cleanUp();
×
782
    }
×
783

784
    @Override
785
    public Policy<K, V> policy() {
786
      return asyncCache().policy();
×
787
    }
788

789
    @Override
790
    public ConcurrentMap<K, V> asMap() {
791
      return (asMapView == null) ? (asMapView = new AsMapView<>(asyncCache().cache())) : asMapView;
×
792
    }
793

794
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
795
      throw new InvalidObjectException("Proxy required");
×
796
    }
797

798
    @SuppressWarnings("unused")
799
    private void readObjectNoData() throws InvalidObjectException {
800
      throw new InvalidObjectException("Proxy required");
×
801
    }
802

803
    Object writeReplace() {
804
      return new SyncViewProxy<>(asyncCache());
×
805
    }
806
  }
807

808
  final class AsMapView<K, V> implements ConcurrentMap<K, V> {
809
    final LocalCache<K, CompletableFuture<V>> delegate;
810

811
    @Nullable Set<K> keys;
812
    @Nullable Collection<V> values;
813
    @Nullable Set<Entry<K, V>> entries;
814

815
    AsMapView(LocalCache<K, CompletableFuture<V>> delegate) {
×
816
      this.delegate = delegate;
×
817
    }
×
818

819
    @Override
820
    public boolean isEmpty() {
821
      return delegate.isEmpty();
×
822
    }
823

824
    @Override
825
    public int size() {
826
      return delegate.size();
×
827
    }
828

829
    @Override
830
    public void clear() {
831
      delegate.clear();
×
832
    }
×
833

834
    @Override
835
    public boolean containsKey(Object key) {
836
      return Async.isReady(delegate.getIfPresentQuietly(key));
×
837
    }
838

839
    @Override
840
    public boolean containsValue(Object value) {
841
      requireNonNull(value);
×
842

843
      for (CompletableFuture<V> valueFuture : delegate.values()) {
×
844
        if (value.equals(Async.getIfReady(valueFuture))) {
×
845
          return true;
×
846
        }
847
      }
×
848
      return false;
×
849
    }
850

851
    @Override
852
    public @Nullable V get(Object key) {
853
      return Async.getIfReady(delegate.get(key));
×
854
    }
855

856
    @Override
857
    @SuppressWarnings("ResultOfMethodCallIgnored")
858
    public @Nullable V putIfAbsent(K key, V value) {
859
      requireNonNull(value);
×
860

861
      // Keep in sync with BoundedVarExpiration.putIfAbsentAsync(key, value, duration, unit)
862
      @Var CompletableFuture<V> priorFuture = null;
×
863
      for (;;) {
864
        priorFuture = (priorFuture == null)
×
865
            ? delegate.get(key)
×
866
            : delegate.getIfPresentQuietly(key);
×
867
        if (priorFuture != null) {
×
868
          if (!priorFuture.isDone()) {
×
869
            Async.getWhenSuccessful(priorFuture);
×
870
            continue;
×
871
          }
872

873
          V prior = Async.getWhenSuccessful(priorFuture);
×
874
          if (prior != null) {
×
875
            return prior;
×
876
          }
877
        }
878

879
        boolean[] added = { false };
×
880
        var hints = new LocalCache.RemapHints();
×
881
        CompletableFuture<V> computed = delegate.compute(
×
882
            key, (K k, @Nullable CompletableFuture<V> valueFuture) -> {
883
              added[0] = (valueFuture == null)
×
884
                  || (valueFuture.isDone() && (Async.getIfReady(valueFuture) == null));
×
885
              if (added[0]) {
×
886
                return CompletableFuture.completedFuture(value);
×
887
              }
888
              hints.preserveTimestamps = true;
×
889
              hints.preserveRefresh = true;
×
890
              return valueFuture;
×
891
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
×
892

893
        if (added[0]) {
×
894
          return null;
×
895
        } else {
896
          V prior = Async.getWhenSuccessful(computed);
×
897
          if (prior != null) {
×
898
            return prior;
×
899
          }
900
        }
901
      }
×
902
    }
903

904
    @Override
905
    @SuppressWarnings("ResultOfMethodCallIgnored")
906
    public void putAll(Map<? extends K, ? extends V> map) {
907
      map.forEach(this::put);
×
908
    }
×
909

910
    @Override
911
    public @Nullable V put(K key, V value) {
912
      requireNonNull(value);
×
913
      CompletableFuture<V> oldValueFuture =
×
914
          delegate.put(key, CompletableFuture.completedFuture(value));
×
915
      return Async.getWhenSuccessful(oldValueFuture);
×
916
    }
917

918
    @Override
919
    public @Nullable V remove(Object key) {
920
      CompletableFuture<V> oldValueFuture = delegate.remove(key);
×
921
      return Async.getWhenSuccessful(oldValueFuture);
×
922
    }
923

924
    @Override
925
    @SuppressWarnings("ResultOfMethodCallIgnored")
926
    public boolean remove(Object key, @Nullable Object value) {
927
      requireNonNull(key);
×
928
      if (value == null) {
×
929
        return false;
×
930
      }
931

932
      @SuppressWarnings("unchecked")
933
      var castedKey = (K) key;
×
934
      boolean[] done = { false };
×
935
      boolean[] removed = { false };
×
936
      for (;;) {
937
        CompletableFuture<V> future = delegate.getIfPresentQuietly(castedKey);
×
938
        if ((future == null) || future.isCompletedExceptionally()) {
×
939
          return false;
×
940
        }
941

942
        Async.getWhenSuccessful(future);
×
943
        var hints = new LocalCache.RemapHints();
×
944
        delegate.compute(castedKey, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
×
945
          if (oldValueFuture == null) {
×
946
            done[0] = true;
×
947
            return null;
×
948
          } else if (!oldValueFuture.isDone()) {
×
949
            hints.preserveTimestamps = true;
×
950
            hints.preserveRefresh = true;
×
951
            return oldValueFuture;
×
952
          }
953

954
          done[0] = true;
×
955
          V oldValue = Async.getIfReady(oldValueFuture);
×
956
          removed[0] = Objects.equals(value, oldValue);
×
957
          if (!removed[0] && (oldValue != null)) {
×
958
            hints.preserveTimestamps = true;
×
959
            hints.preserveRefresh = true;
×
960
          }
961
          return (oldValue == null) || removed[0] ? null : oldValueFuture;
×
962
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ true, hints);
×
963

964
        if (done[0]) {
×
965
          return removed[0];
×
966
        }
967
      }
×
968
    }
969

970
    @Override
971
    @SuppressWarnings("ResultOfMethodCallIgnored")
972
    public @Nullable V replace(K key, V value) {
973
      requireNonNull(value);
×
974

975
      @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
976
      @Nullable V[] oldValue = (V[]) new Object[1];
×
977
      boolean[] done = { false };
×
978
      for (;;) {
979
        CompletableFuture<V> future = delegate.getIfPresentQuietly(key);
×
980
        if ((future == null) || future.isCompletedExceptionally()) {
×
981
          return null;
×
982
        }
983

984
        Async.getWhenSuccessful(future);
×
985
        var hints = new LocalCache.RemapHints();
×
986
        delegate.compute(key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
×
987
          if (oldValueFuture == null) {
×
988
            done[0] = true;
×
989
            return null;
×
990
          } else if (!oldValueFuture.isDone()) {
×
991
            hints.preserveTimestamps = true;
×
992
            hints.preserveRefresh = true;
×
993
            return oldValueFuture;
×
994
          }
995

996
          done[0] = true;
×
997
          oldValue[0] = Async.getIfReady(oldValueFuture);
×
998
          return (oldValue[0] == null) ? null : CompletableFuture.completedFuture(value);
×
999
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
×
1000

1001
        if (done[0]) {
×
1002
          return oldValue[0];
×
1003
        }
1004
      }
×
1005
    }
1006

1007
    @Override
1008
    @SuppressWarnings("ResultOfMethodCallIgnored")
1009
    public boolean replace(K key, V oldValue, V newValue) {
1010
      requireNonNull(oldValue);
×
1011
      requireNonNull(newValue);
×
1012

1013
      boolean[] done = { false };
×
1014
      boolean[] replaced = { false };
×
1015
      for (;;) {
1016
        CompletableFuture<V> future = delegate.getIfPresentQuietly(key);
×
1017
        if ((future == null) || future.isCompletedExceptionally()) {
×
1018
          return false;
×
1019
        }
1020

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

1033
          done[0] = true;
×
1034
          replaced[0] = Objects.equals(oldValue, Async.getIfReady(oldValueFuture));
×
1035
          if (!replaced[0]) {
×
1036
            hints.preserveTimestamps = true;
×
1037
            hints.preserveRefresh = true;
×
1038
          }
1039
          return replaced[0] ? CompletableFuture.completedFuture(newValue) : oldValueFuture;
×
1040
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
×
1041

1042
        if (done[0]) {
×
1043
          return replaced[0];
×
1044
        }
1045
      }
×
1046
    }
1047

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

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

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

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

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

1091
        V result = Async.getWhenSuccessful(computed);
×
1092
        if ((computed == future[0]) || (result != null)) {
×
1093
          return result;
×
1094
        }
1095
      }
×
1096
    }
1097

1098
    @Override
1099
    @SuppressWarnings("ResultOfMethodCallIgnored")
1100
    public @Nullable V computeIfPresent(K key,
1101
        BiFunction<? super K, ? super V, ? extends @Nullable V> remappingFunction) {
1102
      requireNonNull(remappingFunction);
×
1103

1104
      @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
1105
      @Nullable V[] newValue = (@Nullable V[]) new Object[1];
×
1106
      for (;;) {
1107
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
×
1108

1109
        var hints = new LocalCache.RemapHints();
×
1110
        CompletableFuture<V> valueFuture = delegate.compute(
×
1111
            key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1112
              if (oldValueFuture == null) {
×
1113
                return null;
×
1114
              } else if (!oldValueFuture.isDone()) {
×
1115
                hints.preserveTimestamps = true;
×
1116
                hints.preserveRefresh = true;
×
1117
                return oldValueFuture;
×
1118
              }
1119

1120
              V oldValue = Async.getIfReady(oldValueFuture);
×
1121
              if (oldValue == null) {
×
1122
                return null;
×
1123
              }
1124

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

1131
        if (newValue[0] != null) {
×
1132
          return newValue[0];
×
1133
        } else if (valueFuture == null) {
×
1134
          return null;
×
1135
        }
1136
      }
×
1137
    }
1138

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

1146
      @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
1147
      @Nullable V[] newValue = (@Nullable V[]) new Object[1];
×
1148
      for (;;) {
1149
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
×
1150

1151
        var hints = new LocalCache.RemapHints();
×
1152
        CompletableFuture<V> valueFuture = delegate.compute(
×
1153
            key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1154
              if ((oldValueFuture != null) && !oldValueFuture.isDone()) {
×
1155
                hints.preserveTimestamps = true;
×
1156
                hints.preserveRefresh = true;
×
1157
                return oldValueFuture;
×
1158
              }
1159

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

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

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

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

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

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

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

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

1227
    @Override
1228
    public Collection<V> values() {
1229
      return (values == null) ? (values = new Values()) : values;
×
1230
    }
1231

1232
    @Override
1233
    public Set<Entry<K, V>> entrySet() {
1234
      return (entries == null) ? (entries = new EntrySet()) : entries;
×
1235
    }
1236

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

1246
      var map = (Map<?, ?>) o;
×
1247
      int expectedSize = size();
×
1248
      if (map.size() != expectedSize) {
×
1249
        return false;
×
1250
      }
1251

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

1268
    @Override
1269
    public int hashCode() {
1270
      @Var int hash = 0;
×
1271
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
×
1272
        var entry = iterator.next();
×
1273
        hash += entry.hashCode();
×
1274
      }
×
1275
      return hash;
×
1276
    }
1277

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

1287
        if (iterator.hasNext()) {
×
1288
          result.append(", ");
×
1289
        }
1290
      }
×
1291
      return result.append('}').toString();
×
1292
    }
1293

1294
    private final class KeySet extends AbstractSet<K> {
×
1295

1296
      @Override
1297
      public boolean isEmpty() {
1298
        return AsMapView.this.isEmpty();
×
1299
      }
1300

1301
      @Override
1302
      public int size() {
1303
        return AsMapView.this.size();
×
1304
      }
1305

1306
      @Override
1307
      public void clear() {
1308
        AsMapView.this.clear();
×
1309
      }
×
1310

1311
      @Override
1312
      public boolean contains(Object o) {
1313
        return AsMapView.this.containsKey(o);
×
1314
      }
1315

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

1329
      @Override
1330
      public boolean removeAll(Collection<?> collection) {
1331
        return delegate.keySet().removeAll(collection);
×
1332
      }
1333

1334
      @Override
1335
      @SuppressWarnings("RedundantCollectionOperation")
1336
      public boolean remove(Object o) {
1337
        return delegate.keySet().remove(o);
×
1338
      }
1339

1340
      @Override
1341
      public boolean removeIf(Predicate<? super K> filter) {
1342
        return delegate.keySet().removeIf(filter);
×
1343
      }
1344

1345
      @Override
1346
      public boolean retainAll(Collection<?> collection) {
1347
        return delegate.keySet().retainAll(collection);
×
1348
      }
1349

1350
      @Override
1351
      public Iterator<K> iterator() {
1352
        return new KeyIterator<>(new EntryIterator());
×
1353
      }
1354

1355
      @Override
1356
      public Spliterator<K> spliterator() {
1357
        return new KeySpliterator<>(new EntrySpliterator());
×
1358
      }
1359
    }
1360

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

1364
      KeyIterator(Iterator<Entry<K, V>> iterator) {
×
1365
        this.iterator = requireNonNull(iterator);
×
1366
      }
×
1367

1368
      @Override
1369
      public boolean hasNext() {
1370
        return iterator.hasNext();
×
1371
      }
1372

1373
      @Override
1374
      public K next() {
1375
        return iterator.next().getKey();
×
1376
      }
1377

1378
      @Override
1379
      public void remove() {
1380
        iterator.remove();
×
1381
      }
×
1382
    }
1383

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

1387
      KeySpliterator(Spliterator<Entry<K, V>> iterator) {
×
1388
        this.spliterator = requireNonNull(iterator);
×
1389
      }
×
1390

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

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

1405
      @Override
1406
      public long estimateSize() {
1407
        return spliterator.estimateSize();
×
1408
      }
1409

1410
      @Override
1411
      public int characteristics() {
1412
        return DISTINCT | NONNULL | CONCURRENT;
×
1413
      }
1414
    }
1415

1416
    private final class Values extends AbstractCollection<V> {
×
1417

1418
      @Override
1419
      public boolean isEmpty() {
1420
        return AsMapView.this.isEmpty();
×
1421
      }
1422

1423
      @Override
1424
      public int size() {
1425
        return AsMapView.this.size();
×
1426
      }
1427

1428
      @Override
1429
      public void clear() {
1430
        AsMapView.this.clear();
×
1431
      }
×
1432

1433
      @Override
1434
      public boolean contains(Object o) {
1435
        return AsMapView.this.containsValue(o);
×
1436
      }
1437

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

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

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

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

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

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

1513
      @Override
1514
      public Iterator<V> iterator() {
1515
        return new ValueIterator<>(new EntryIterator());
×
1516
      }
1517

1518
      @Override
1519
      public Spliterator<V> spliterator() {
1520
        return new ValueSpliterator<>(new EntrySpliterator());
×
1521
      }
1522
    }
1523

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

1527
      ValueIterator(Iterator<Entry<K, V>> iterator) {
×
1528
        this.iterator = requireNonNull(iterator);
×
1529
      }
×
1530

1531
      @Override
1532
      public boolean hasNext() {
1533
        return iterator.hasNext();
×
1534
      }
1535

1536
      @Override
1537
      public V next() {
1538
        return iterator.next().getValue();
×
1539
      }
1540

1541
      @Override
1542
      public void remove() {
1543
        iterator.remove();
×
1544
      }
×
1545
    }
1546

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

1550
      ValueSpliterator(Spliterator<Entry<K, V>> iterator) {
×
1551
        this.spliterator = requireNonNull(iterator);
×
1552
      }
×
1553

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

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

1568
      @Override
1569
      public long estimateSize() {
1570
        return spliterator.estimateSize();
×
1571
      }
1572

1573
      @Override
1574
      public int characteristics() {
1575
        return NONNULL | CONCURRENT;
×
1576
      }
1577
    }
1578

1579
    private final class EntrySet extends AbstractSet<Entry<K, V>> {
×
1580

1581
      @Override
1582
      public boolean isEmpty() {
1583
        return AsMapView.this.isEmpty();
×
1584
      }
1585

1586
      @Override
1587
      public int size() {
1588
        return AsMapView.this.size();
×
1589
      }
1590

1591
      @Override
1592
      public void clear() {
1593
        AsMapView.this.clear();
×
1594
      }
×
1595

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

1611
      @Override
1612
      public boolean removeAll(Collection<?> collection) {
1613
        requireNonNull(collection);
×
1614
        @Var boolean modified = false;
×
1615
        if (delegate.collectKeys()
×
1616
            || ((collection instanceof Set<?>) && (collection.size() > size()))) {
×
1617
          for (var entry : this) {
×
1618
            if (collection.contains(entry)) {
×
1619
              modified |= remove(entry);
×
1620
            }
1621
          }
×
1622
        } else {
1623
          for (var o : collection) {
×
1624
            modified |= remove(o);
×
1625
          }
×
1626
        }
1627
        return modified;
×
1628
      }
1629

1630
      @Override
1631
      public boolean remove(Object obj) {
1632
        if (!(obj instanceof Entry<?, ?>)) {
×
1633
          return false;
×
1634
        }
1635
        var entry = (Entry<?, ?>) obj;
×
1636
        var key = entry.getKey();
×
1637
        return (key != null) && AsMapView.this.remove(key, entry.getValue());
×
1638
      }
1639

1640
      @Override
1641
      public boolean removeIf(Predicate<? super Entry<K, V>> filter) {
1642
        requireNonNull(filter);
×
1643
        return delegate.entrySet().removeIf(entry -> {
×
1644
          V value = Async.getIfReady(entry.getValue());
×
1645
          return (value != null) && filter.test(Map.entry(entry.getKey(), value));
×
1646
        });
1647
      }
1648

1649
      @Override
1650
      public boolean retainAll(Collection<?> collection) {
1651
        requireNonNull(collection);
×
1652
        @Var boolean modified = false;
×
1653
        for (var entry : this) {
×
1654
          if (!collection.contains(entry) && remove(entry)) {
×
1655
            modified = true;
×
1656
          }
1657
        }
×
1658
        return modified;
×
1659
      }
1660

1661
      @Override
1662
      public Iterator<Entry<K, V>> iterator() {
1663
        return new EntryIterator();
×
1664
      }
1665

1666
      @Override
1667
      public Spliterator<Entry<K, V>> spliterator() {
1668
        return new EntrySpliterator();
×
1669
      }
1670
    }
1671

1672
    private final class EntryIterator implements Iterator<Entry<K, V>> {
1673
      final Iterator<Entry<K, CompletableFuture<V>>> iterator;
1674
      @Nullable Entry<K, V> cursor;
1675
      @Nullable K removalKey;
1676

1677
      EntryIterator() {
×
1678
        this.iterator = delegate.entrySet().iterator();
×
1679
      }
×
1680

1681
      @Override
1682
      public boolean hasNext() {
1683
        while ((cursor == null) && iterator.hasNext()) {
×
1684
          Entry<K, CompletableFuture<V>> entry = iterator.next();
×
1685
          V value = Async.getIfReady(entry.getValue());
×
1686
          if (value != null) {
×
1687
            cursor = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
×
1688
          }
1689
        }
×
1690
        return (cursor != null);
×
1691
      }
1692

1693
      @Override
1694
      public Entry<K, V> next() {
1695
        if (!hasNext()) {
×
1696
          throw new NoSuchElementException();
×
1697
        }
1698
        K key = requireNonNull(cursor).getKey();
×
1699
        Entry<K, V> entry = cursor;
×
1700
        removalKey = key;
×
1701
        cursor = null;
×
1702
        return entry;
×
1703
      }
1704

1705
      @Override
1706
      public void remove() {
1707
        requireState(removalKey != null);
×
1708
        delegate.remove(removalKey);
×
1709
        removalKey = null;
×
1710
      }
×
1711
    }
1712

1713
    private final class EntrySpliterator implements Spliterator<Entry<K, V>> {
1714
      final Spliterator<Entry<K, CompletableFuture<V>>> spliterator;
1715

1716
      EntrySpliterator() {
1717
        this(delegate.entrySet().spliterator());
×
1718
      }
×
1719

1720
      EntrySpliterator(Spliterator<Entry<K, CompletableFuture<V>>> spliterator) {
×
1721
        this.spliterator = requireNonNull(spliterator);
×
1722
      }
×
1723

1724
      @Override
1725
      public void forEachRemaining(Consumer<? super Entry<K, V>> action) {
1726
        requireNonNull(action);
×
1727
        spliterator.forEachRemaining(entry -> {
×
1728
          V value = Async.getIfReady(entry.getValue());
×
1729
          if (value != null) {
×
1730
            var e = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
×
1731
            action.accept(e);
×
1732
          }
1733
        });
×
1734
      }
×
1735

1736
      @Override
1737
      public boolean tryAdvance(Consumer<? super Entry<K, V>> action) {
1738
        requireNonNull(action);
×
1739
        boolean[] advanced = { false };
×
1740
        Consumer<? super Entry<K, CompletableFuture<V>>> consumer = entry -> {
×
1741
          V value = Async.getIfReady(entry.getValue());
×
1742
          if (value != null) {
×
1743
            var e = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
×
1744
            action.accept(e);
×
1745
            advanced[0] = true;
×
1746
          }
1747
        };
×
1748
        while (spliterator.tryAdvance(consumer)) {
×
1749
          if (advanced[0]) {
×
1750
            return true;
×
1751
          }
1752
        }
1753
        return false;
×
1754
      }
1755

1756
      @Override
1757
      public @Nullable Spliterator<Entry<K, V>> trySplit() {
1758
        Spliterator<Entry<K, CompletableFuture<V>>> split = spliterator.trySplit();
×
1759
        return (split == null) ? null : new EntrySpliterator(split);
×
1760
      }
1761

1762
      @Override
1763
      public long estimateSize() {
1764
        return spliterator.estimateSize();
×
1765
      }
1766

1767
      @Override
1768
      public int characteristics() {
1769
        return DISTINCT | CONCURRENT | NONNULL;
×
1770
      }
1771
    }
1772
  }
1773

1774
  @SuppressWarnings("serial")
1775
  final class SyncViewProxy<K, V> implements Serializable {
1776
    private static final long serialVersionUID = 1;
1777

1778
    final AsyncCache<K, V> asyncCache;
1779

1780
    SyncViewProxy(AsyncCache<K, V> asyncCache) {
×
1781
      this.asyncCache = requireNonNull(asyncCache);
×
1782
    }
×
1783

1784
    @SuppressWarnings("unused")
1785
    private void readObject(ObjectInputStream stream)
1786
        throws IOException, ClassNotFoundException {
1787
      stream.defaultReadObject();
×
1788
      if (asyncCache == null) {
×
1789
        throw new InvalidObjectException("asyncCache required");
×
1790
      }
1791
    }
×
1792

1793
    Object readResolve() {
1794
      return asyncCache.synchronous();
×
1795
    }
1796
  }
1797
}
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