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

ben-manes / caffeine / #5590

05 Jul 2026 06:38AM UTC coverage: 99.652% (-0.3%) from 99.904%
#5590

push

github

ben-manes
Resolve substitutions in URI-loaded jcache configs

TypesafeConfigurator's file/jar/classpath branches returned a config
stack built on defaultReferenceUnresolved without calling resolve(),
so any HOCON substitution (${ref}, or ${caffeine.jcache.default}
inheritance) threw ConfigException.NotResolved on the first getter.
The default ConfigFactory.load() branch already resolves; append
.resolve() to the three URI branches to match.

4062 of 4088 branches covered (99.36%)

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

21 existing lines in 3 files now uncovered.

8308 of 8337 relevant lines covered (99.65%)

1.0 hits per line

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

99.52
/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
    CompletableFuture<V> present = cache().getIfPresent(key, /* recordStats= */ false);
1✔
95
    if (present != null) {
1✔
96
      if (recordStats) {
1✔
97
        cache().statsCounter().recordHits(1);
1✔
98
      }
99
      return present;
1✔
100
    }
101

102
    long startTime = cache().statsTicker().read();
1✔
103
    @SuppressWarnings({"rawtypes", "unchecked"})
104
    @Nullable CompletableFuture<? extends V>[] result = new CompletableFuture[1];
1✔
105
    CompletableFuture<V> future = cache().computeIfAbsent(key, k -> {
1✔
106
      @SuppressWarnings("unchecked")
107
      var castedResult = (CompletableFuture<V>) mappingFunction.apply(key, cache().executor());
1✔
108
      result[0] = castedResult;
1✔
109
      return requireNonNull(castedResult);
1✔
110
    }, recordStats, /* recordLoad= */ false);
111
    if (result[0] != null) {
1✔
112
      handleCompletion(key, result[0], startTime, /* recordMiss= */ false);
1✔
113
    }
114
    return requireNonNull(future);
1✔
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);
1✔
121
    return getAll(keys, (keysToLoad, executor) ->
1✔
122
        CompletableFuture.supplyAsync(() -> mappingFunction.apply(keysToLoad), executor));
1✔
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);
1✔
131
    requireNonNull(keys);
1✔
132

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

157
    var completer = new AsyncBulkCompleter<>(cache(), proxies);
1✔
158
    try {
159
      var loader = mappingFunction.apply(
1✔
160
          Collections.unmodifiableSet(proxies.keySet()), cache().executor());
1✔
161
      return loader.handle(completer).thenCompose(ignored -> composeResult(futures));
1✔
162
    } catch (Throwable t) {
1✔
163
      throw completer.error(t);
1✔
164
    }
165
  }
166

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

193
  @Override
194
  @SuppressWarnings("FutureReturnValueIgnored")
195
  default void put(K key, CompletableFuture<? extends @Nullable V> valueFuture) {
196
    if (valueFuture.isDone() && (Async.getWhenSuccessful(valueFuture) == null)) {
1✔
197
      cache().statsCounter().recordLoadFailure(0L);
1✔
198
      cache().remove(key);
1✔
199
      return;
1✔
200
    }
201
    long startTime = cache().statsTicker().read();
1✔
202

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

211
  @SuppressWarnings({"FutureReturnValueIgnored", "ResultOfMethodCallIgnored"})
212
  default void handleCompletion(K key, CompletableFuture<? extends V> valueFuture,
213
      long startTime, boolean recordMiss) {
214
    valueFuture.whenComplete((value, error) -> {
1✔
215
      long loadTime = cache().statsTicker().read() - startTime;
1✔
216
      if (value == null) {
1✔
217
        if ((error != null) && !(error instanceof CancellationException)
1✔
218
            && !(error instanceof TimeoutException)) {
219
          logger.log(Level.WARNING, "Exception thrown during asynchronous load", error);
1✔
220
        }
221
        cache().statsCounter().recordLoadFailure(loadTime);
1✔
222
        cache().remove(key, valueFuture);
1✔
223
      } else if (!Async.isReady(valueFuture)) {
1✔
224
        logger.log(Level.ERROR, String.format(US, "An invalid state was detected, occurring when "
1✔
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,
1✔
230
            cache().getClass().getSimpleName()), new IllegalStateException());
1✔
231
        cache().statsCounter().recordLoadFailure(loadTime);
1✔
232
        cache().remove(key, valueFuture);
1✔
233
      } else {
234
        @SuppressWarnings("unchecked")
235
        var castedFuture = (CompletableFuture<V>) valueFuture;
1✔
236

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

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

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

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

275
      if (failure == null) {
1✔
276
        if (requireNonNull(result).isEmpty()) {
1✔
277
          cache.statsCounter().recordLoadFailure(loadTime);
1✔
278
        } else {
279
          cache.statsCounter().recordLoadSuccess(loadTime);
1✔
280
        }
281
        return result;
1✔
282
      }
283

284
      cache.statsCounter().recordLoadFailure(loadTime);
1✔
285
      if (failure instanceof RuntimeException) {
1✔
286
        throw (RuntimeException) failure;
1✔
287
      } else if (failure instanceof Error) {
1✔
288
        throw (Error) failure;
1✔
289
      }
290
      throw new CompletionException(failure);
1✔
291
    }
292

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

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

322
    /** Populates the proxies with the computed result. */
323
    private @Nullable Throwable fillProxies(Map<? extends K, ? extends V> result) {
324
      @Var Throwable error = null;
1✔
325
      for (var entry : proxies.entrySet()) {
1✔
326
        var key = entry.getKey();
1✔
327
        var value = result.get(key);
1✔
328
        var future = entry.getValue();
1✔
329
        future.obtrudeValue(value);
1✔
330

331
        if (value == null) {
1✔
332
          cache.remove(key, future);
1✔
333
        } else {
334
          try {
335
            // update the weight and expiration timestamps
336
            cache.replace(key, future, future);
1✔
337
          } catch (Throwable t) {
1✔
338
            logger.log(Level.WARNING, "Exception thrown during asynchronous load", t);
1✔
339
            cache.remove(key, future);
1✔
340
            if (error == null) {
1✔
341
              error = t;
1✔
342
            } else if (error != t) {
1✔
343
              error.addSuppressed(t);
1✔
344
            }
345
          }
1✔
346
        }
347
      }
1✔
348
      return error;
1✔
349
    }
350

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

377
    static final class NullMapCompletionException extends CompletionException {
1✔
378
      private static final long serialVersionUID = 1L;
379
    }
380
  }
381

382
  /* --------------- Asynchronous view --------------- */
383
  final class AsyncAsMapView<K, V> implements ConcurrentMap<K, CompletableFuture<V>> {
384
    final LocalAsyncCache<K, V> asyncCache;
385

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

473
      @SuppressWarnings({"rawtypes", "unchecked"})
474
      @Nullable CompletableFuture<V>[] result = new CompletableFuture[1];
1✔
475
      @SuppressWarnings({"rawtypes", "unchecked"})
476
      @Nullable CompletableFuture<V>[] prior = new CompletableFuture[1];
1✔
477
      long startTime = asyncCache.cache().statsTicker().read();
1✔
478
      asyncCache.cache().compute(key, (K k, @Nullable CompletableFuture<V> oldValue) -> {
1✔
479
        result[0] = (oldValue == null) ? null : remappingFunction.apply(k, oldValue);
1✔
480
        prior[0] = oldValue;
1✔
481
        return result[0];
1✔
482
      }, asyncCache.cache().expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
1✔
483

484
      if ((result[0] != null) && (result[0] != prior[0])) {
1✔
485
        asyncCache.handleCompletion(key, result[0], startTime, /* recordMiss= */ false);
1✔
486
      }
487
      return result[0];
1✔
488
    }
489
    @SuppressWarnings("ResultOfMethodCallIgnored")
490
    @Override public @Nullable CompletableFuture<V> compute(K key, BiFunction<? super K,
491
        ? super CompletableFuture<V>, ? extends CompletableFuture<V>> remappingFunction) {
492
      requireNonNull(remappingFunction);
1✔
493

494
      @SuppressWarnings({"rawtypes", "unchecked"})
495
      @Nullable CompletableFuture<V>[] result = new CompletableFuture[1];
1✔
496
      @SuppressWarnings({"rawtypes", "unchecked"})
497
      @Nullable CompletableFuture<V>[] prior = new CompletableFuture[1];
1✔
498
      long startTime = asyncCache.cache().statsTicker().read();
1✔
499
      asyncCache.cache().compute(key, (k, oldValue) -> {
1✔
500
        result[0] = remappingFunction.apply(k, oldValue);
1✔
501
        prior[0] = oldValue;
1✔
502
        return result[0];
1✔
503
      }, asyncCache.cache().expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
1✔
504

505
      if ((result[0] != null) && (result[0] != prior[0])) {
1✔
506
        asyncCache.handleCompletion(key, result[0], startTime, /* recordMiss= */ false);
1✔
507
      }
508
      return result[0];
1✔
509
    }
510
    @SuppressWarnings("ResultOfMethodCallIgnored")
511
    @Override public @Nullable CompletableFuture<V> merge(K key, CompletableFuture<V> value,
512
        BiFunction<? super CompletableFuture<V>, ? super CompletableFuture<V>,
513
            ? extends CompletableFuture<V>> remappingFunction) {
514
      requireNonNull(value);
1✔
515
      requireNonNull(remappingFunction);
1✔
516

517
      @SuppressWarnings({"rawtypes", "unchecked"})
518
      @Nullable  CompletableFuture<V>[] result = new CompletableFuture[1];
1✔
519
      @SuppressWarnings({"rawtypes", "unchecked"})
520
      @Nullable  CompletableFuture<V>[] prior = new CompletableFuture[1];
1✔
521
      long startTime = asyncCache.cache().statsTicker().read();
1✔
522
      asyncCache.cache().compute(key, (K k, @Nullable CompletableFuture<V> oldValue) -> {
1✔
523
        result[0] = (oldValue == null) ? value : remappingFunction.apply(oldValue, value);
1✔
524
        prior[0] = oldValue;
1✔
525
        return result[0];
1✔
526
      }, asyncCache.cache().expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false);
1✔
527

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

556
    private final class AsyncEntrySet extends AbstractSet<Entry<K, CompletableFuture<V>>> {
1✔
557
      @Override public int size() {
558
        return AsyncAsMapView.this.size();
1✔
559
      }
560
      @Override public boolean isEmpty() {
561
        return AsyncAsMapView.this.isEmpty();
1✔
562
      }
563
      @Override public void clear() {
564
        AsyncAsMapView.this.clear();
1✔
565
      }
1✔
566
      @Override public boolean contains(Object o) {
567
        return asyncCache.cache().entrySet().contains(o);
1✔
568
      }
569
      @Override public boolean remove(Object o) {
570
        return asyncCache.cache().entrySet().remove(o);
1✔
571
      }
572
      @Override public boolean removeAll(Collection<?> collection) {
573
        return asyncCache.cache().entrySet().removeAll(collection);
1✔
574
      }
575
      @Override public boolean retainAll(Collection<?> collection) {
576
        return asyncCache.cache().entrySet().retainAll(collection);
1✔
577
      }
578
      @Override public boolean removeIf(Predicate<? super Entry<K, CompletableFuture<V>>> filter) {
579
        return asyncCache.cache().entrySet().removeIf(filter);
1✔
580
      }
581
      @Override public Iterator<Entry<K, CompletableFuture<V>>> iterator() {
582
        return new AsyncEntryIterator();
1✔
583
      }
584
      @Override public Spliterator<Entry<K, CompletableFuture<V>>> spliterator() {
585
        return new AsyncEntrySpliterator();
1✔
586
      }
587
    }
588

589
    private final class AsyncEntryIterator implements Iterator<Entry<K, CompletableFuture<V>>> {
1✔
590
      final Iterator<Entry<K, CompletableFuture<V>>> iterator =
1✔
591
          asyncCache.cache().entrySet().iterator();
1✔
592

593
      @Override public boolean hasNext() {
594
        return iterator.hasNext();
1✔
595
      }
596
      @Override public Entry<K, CompletableFuture<V>> next() {
597
        var entry = iterator.next();
1✔
598
        return new WriteThroughEntry<>(AsyncAsMapView.this, entry.getKey(), entry.getValue());
1✔
599
      }
600
      @Override public void remove() {
601
        iterator.remove();
1✔
602
      }
1✔
603
    }
604

605
    private final class AsyncEntrySpliterator
606
        implements Spliterator<Entry<K, CompletableFuture<V>>> {
607
      final Spliterator<Entry<K, CompletableFuture<V>>> spliterator;
608

609
      AsyncEntrySpliterator() {
610
        this(asyncCache.cache().entrySet().spliterator());
1✔
611
      }
1✔
612
      AsyncEntrySpliterator(Spliterator<Entry<K, CompletableFuture<V>>> spliterator) {
1✔
613
        this.spliterator = requireNonNull(spliterator);
1✔
614
      }
1✔
615
      @Override public boolean tryAdvance(Consumer<? super Entry<K, CompletableFuture<V>>> action) {
616
        requireNonNull(action);
1✔
617
        return spliterator.tryAdvance(entry -> action.accept(
1✔
618
            new WriteThroughEntry<>(AsyncAsMapView.this, entry.getKey(), entry.getValue())));
1✔
619
      }
620
      @Override public void forEachRemaining(
621
          Consumer<? super Entry<K, CompletableFuture<V>>> action) {
622
        requireNonNull(action);
1✔
623
        spliterator.forEachRemaining(entry -> action.accept(
1✔
624
            new WriteThroughEntry<>(AsyncAsMapView.this, entry.getKey(), entry.getValue())));
1✔
625
      }
1✔
626
      @Override public @Nullable Spliterator<Entry<K, CompletableFuture<V>>> trySplit() {
627
        var split = spliterator.trySplit();
1✔
628
        return (split == null) ? null : new AsyncEntrySpliterator(split);
1✔
629
      }
630
      @Override public long estimateSize() {
631
        return spliterator.estimateSize();
1✔
632
      }
633
      @Override public int characteristics() {
634
        return spliterator.characteristics();
1✔
635
      }
636
    }
637
  }
638

639
  /* --------------- Synchronous view --------------- */
640
  final class CacheView<K, V> extends AbstractCacheView<K, V> {
641
    private static final long serialVersionUID = 1L;
642

643
    @SuppressWarnings("serial")
644
    final LocalAsyncCache<K, V> asyncCache;
645

646
    CacheView(LocalAsyncCache<K, V> asyncCache) {
1✔
647
      this.asyncCache = requireNonNull(asyncCache);
1✔
648
    }
1✔
649
    @Override LocalAsyncCache<K, V> asyncCache() {
650
      return asyncCache;
1✔
651
    }
652
  }
653

654
  abstract class AbstractCacheView<K, V> implements Cache<K, V>, Serializable {
1✔
655
    private static final long serialVersionUID = 1L;
656

657
    transient @Nullable ConcurrentMap<K, V> asMapView;
658

659
    abstract LocalAsyncCache<K, V> asyncCache();
660

661
    @Override
662
    public @Nullable V getIfPresent(K key) {
663
      var future = asyncCache().cache().getIfPresent(key, /* recordStats= */ false);
1✔
664
      var value = Async.getIfReady(future);
1✔
665
      if (value == null) {
1✔
666
        asyncCache().cache().statsCounter().recordMisses(1);
1✔
667
      } else {
668
        asyncCache().cache().statsCounter().recordHits(1);
1✔
669
      }
670
      return value;
1✔
671
    }
672

673
    @Override
674
    public Map<K, V> getAllPresent(Iterable<? extends K> keys) {
675
      var result = new LinkedHashMap<K, @Nullable V>(calculateHashMapCapacity(keys));
1✔
676
      for (K key : keys) {
1✔
677
        result.put(key, null);
1✔
678
      }
1✔
679

680
      int uniqueKeys = result.size();
1✔
681
      for (var iter = result.entrySet().iterator(); iter.hasNext();) {
1✔
682
        Map.Entry<K, @Nullable V> entry = iter.next();
1✔
683

684
        CompletableFuture<V> future = asyncCache().cache().get(entry.getKey());
1✔
685
        V value = Async.getIfReady(future);
1✔
686
        if (value == null) {
1✔
687
          iter.remove();
1✔
688
        } else {
689
          entry.setValue(value);
1✔
690
        }
691
      }
1✔
692
      asyncCache().cache().statsCounter().recordHits(result.size());
1✔
693
      asyncCache().cache().statsCounter().recordMisses(uniqueKeys - result.size());
1✔
694

695
      @SuppressWarnings("NullableProblems")
696
      Map<K, V> unmodifiable = Collections.unmodifiableMap(result);
1✔
697
      return unmodifiable;
1✔
698
    }
699

700
    @Override
701
    public V get(K key, Function<? super K, ? extends V> mappingFunction) {
702
      return resolve(asyncCache().get(key, mappingFunction));
1✔
703
    }
704

705
    @Override
706
    public Map<K, V> getAll(
707
        Iterable<? extends K> keys,
708
        Function<
709
            ? super Set<? extends K>,
710
            ? extends Map<? extends K, ? extends V>> mappingFunction) {
711
      return resolve(asyncCache().getAll(keys, mappingFunction));
1✔
712
    }
713

714
    @SuppressWarnings({"PMD.AvoidThrowingNullPointerException",
715
      "PMD.PreserveStackTrace", "UnusedException"})
716
    protected static <T> T resolve(CompletableFuture<T> future) {
717
      try {
718
        return future.join();
1✔
719
      } catch (NullMapCompletionException e) {
1✔
720
        throw new NullPointerException("null map");
1✔
721
      } catch (CompletionException e) {
1✔
722
        if (e.getCause() instanceof RuntimeException) {
1✔
723
          throw (RuntimeException) e.getCause();
1✔
724
        } else if (e.getCause() instanceof Error) {
1✔
725
          throw (Error) e.getCause();
1✔
726
        }
727
        throw e;
1✔
728
      }
729
    }
730

731
    @Override
732
    public void put(K key, V value) {
733
      requireNonNull(value);
1✔
734
      asyncCache().cache().put(key, CompletableFuture.completedFuture(value));
1✔
735
    }
1✔
736

737
    @Override
738
    public void putAll(Map<? extends K, ? extends V> map) {
739
      map.forEach(this::put);
1✔
740
    }
1✔
741

742
    @Override
743
    public void invalidate(K key) {
744
      asyncCache().cache().remove(key);
1✔
745
    }
1✔
746

747
    @Override
748
    public void invalidateAll(Iterable<? extends K> keys) {
749
      asyncCache().cache().invalidateAll(keys);
1✔
750
    }
1✔
751

752
    @Override
753
    public void invalidateAll() {
754
      asyncCache().cache().clear();
1✔
755
    }
1✔
756

757
    @Override
758
    public long estimatedSize() {
759
      return asyncCache().cache().estimatedSize();
1✔
760
    }
761

762
    @Override
763
    public CacheStats stats() {
764
      return asyncCache().cache().statsCounter().snapshot();
1✔
765
    }
766

767
    @Override
768
    public void cleanUp() {
769
      asyncCache().cache().cleanUp();
1✔
770
    }
1✔
771

772
    @Override
773
    public Policy<K, V> policy() {
774
      return asyncCache().policy();
1✔
775
    }
776

777
    @Override
778
    public ConcurrentMap<K, V> asMap() {
779
      return (asMapView == null) ? (asMapView = new AsMapView<>(asyncCache().cache())) : asMapView;
1✔
780
    }
781

782
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
783
      throw new InvalidObjectException("Proxy required");
1✔
784
    }
785

786
    @SuppressWarnings("unused")
787
    private void readObjectNoData() throws InvalidObjectException {
788
      throw new InvalidObjectException("Proxy required");
1✔
789
    }
790

791
    Object writeReplace() {
792
      return new SyncViewProxy<>(asyncCache());
1✔
793
    }
794
  }
795

796
  final class AsMapView<K, V> implements ConcurrentMap<K, V> {
797
    final LocalCache<K, CompletableFuture<V>> delegate;
798

799
    @Nullable Set<K> keys;
800
    @Nullable Collection<V> values;
801
    @Nullable Set<Entry<K, V>> entries;
802

803
    AsMapView(LocalCache<K, CompletableFuture<V>> delegate) {
1✔
804
      this.delegate = delegate;
1✔
805
    }
1✔
806

807
    @Override
808
    public boolean isEmpty() {
809
      return delegate.isEmpty();
1✔
810
    }
811

812
    @Override
813
    public int size() {
814
      return delegate.size();
1✔
815
    }
816

817
    @Override
818
    public void clear() {
819
      delegate.clear();
1✔
820
    }
1✔
821

822
    @Override
823
    public boolean containsKey(Object key) {
824
      return Async.isReady(delegate.getIfPresentQuietly(key));
1✔
825
    }
826

827
    @Override
828
    public boolean containsValue(Object value) {
829
      requireNonNull(value);
1✔
830

831
      for (CompletableFuture<V> valueFuture : delegate.values()) {
1✔
832
        if (value.equals(Async.getIfReady(valueFuture))) {
1✔
833
          return true;
1✔
834
        }
835
      }
1✔
836
      return false;
1✔
837
    }
838

839
    @Override
840
    public @Nullable V get(Object key) {
841
      return Async.getIfReady(delegate.get(key));
1✔
842
    }
843

844
    @Override
845
    @SuppressWarnings("ResultOfMethodCallIgnored")
846
    public @Nullable V putIfAbsent(K key, V value) {
847
      requireNonNull(value);
1✔
848

849
      // Keep in sync with BoundedVarExpiration.putIfAbsentAsync(key, value, duration, unit)
850
      @Var CompletableFuture<V> priorFuture = null;
1✔
851
      for (;;) {
852
        priorFuture = (priorFuture == null)
1✔
853
            ? delegate.get(key)
1✔
854
            : delegate.getIfPresentQuietly(key);
1✔
855
        if (priorFuture != null) {
1✔
856
          if (!priorFuture.isDone()) {
1✔
857
            Async.getWhenSuccessful(priorFuture);
1✔
858
            continue;
1✔
859
          }
860

861
          V prior = Async.getWhenSuccessful(priorFuture);
1✔
862
          if (prior != null) {
1✔
863
            return prior;
1✔
864
          }
865
        }
866

867
        boolean[] added = { false };
1✔
868
        var hints = new LocalCache.RemapHints();
1✔
869
        CompletableFuture<V> computed = delegate.compute(
1✔
870
            key, (K k, @Nullable CompletableFuture<V> valueFuture) -> {
871
              added[0] = (valueFuture == null)
1✔
872
                  || (valueFuture.isDone() && (Async.getIfReady(valueFuture) == null));
1!
873
              if (added[0]) {
1✔
874
                return CompletableFuture.completedFuture(value);
1✔
875
              }
876
              hints.preserveTimestamps = true;
1✔
877
              hints.preserveRefresh = true;
1✔
878
              return valueFuture;
1✔
879
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
880

881
        if (added[0]) {
1✔
882
          return null;
1✔
883
        } else {
884
          V prior = Async.getWhenSuccessful(computed);
1✔
885
          if (prior != null) {
1!
886
            return prior;
1✔
887
          }
888
        }
UNCOV
889
      }
×
890
    }
891

892
    @Override
893
    @SuppressWarnings("ResultOfMethodCallIgnored")
894
    public void putAll(Map<? extends K, ? extends V> map) {
895
      map.forEach(this::put);
1✔
896
    }
1✔
897

898
    @Override
899
    public @Nullable V put(K key, V value) {
900
      requireNonNull(value);
1✔
901
      CompletableFuture<V> oldValueFuture =
1✔
902
          delegate.put(key, CompletableFuture.completedFuture(value));
1✔
903
      return Async.getWhenSuccessful(oldValueFuture);
1✔
904
    }
905

906
    @Override
907
    public @Nullable V remove(Object key) {
908
      CompletableFuture<V> oldValueFuture = delegate.remove(key);
1✔
909
      return Async.getWhenSuccessful(oldValueFuture);
1✔
910
    }
911

912
    @Override
913
    @SuppressWarnings("ResultOfMethodCallIgnored")
914
    public boolean remove(Object key, @Nullable Object value) {
915
      requireNonNull(key);
1✔
916
      if (value == null) {
1✔
917
        return false;
1✔
918
      }
919

920
      @SuppressWarnings("unchecked")
921
      var castedKey = (K) key;
1✔
922
      boolean[] done = { false };
1✔
923
      boolean[] removed = { false };
1✔
924
      for (;;) {
925
        CompletableFuture<V> future = delegate.getIfPresentQuietly(castedKey);
1✔
926
        if ((future == null) || future.isCompletedExceptionally()) {
1✔
927
          return false;
1✔
928
        }
929

930
        Async.getWhenSuccessful(future);
1✔
931
        var hints = new LocalCache.RemapHints();
1✔
932
        delegate.compute(castedKey, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1✔
933
          if (oldValueFuture == null) {
1✔
934
            done[0] = true;
1✔
935
            return null;
1✔
936
          } else if (!oldValueFuture.isDone()) {
1✔
937
            hints.preserveTimestamps = true;
1✔
938
            hints.preserveRefresh = true;
1✔
939
            return oldValueFuture;
1✔
940
          }
941

942
          done[0] = true;
1✔
943
          V oldValue = Async.getIfReady(oldValueFuture);
1✔
944
          removed[0] = Objects.equals(value, oldValue);
1✔
945
          if (!removed[0] && (oldValue != null)) {
1✔
946
            hints.preserveTimestamps = true;
1✔
947
            hints.preserveRefresh = true;
1✔
948
          }
949
          return (oldValue == null) || removed[0] ? null : oldValueFuture;
1✔
950
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ true, hints);
1✔
951

952
        if (done[0]) {
1✔
953
          return removed[0];
1✔
954
        }
955
      }
1✔
956
    }
957

958
    @Override
959
    @SuppressWarnings("ResultOfMethodCallIgnored")
960
    public @Nullable V replace(K key, V value) {
961
      requireNonNull(value);
1✔
962

963
      @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
964
      @Nullable V[] oldValue = (V[]) new Object[1];
1✔
965
      boolean[] done = { false };
1✔
966
      for (;;) {
967
        CompletableFuture<V> future = delegate.getIfPresentQuietly(key);
1✔
968
        if ((future == null) || future.isCompletedExceptionally()) {
1✔
969
          return null;
1✔
970
        }
971

972
        Async.getWhenSuccessful(future);
1✔
973
        var hints = new LocalCache.RemapHints();
1✔
974
        delegate.compute(key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1✔
975
          if (oldValueFuture == null) {
1✔
976
            done[0] = true;
1✔
977
            return null;
1✔
978
          } else if (!oldValueFuture.isDone()) {
1✔
979
            hints.preserveTimestamps = true;
1✔
980
            hints.preserveRefresh = true;
1✔
981
            return oldValueFuture;
1✔
982
          }
983

984
          done[0] = true;
1✔
985
          oldValue[0] = Async.getIfReady(oldValueFuture);
1✔
986
          return (oldValue[0] == null) ? null : CompletableFuture.completedFuture(value);
1✔
987
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
988

989
        if (done[0]) {
1✔
990
          return oldValue[0];
1✔
991
        }
992
      }
1✔
993
    }
994

995
    @Override
996
    @SuppressWarnings("ResultOfMethodCallIgnored")
997
    public boolean replace(K key, V oldValue, V newValue) {
998
      requireNonNull(oldValue);
1✔
999
      requireNonNull(newValue);
1✔
1000

1001
      boolean[] done = { false };
1✔
1002
      boolean[] replaced = { false };
1✔
1003
      for (;;) {
1004
        CompletableFuture<V> future = delegate.getIfPresentQuietly(key);
1✔
1005
        if ((future == null) || future.isCompletedExceptionally()) {
1✔
1006
          return false;
1✔
1007
        }
1008

1009
        Async.getWhenSuccessful(future);
1✔
1010
        var hints = new LocalCache.RemapHints();
1✔
1011
        delegate.compute(key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1✔
1012
          if (oldValueFuture == null) {
1✔
1013
            done[0] = true;
1✔
1014
            return null;
1✔
1015
          } else if (!oldValueFuture.isDone()) {
1✔
1016
            hints.preserveTimestamps = true;
1✔
1017
            hints.preserveRefresh = true;
1✔
1018
            return oldValueFuture;
1✔
1019
          }
1020

1021
          done[0] = true;
1✔
1022
          replaced[0] = Objects.equals(oldValue, Async.getIfReady(oldValueFuture));
1✔
1023
          if (!replaced[0]) {
1✔
1024
            hints.preserveTimestamps = true;
1✔
1025
            hints.preserveRefresh = true;
1✔
1026
          }
1027
          return replaced[0] ? CompletableFuture.completedFuture(newValue) : oldValueFuture;
1✔
1028
        }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
1029

1030
        if (done[0]) {
1✔
1031
          return replaced[0];
1✔
1032
        }
1033
      }
1✔
1034
    }
1035

1036
    @Override
1037
    @SuppressWarnings("ResultOfMethodCallIgnored")
1038
    public @Nullable V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
1039
      requireNonNull(mappingFunction);
1✔
1040

1041
      @Var CompletableFuture<V> priorFuture = null;
1✔
1042
      for (;;) {
1043
        priorFuture = (priorFuture == null)
1✔
1044
            ? delegate.get(key)
1✔
1045
            : delegate.getIfPresentQuietly(key);
1✔
1046
        if (priorFuture != null) {
1✔
1047
          if (!priorFuture.isDone()) {
1✔
1048
            Async.getWhenSuccessful(priorFuture);
1✔
1049
            continue;
1✔
1050
          }
1051

1052
          V prior = Async.getWhenSuccessful(priorFuture);
1✔
1053
          if (prior != null) {
1✔
1054
            delegate.statsCounter().recordHits(1);
1✔
1055
            return prior;
1✔
1056
          }
1057
        }
1058

1059
        @SuppressWarnings({"rawtypes", "unchecked"})
1060
        CompletableFuture<V>[] future = new CompletableFuture[1];
1✔
1061
        var hints = new LocalCache.RemapHints();
1✔
1062
        CompletableFuture<V> computed = delegate.compute(
1✔
1063
            key, (K k, @Nullable CompletableFuture<V> valueFuture) -> {
1064
              if ((valueFuture != null)
1✔
1065
                  && (!valueFuture.isDone() || (Async.getIfReady(valueFuture) != null))) {
1✔
1066
                hints.preserveTimestamps = true;
1✔
1067
                hints.preserveRefresh = true;
1✔
1068
                return valueFuture;
1✔
1069
              }
1070

1071
              V newValue = delegate.statsAware(mappingFunction, /* recordLoad= */ true).apply(key);
1✔
1072
              if (newValue == null) {
1✔
1073
                return null;
1✔
1074
              }
1075
              future[0] = CompletableFuture.completedFuture(newValue);
1✔
1076
              return future[0];
1✔
1077
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
1078

1079
        V result = Async.getWhenSuccessful(computed);
1✔
1080
        if ((computed == future[0]) || (result != null)) {
1✔
1081
          return result;
1✔
1082
        }
1083
      }
1✔
1084
    }
1085

1086
    @Override
1087
    @SuppressWarnings("ResultOfMethodCallIgnored")
1088
    public @Nullable V computeIfPresent(K key,
1089
        BiFunction<? super K, ? super V, ? extends @Nullable V> remappingFunction) {
1090
      requireNonNull(remappingFunction);
1✔
1091

1092
      @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
1093
      @Nullable V[] newValue = (@Nullable V[]) new Object[1];
1✔
1094
      for (;;) {
1095
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
1✔
1096

1097
        var hints = new LocalCache.RemapHints();
1✔
1098
        CompletableFuture<V> valueFuture = delegate.compute(
1✔
1099
            key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1100
              if (oldValueFuture == null) {
1✔
1101
                return null;
1✔
1102
              } else if (!oldValueFuture.isDone()) {
1✔
1103
                hints.preserveTimestamps = true;
1✔
1104
                hints.preserveRefresh = true;
1✔
1105
                return oldValueFuture;
1✔
1106
              }
1107

1108
              V oldValue = Async.getIfReady(oldValueFuture);
1✔
1109
              if (oldValue == null) {
1✔
1110
                return null;
1✔
1111
              }
1112

1113
              BiFunction<? super K, ? super V, ? extends @Nullable V> function =
1✔
1114
                  delegate.statsAware(remappingFunction);
1✔
1115
              newValue[0] = function.apply(key, oldValue);
1✔
1116
              return (newValue[0] == null) ? null : CompletableFuture.completedFuture(newValue[0]);
1✔
1117
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
1118

1119
        if (newValue[0] != null) {
1✔
1120
          return newValue[0];
1✔
1121
        } else if (valueFuture == null) {
1✔
1122
          return null;
1✔
1123
        }
1124
      }
1✔
1125
    }
1126

1127
    @Override
1128
    @SuppressWarnings("ResultOfMethodCallIgnored")
1129
    public @Nullable V compute(K key,
1130
        BiFunction<? super K, ? super @Nullable V, ? extends @Nullable V> remappingFunction) {
1131
      // Keep in sync with BoundedVarExpiration.computeAsync(key, remappingFunction, expiry)
1132
      requireNonNull(remappingFunction);
1✔
1133

1134
      @SuppressWarnings({"rawtypes", "unchecked", "Varifier"})
1135
      @Nullable V[] newValue = (@Nullable V[]) new Object[1];
1✔
1136
      for (;;) {
1137
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
1✔
1138

1139
        var hints = new LocalCache.RemapHints();
1✔
1140
        CompletableFuture<V> valueFuture = delegate.compute(
1✔
1141
            key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1142
              if ((oldValueFuture != null) && !oldValueFuture.isDone()) {
1✔
1143
                hints.preserveTimestamps = true;
1✔
1144
                hints.preserveRefresh = true;
1✔
1145
                return oldValueFuture;
1✔
1146
              }
1147

1148
              V oldValue = Async.getIfReady(oldValueFuture);
1✔
1149
              BiFunction<? super K, ? super V, ? extends @Nullable V> function =
1✔
1150
                  delegate.statsAware(remappingFunction,
1✔
1151
                      /* recordLoad= */ true, /* recordLoadFailure= */ true);
1152
              newValue[0] = function.apply(key, oldValue);
1✔
1153
              return (newValue[0] == null) ? null : CompletableFuture.completedFuture(newValue[0]);
1✔
1154
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
1155

1156
        if (newValue[0] != null) {
1✔
1157
          return newValue[0];
1✔
1158
        } else if (valueFuture == null) {
1✔
1159
          return null;
1✔
1160
        }
1161
      }
1✔
1162
    }
1163

1164
    @Override
1165
    @SuppressWarnings("ResultOfMethodCallIgnored")
1166
    public @Nullable V merge(K key, V value,
1167
        BiFunction<? super V, ? super V, ? extends @Nullable V> remappingFunction) {
1168
      requireNonNull(value);
1✔
1169
      requireNonNull(remappingFunction);
1✔
1170

1171
      CompletableFuture<V> newValueFuture = CompletableFuture.completedFuture(value);
1✔
1172
      boolean[] merged = { false };
1✔
1173
      for (;;) {
1174
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));
1✔
1175

1176
        var hints = new LocalCache.RemapHints();
1✔
1177
        CompletableFuture<V> mergedValueFuture = delegate.compute(
1✔
1178
            key, (K k, @Nullable CompletableFuture<V> oldValueFuture) -> {
1179
              if (oldValueFuture == null) {
1✔
1180
                merged[0] = true;
1✔
1181
                return newValueFuture;
1✔
1182
              } else if (!oldValueFuture.isDone()) {
1✔
1183
                hints.preserveTimestamps = true;
1✔
1184
                hints.preserveRefresh = true;
1✔
1185
                return oldValueFuture;
1✔
1186
              }
1187

1188
              merged[0] = true;
1✔
1189
              V oldValue = Async.getIfReady(oldValueFuture);
1✔
1190
              if (oldValue == null) {
1✔
1191
                return newValueFuture;
1✔
1192
              }
1193
              V mergedValue = delegate.statsAware(remappingFunction).apply(oldValue, value);
1✔
1194
              if (mergedValue == null) {
1✔
1195
                return null;
1✔
1196
              } else if (mergedValue == oldValue) {
1✔
1197
                hints.preserveTimestamps = true;
1✔
1198
                hints.preserveRefresh = true;
1✔
1199
                return oldValueFuture;
1✔
1200
              } else if (mergedValue == value) {
1✔
1201
                return newValueFuture;
1✔
1202
              }
1203
              return CompletableFuture.completedFuture(mergedValue);
1✔
1204
            }, delegate.expiry(), /* recordLoad= */ false, /* recordLoadFailure= */ false, hints);
1✔
1205

1206
        if (merged[0]) {
1✔
1207
          return Async.getWhenSuccessful(mergedValueFuture);
1✔
1208
        }
1209
      }
1✔
1210
    }
1211

1212
    @Override
1213
    public Set<K> keySet() {
1214
      return (keys == null) ? (keys = new KeySet()) : keys;
1✔
1215
    }
1216

1217
    @Override
1218
    public Collection<V> values() {
1219
      return (values == null) ? (values = new Values()) : values;
1✔
1220
    }
1221

1222
    @Override
1223
    public Set<Entry<K, V>> entrySet() {
1224
      return (entries == null) ? (entries = new EntrySet()) : entries;
1✔
1225
    }
1226

1227
    /** See {@link BoundedLocalCache#equals(Object)} for semantics. */
1228
    @Override
1229
    public boolean equals(@Nullable Object o) {
1230
      if (o == this) {
1✔
1231
        return true;
1✔
1232
      } else if (!(o instanceof Map)) {
1✔
1233
        return false;
1✔
1234
      }
1235

1236
      var map = (Map<?, ?>) o;
1✔
1237
      int expectedSize = size();
1✔
1238
      if (map.size() != expectedSize) {
1✔
1239
        return false;
1✔
1240
      }
1241

1242
      @Var int count = 0;
1✔
1243
      try {
1244
        for (var iterator = new EntryIterator(); iterator.hasNext();) {
1✔
1245
          var entry = iterator.next();
1✔
1246
          var value = map.get(entry.getKey());
1✔
1247
          if ((value == null) || ((value != entry.getValue()) && !value.equals(entry.getValue()))) {
1✔
1248
            return false;
1✔
1249
          }
1250
          count++;
1✔
1251
        }
1✔
UNCOV
1252
      } catch (ClassCastException | NullPointerException ignored) {
×
UNCOV
1253
        return false;
×
1254
      }
1✔
1255
      return (count == expectedSize);
1✔
1256
    }
1257

1258
    @Override
1259
    public int hashCode() {
1260
      @Var int hash = 0;
1✔
1261
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
1✔
1262
        var entry = iterator.next();
1✔
1263
        hash += entry.hashCode();
1✔
1264
      }
1✔
1265
      return hash;
1✔
1266
    }
1267

1268
    @Override
1269
    public String toString() {
1270
      var result = new StringBuilder(50).append('{');
1✔
1271
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
1✔
1272
        var entry = iterator.next();
1✔
1273
        result.append((entry.getKey() == this) ? "(this Map)" : entry.getKey())
1✔
1274
            .append('=')
1✔
1275
            .append((entry.getValue() == this) ? "(this Map)" : entry.getValue());
1✔
1276

1277
        if (iterator.hasNext()) {
1✔
1278
          result.append(", ");
1✔
1279
        }
1280
      }
1✔
1281
      return result.append('}').toString();
1✔
1282
    }
1283

1284
    private final class KeySet extends AbstractSet<K> {
1✔
1285

1286
      @Override
1287
      public boolean isEmpty() {
1288
        return AsMapView.this.isEmpty();
1✔
1289
      }
1290

1291
      @Override
1292
      public int size() {
1293
        return AsMapView.this.size();
1✔
1294
      }
1295

1296
      @Override
1297
      public void clear() {
1298
        AsMapView.this.clear();
1✔
1299
      }
1✔
1300

1301
      @Override
1302
      public boolean contains(Object o) {
1303
        return AsMapView.this.containsKey(o);
1✔
1304
      }
1305

1306
      @Override
1307
      public boolean removeAll(Collection<?> collection) {
1308
        return delegate.keySet().removeAll(collection);
1✔
1309
      }
1310

1311
      @Override
1312
      @SuppressWarnings("RedundantCollectionOperation")
1313
      public boolean remove(Object o) {
1314
        return delegate.keySet().remove(o);
1✔
1315
      }
1316

1317
      @Override
1318
      public boolean removeIf(Predicate<? super K> filter) {
1319
        return delegate.keySet().removeIf(filter);
1✔
1320
      }
1321

1322
      @Override
1323
      public boolean retainAll(Collection<?> collection) {
1324
        return delegate.keySet().retainAll(collection);
1✔
1325
      }
1326

1327
      @Override
1328
      public Iterator<K> iterator() {
1329
        return new KeyIterator<>(new EntryIterator());
1✔
1330
      }
1331

1332
      @Override
1333
      public Spliterator<K> spliterator() {
1334
        return new KeySpliterator<>(new EntrySpliterator());
1✔
1335
      }
1336
    }
1337

1338
    private static final class KeyIterator<K, V> implements Iterator<K> {
1339
      private final Iterator<Entry<K, V>> iterator;
1340

1341
      KeyIterator(Iterator<Entry<K, V>> iterator) {
1✔
1342
        this.iterator = requireNonNull(iterator);
1✔
1343
      }
1✔
1344

1345
      @Override
1346
      public boolean hasNext() {
1347
        return iterator.hasNext();
1✔
1348
      }
1349

1350
      @Override
1351
      public K next() {
1352
        return iterator.next().getKey();
1✔
1353
      }
1354

1355
      @Override
1356
      public void remove() {
1357
        iterator.remove();
1✔
1358
      }
1✔
1359
    }
1360

1361
    private static final class KeySpliterator<K, V> implements Spliterator<K> {
1362
      private final Spliterator<Entry<K, V>> spliterator;
1363

1364
      KeySpliterator(Spliterator<Entry<K, V>> iterator) {
1✔
1365
        this.spliterator = requireNonNull(iterator);
1✔
1366
      }
1✔
1367

1368
      @Override
1369
      public boolean tryAdvance(Consumer<? super K> action) {
1370
        requireNonNull(action);
1✔
1371
        return spliterator.tryAdvance(entry -> {
1✔
1372
          action.accept(entry.getKey());
1✔
1373
        });
1✔
1374
      }
1375

1376
      @Override
1377
      public @Nullable Spliterator<K> trySplit() {
1378
        Spliterator<Entry<K, V>> split = spliterator.trySplit();
1✔
1379
        return (split == null) ? null : new KeySpliterator<>(split);
1✔
1380
      }
1381

1382
      @Override
1383
      public long estimateSize() {
1384
        return spliterator.estimateSize();
1✔
1385
      }
1386

1387
      @Override
1388
      public int characteristics() {
1389
        return DISTINCT | NONNULL | CONCURRENT;
1✔
1390
      }
1391
    }
1392

1393
    private final class Values extends AbstractCollection<V> {
1✔
1394

1395
      @Override
1396
      public boolean isEmpty() {
1397
        return AsMapView.this.isEmpty();
1✔
1398
      }
1399

1400
      @Override
1401
      public int size() {
1402
        return AsMapView.this.size();
1✔
1403
      }
1404

1405
      @Override
1406
      public void clear() {
1407
        AsMapView.this.clear();
1✔
1408
      }
1✔
1409

1410
      @Override
1411
      public boolean contains(Object o) {
1412
        return AsMapView.this.containsValue(o);
1✔
1413
      }
1414

1415
      @Override
1416
      public boolean removeAll(Collection<?> collection) {
1417
        requireNonNull(collection);
1✔
1418
        @Var boolean modified = false;
1✔
1419
        for (var entry : delegate.entrySet()) {
1✔
1420
          V value = Async.getIfReady(entry.getValue());
1✔
1421
          if ((value != null) && collection.contains(value)
1!
1422
              && AsMapView.this.remove(entry.getKey(), value)) {
1✔
1423
            modified = true;
1✔
1424
          }
1425
        }
1✔
1426
        return modified;
1✔
1427
      }
1428

1429
      @Override
1430
      public boolean remove(@Nullable Object o) {
1431
        if (o == null) {
1✔
1432
          return false;
1✔
1433
        }
1434
        for (var entry : delegate.entrySet()) {
1✔
1435
          V value = Async.getIfReady(entry.getValue());
1✔
1436
          if (o.equals(value) && AsMapView.this.remove(entry.getKey(), value)) {
1✔
1437
            return true;
1✔
1438
          }
1439
        }
1✔
1440
        return false;
1✔
1441
      }
1442

1443
      @Override
1444
      public boolean removeIf(Predicate<? super V> filter) {
1445
        requireNonNull(filter);
1✔
1446
        return delegate.values().removeIf(future -> {
1✔
1447
          V value = Async.getIfReady(future);
1✔
1448
          return (value != null) && filter.test(value);
1✔
1449
        });
1450
      }
1451

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

1466
      @Override
1467
      public void forEach(Consumer<? super V> action) {
1468
        requireNonNull(action);
1✔
1469
        delegate.values().forEach(future -> {
1✔
1470
          V value = Async.getIfReady(future);
1✔
1471
          if (value != null) {
1✔
1472
            action.accept(value);
1✔
1473
          }
1474
        });
1✔
1475
      }
1✔
1476

1477
      @Override
1478
      public Iterator<V> iterator() {
1479
        return new ValueIterator<>(new EntryIterator());
1✔
1480
      }
1481

1482
      @Override
1483
      public Spliterator<V> spliterator() {
1484
        return new ValueSpliterator<>(new EntrySpliterator());
1✔
1485
      }
1486
    }
1487

1488
    private static final class ValueIterator<K, V> implements Iterator<V> {
1489
      private final Iterator<Entry<K, V>> iterator;
1490

1491
      ValueIterator(Iterator<Entry<K, V>> iterator) {
1✔
1492
        this.iterator = requireNonNull(iterator);
1✔
1493
      }
1✔
1494

1495
      @Override
1496
      public boolean hasNext() {
1497
        return iterator.hasNext();
1✔
1498
      }
1499

1500
      @Override
1501
      public V next() {
1502
        return iterator.next().getValue();
1✔
1503
      }
1504

1505
      @Override
1506
      public void remove() {
1507
        iterator.remove();
1✔
1508
      }
1✔
1509
    }
1510

1511
    private static final class ValueSpliterator<K, V> implements Spliterator<V> {
1512
      private final Spliterator<Entry<K, V>> spliterator;
1513

1514
      ValueSpliterator(Spliterator<Entry<K, V>> iterator) {
1✔
1515
        this.spliterator = requireNonNull(iterator);
1✔
1516
      }
1✔
1517

1518
      @Override
1519
      public boolean tryAdvance(Consumer<? super V> action) {
1520
        requireNonNull(action);
1✔
1521
        return spliterator.tryAdvance(entry -> {
1✔
1522
          action.accept(entry.getValue());
1✔
1523
        });
1✔
1524
      }
1525

1526
      @Override
1527
      public @Nullable Spliterator<V> trySplit() {
1528
        Spliterator<Entry<K, V>> split = spliterator.trySplit();
1✔
1529
        return (split == null) ? null : new ValueSpliterator<>(split);
1✔
1530
      }
1531

1532
      @Override
1533
      public long estimateSize() {
1534
        return spliterator.estimateSize();
1✔
1535
      }
1536

1537
      @Override
1538
      public int characteristics() {
1539
        return NONNULL | CONCURRENT;
1✔
1540
      }
1541
    }
1542

1543
    private final class EntrySet extends AbstractSet<Entry<K, V>> {
1✔
1544

1545
      @Override
1546
      public boolean isEmpty() {
1547
        return AsMapView.this.isEmpty();
1✔
1548
      }
1549

1550
      @Override
1551
      public int size() {
1552
        return AsMapView.this.size();
1✔
1553
      }
1554

1555
      @Override
1556
      public void clear() {
1557
        AsMapView.this.clear();
1✔
1558
      }
1✔
1559

1560
      @Override
1561
      public boolean contains(Object o) {
1562
        if (!(o instanceof Entry<?, ?>)) {
1✔
1563
          return false;
1✔
1564
        }
1565
        var entry = (Entry<?, ?>) o;
1✔
1566
        var key = entry.getKey();
1✔
1567
        var value = entry.getValue();
1✔
1568
        if ((key == null) || (value == null)) {
1✔
1569
          return false;
1✔
1570
        }
1571
        V cachedValue = Async.getIfReady(delegate.getIfPresentQuietly(key));
1✔
1572
        return value.equals(cachedValue);
1✔
1573
      }
1574

1575
      @Override
1576
      public boolean removeAll(Collection<?> collection) {
1577
        requireNonNull(collection);
1✔
1578
        @Var boolean modified = false;
1✔
1579
        if (delegate.collectKeys()
1✔
1580
            || ((collection instanceof Set<?>) && (collection.size() > size()))) {
1✔
1581
          for (var entry : this) {
1✔
1582
            if (collection.contains(entry)) {
1✔
1583
              modified |= remove(entry);
1✔
1584
            }
1585
          }
1✔
1586
        } else {
1587
          for (var o : collection) {
1✔
1588
            modified |= remove(o);
1✔
1589
          }
1✔
1590
        }
1591
        return modified;
1✔
1592
      }
1593

1594
      @Override
1595
      public boolean remove(Object obj) {
1596
        if (!(obj instanceof Entry<?, ?>)) {
1✔
1597
          return false;
1✔
1598
        }
1599
        var entry = (Entry<?, ?>) obj;
1✔
1600
        var key = entry.getKey();
1✔
1601
        return (key != null) && AsMapView.this.remove(key, entry.getValue());
1✔
1602
      }
1603

1604
      @Override
1605
      public boolean removeIf(Predicate<? super Entry<K, V>> filter) {
1606
        requireNonNull(filter);
1✔
1607
        return delegate.entrySet().removeIf(entry -> {
1✔
1608
          V value = Async.getIfReady(entry.getValue());
1✔
1609
          return (value != null) && filter.test(Map.entry(entry.getKey(), value));
1!
1610
        });
1611
      }
1612

1613
      @Override
1614
      public boolean retainAll(Collection<?> collection) {
1615
        requireNonNull(collection);
1✔
1616
        @Var boolean modified = false;
1✔
1617
        for (var entry : this) {
1✔
1618
          if (!collection.contains(entry) && remove(entry)) {
1✔
1619
            modified = true;
1✔
1620
          }
1621
        }
1✔
1622
        return modified;
1✔
1623
      }
1624

1625
      @Override
1626
      public Iterator<Entry<K, V>> iterator() {
1627
        return new EntryIterator();
1✔
1628
      }
1629

1630
      @Override
1631
      public Spliterator<Entry<K, V>> spliterator() {
1632
        return new EntrySpliterator();
1✔
1633
      }
1634
    }
1635

1636
    private final class EntryIterator implements Iterator<Entry<K, V>> {
1637
      final Iterator<Entry<K, CompletableFuture<V>>> iterator;
1638
      @Nullable Entry<K, V> cursor;
1639
      @Nullable K removalKey;
1640

1641
      EntryIterator() {
1✔
1642
        this.iterator = delegate.entrySet().iterator();
1✔
1643
      }
1✔
1644

1645
      @Override
1646
      public boolean hasNext() {
1647
        while ((cursor == null) && iterator.hasNext()) {
1✔
1648
          Entry<K, CompletableFuture<V>> entry = iterator.next();
1✔
1649
          V value = Async.getIfReady(entry.getValue());
1✔
1650
          if (value != null) {
1✔
1651
            cursor = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
1✔
1652
          }
1653
        }
1✔
1654
        return (cursor != null);
1✔
1655
      }
1656

1657
      @Override
1658
      public Entry<K, V> next() {
1659
        if (!hasNext()) {
1✔
1660
          throw new NoSuchElementException();
1✔
1661
        }
1662
        K key = requireNonNull(cursor).getKey();
1✔
1663
        Entry<K, V> entry = cursor;
1✔
1664
        removalKey = key;
1✔
1665
        cursor = null;
1✔
1666
        return entry;
1✔
1667
      }
1668

1669
      @Override
1670
      public void remove() {
1671
        requireState(removalKey != null);
1✔
1672
        delegate.remove(removalKey);
1✔
1673
        removalKey = null;
1✔
1674
      }
1✔
1675
    }
1676

1677
    private final class EntrySpliterator implements Spliterator<Entry<K, V>> {
1678
      final Spliterator<Entry<K, CompletableFuture<V>>> spliterator;
1679

1680
      EntrySpliterator() {
1681
        this(delegate.entrySet().spliterator());
1✔
1682
      }
1✔
1683

1684
      EntrySpliterator(Spliterator<Entry<K, CompletableFuture<V>>> spliterator) {
1✔
1685
        this.spliterator = requireNonNull(spliterator);
1✔
1686
      }
1✔
1687

1688
      @Override
1689
      public void forEachRemaining(Consumer<? super Entry<K, V>> action) {
1690
        requireNonNull(action);
1✔
1691
        spliterator.forEachRemaining(entry -> {
1✔
1692
          V value = Async.getIfReady(entry.getValue());
1✔
1693
          if (value != null) {
1✔
1694
            var e = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
1✔
1695
            action.accept(e);
1✔
1696
          }
1697
        });
1✔
1698
      }
1✔
1699

1700
      @Override
1701
      public boolean tryAdvance(Consumer<? super Entry<K, V>> action) {
1702
        requireNonNull(action);
1✔
1703
        boolean[] advanced = { false };
1✔
1704
        Consumer<? super Entry<K, CompletableFuture<V>>> consumer = entry -> {
1✔
1705
          V value = Async.getIfReady(entry.getValue());
1✔
1706
          if (value != null) {
1✔
1707
            var e = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
1✔
1708
            action.accept(e);
1✔
1709
            advanced[0] = true;
1✔
1710
          }
1711
        };
1✔
1712
        while (spliterator.tryAdvance(consumer)) {
1✔
1713
          if (advanced[0]) {
1✔
1714
            return true;
1✔
1715
          }
1716
        }
1717
        return false;
1✔
1718
      }
1719

1720
      @Override
1721
      public @Nullable Spliterator<Entry<K, V>> trySplit() {
1722
        Spliterator<Entry<K, CompletableFuture<V>>> split = spliterator.trySplit();
1✔
1723
        return (split == null) ? null : new EntrySpliterator(split);
1✔
1724
      }
1725

1726
      @Override
1727
      public long estimateSize() {
1728
        return spliterator.estimateSize();
1✔
1729
      }
1730

1731
      @Override
1732
      public int characteristics() {
1733
        return DISTINCT | CONCURRENT | NONNULL;
1✔
1734
      }
1735
    }
1736
  }
1737

1738
  @SuppressWarnings("serial")
1739
  final class SyncViewProxy<K, V> implements Serializable {
1740
    private static final long serialVersionUID = 1;
1741

1742
    final AsyncCache<K, V> asyncCache;
1743

1744
    SyncViewProxy(AsyncCache<K, V> asyncCache) {
1✔
1745
      this.asyncCache = requireNonNull(asyncCache);
1✔
1746
    }
1✔
1747

1748
    @SuppressWarnings("unused")
1749
    private void readObject(ObjectInputStream stream)
1750
        throws IOException, ClassNotFoundException {
1751
      stream.defaultReadObject();
1✔
1752
      if (asyncCache == null) {
1✔
1753
        throw new InvalidObjectException("asyncCache required");
1✔
1754
      }
1755
    }
1✔
1756

1757
    Object readResolve() {
1758
      return asyncCache.synchronous();
1✔
1759
    }
1760
  }
1761
}
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