All Downloads are FREE. Search and download functionalities are using the official Maven repository.

keycloakjar.com.github.benmanes.caffeine.cache.LocalAsyncCache Maven / Gradle / Ivy

There is a newer version: 7.22.0
Show newest version
/*
 * Copyright 2018 Ben Manes. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.benmanes.caffeine.cache;

import static com.github.benmanes.caffeine.cache.Caffeine.calculateHashMapCapacity;
import static com.github.benmanes.caffeine.cache.Caffeine.requireState;
import static java.util.Objects.requireNonNull;

import java.io.Serializable;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

import org.checkerframework.checker.nullness.qual.Nullable;

import com.github.benmanes.caffeine.cache.LocalAsyncCache.AsyncBulkCompleter.NullMapCompletionException;
import com.github.benmanes.caffeine.cache.stats.CacheStats;

/**
 * This class provides a skeletal implementation of the {@link AsyncCache} interface to minimize the
 * effort required to implement a {@link LocalCache}.
 *
 * @author [email protected] (Ben Manes)
 */
interface LocalAsyncCache extends AsyncCache {
  Logger logger = System.getLogger(LocalAsyncCache.class.getName());

  /** Returns the backing {@link LocalCache} data store. */
  LocalCache> cache();

  /** Returns the policy supported by this implementation and its configuration. */
  Policy policy();

  @Override
  default @Nullable CompletableFuture getIfPresent(K key) {
    return cache().getIfPresent(key, /* recordStats */ true);
  }

  @Override
  default CompletableFuture get(K key, Function mappingFunction) {
    requireNonNull(mappingFunction);
    return get(key, (k1, executor) -> CompletableFuture.supplyAsync(
        () -> mappingFunction.apply(key), executor));
  }

  @Override
  default CompletableFuture get(K key, BiFunction> mappingFunction) {
    return get(key, mappingFunction, /* recordStats */ true);
  }

  @SuppressWarnings({"FutureReturnValueIgnored", "NullAway"})
  default CompletableFuture get(K key, BiFunction> mappingFunction, boolean recordStats) {
    long startTime = cache().statsTicker().read();
    @SuppressWarnings({"rawtypes", "unchecked"})
    CompletableFuture[] result = new CompletableFuture[1];
    CompletableFuture future = cache().computeIfAbsent(key, k -> {
      @SuppressWarnings("unchecked")
      var castedResult = (CompletableFuture) mappingFunction.apply(key, cache().executor());
      result[0] = castedResult;
      return requireNonNull(castedResult);
    }, recordStats, /* recordLoad */ false);
    if (result[0] != null) {
      handleCompletion(key, result[0], startTime, /* recordMiss */ false);
    }
    return future;
  }

  @Override
  default CompletableFuture> getAll(Iterable keys,
      Function, ? extends Map> mappingFunction) {
    requireNonNull(mappingFunction);
    return getAll(keys, (keysToLoad, executor) ->
        CompletableFuture.supplyAsync(() -> mappingFunction.apply(keysToLoad), executor));
  }

  @Override
  @SuppressWarnings("FutureReturnValueIgnored")
  default CompletableFuture> getAll(Iterable keys,
      BiFunction, ? super Executor,
          ? extends CompletableFuture>> mappingFunction) {
    requireNonNull(mappingFunction);
    requireNonNull(keys);

    int initialCapacity = calculateHashMapCapacity(keys);
    var futures = new LinkedHashMap>(initialCapacity);
    var proxies = new HashMap>(initialCapacity);
    for (K key : keys) {
      if (futures.containsKey(key)) {
        continue;
      }
      CompletableFuture future = cache().getIfPresent(key, /* recordStats */ false);
      if (future == null) {
        var proxy = new CompletableFuture();
        future = cache().putIfAbsent(key, proxy);
        if (future == null) {
          future = proxy;
          proxies.put(key, proxy);
        }
      }
      futures.put(key, future);
    }
    cache().statsCounter().recordMisses(proxies.size());
    cache().statsCounter().recordHits(futures.size() - proxies.size());
    if (proxies.isEmpty()) {
      return composeResult(futures);
    }

    var completer = new AsyncBulkCompleter<>(cache(), proxies);
    try {
      var loader = mappingFunction.apply(
          Collections.unmodifiableSet(proxies.keySet()), cache().executor());
      loader.whenComplete(completer);
      return composeResult(futures);
    } catch (Throwable t) {
      completer.accept(/* result */ null, t);
      throw t;
    }
  }

  /**
   * Returns a future that waits for all of the dependent futures to complete and returns the
   * combined mapping if successful. If any future fails then it is automatically removed from
   * the cache if still present.
   */
  static  CompletableFuture> composeResult(Map> futures) {
    if (futures.isEmpty()) {
      @SuppressWarnings("ImmutableMapOf")
      Map emptyMap = Collections.unmodifiableMap(Collections.emptyMap());
      return CompletableFuture.completedFuture(emptyMap);
    }
    @SuppressWarnings("rawtypes")
    CompletableFuture[] array = futures.values().toArray(new CompletableFuture[0]);
    return CompletableFuture.allOf(array).thenApply(ignored -> {
      var result = new LinkedHashMap(calculateHashMapCapacity(futures.size()));
      futures.forEach((key, future) -> {
        V value = future.getNow(null);
        if (value != null) {
          result.put(key, value);
        }
      });
      return Collections.unmodifiableMap(result);
    });
  }

  @Override
  @SuppressWarnings("FutureReturnValueIgnored")
  default void put(K key, CompletableFuture valueFuture) {
    if (valueFuture.isCompletedExceptionally()
        || (valueFuture.isDone() && (valueFuture.join() == null))) {
      cache().statsCounter().recordLoadFailure(0L);
      cache().remove(key);
      return;
    }
    long startTime = cache().statsTicker().read();

    @SuppressWarnings("unchecked")
    var castedFuture = (CompletableFuture) valueFuture;
    cache().put(key, castedFuture);
    handleCompletion(key, valueFuture, startTime, /* recordMiss */ false);
  }

  @SuppressWarnings("FutureReturnValueIgnored")
  default void handleCompletion(K key, CompletableFuture valueFuture,
      long startTime, boolean recordMiss) {
    var completed = new AtomicBoolean();
    valueFuture.whenComplete((value, error) -> {
      if (!completed.compareAndSet(false, true)) {
        // Ignore multiple invocations due to ForkJoinPool retrying on delays
        return;
      }
      long loadTime = cache().statsTicker().read() - startTime;
      if (value == null) {
        if ((error != null) && !(error instanceof CancellationException)
            && !(error instanceof TimeoutException)) {
          logger.log(Level.WARNING, "Exception thrown during asynchronous load", error);
        }
        cache().statsCounter().recordLoadFailure(loadTime);
        cache().remove(key, valueFuture);
      } else {
        @SuppressWarnings("unchecked")
        var castedFuture = (CompletableFuture) valueFuture;

        // update the weight and expiration timestamps
        cache().statsCounter().recordLoadSuccess(loadTime);
        cache().replace(key, castedFuture, castedFuture, /* shouldDiscardRefresh */ false);
      }
      if (recordMiss) {
        cache().statsCounter().recordMisses(1);
      }
    });
  }

  /** A function executed asynchronously after a bulk load completes. */
  final class AsyncBulkCompleter
      implements BiConsumer, Throwable> {
    private final LocalCache> cache;
    private final Map> proxies;
    private final long startTime;

    AsyncBulkCompleter(LocalCache> cache,
        Map> proxies) {
      this.startTime = cache.statsTicker().read();
      this.proxies = proxies;
      this.cache = cache;
    }

    @Override
    public void accept(@Nullable Map result, @Nullable Throwable error) {
      long loadTime = cache.statsTicker().read() - startTime;

      if (result == null) {
        if (error == null) {
          error = new NullMapCompletionException();
        }
        for (var entry : proxies.entrySet()) {
          cache.remove(entry.getKey(), entry.getValue());
          entry.getValue().obtrudeException(error);
        }
        cache.statsCounter().recordLoadFailure(loadTime);
        if (!(error instanceof CancellationException) && !(error instanceof TimeoutException)) {
          logger.log(Level.WARNING, "Exception thrown during asynchronous load", error);
        }
      } else {
        fillProxies(result);
        addNewEntries(result);
        cache.statsCounter().recordLoadSuccess(loadTime);
      }
    }

    /** Populates the proxies with the computed result. */
    private void fillProxies(Map result) {
      proxies.forEach((key, future) -> {
        V value = result.get(key);
        future.obtrudeValue(value);
        if (value == null) {
          cache.remove(key, future);
        } else {
          // update the weight and expiration timestamps
          cache.replace(key, future, future);
        }
      });
    }

    /** Adds to the cache any extra entries computed that were not requested. */
    private void addNewEntries(Map result) {
      result.forEach((key, value) -> {
        if (!proxies.containsKey(key)) {
          cache.put(key, CompletableFuture.completedFuture(value));
        }
      });
    }

    static final class NullMapCompletionException extends CompletionException {
      private static final long serialVersionUID = 1L;
    }
  }

  /* --------------- Asynchronous view --------------- */
  final class AsyncAsMapView implements ConcurrentMap> {
    final LocalAsyncCache asyncCache;

    AsyncAsMapView(LocalAsyncCache asyncCache) {
      this.asyncCache = requireNonNull(asyncCache);
    }
    @Override public boolean isEmpty() {
      return asyncCache.cache().isEmpty();
    }
    @Override public int size() {
      return asyncCache.cache().size();
    }
    @Override public void clear() {
      asyncCache.cache().clear();
    }
    @Override public boolean containsKey(Object key) {
      return asyncCache.cache().containsKey(key);
    }
    @Override public boolean containsValue(Object value) {
      return asyncCache.cache().containsValue(value);
    }
    @Override public @Nullable CompletableFuture get(Object key) {
      return asyncCache.cache().get(key);
    }
    @Override public CompletableFuture putIfAbsent(K key, CompletableFuture value) {
      CompletableFuture prior = asyncCache.cache().putIfAbsent(key, value);
      long startTime = asyncCache.cache().statsTicker().read();
      if (prior == null) {
        asyncCache.handleCompletion(key, value, startTime, /* recordMiss */ false);
      }
      return prior;
    }
    @Override public CompletableFuture put(K key, CompletableFuture value) {
      CompletableFuture prior = asyncCache.cache().put(key, value);
      long startTime = asyncCache.cache().statsTicker().read();
      asyncCache.handleCompletion(key, value, startTime, /* recordMiss */ false);
      return prior;
    }
    @SuppressWarnings("FutureReturnValueIgnored")
    @Override public void putAll(Map> map) {
      map.forEach(this::put);
    }
    @Override public CompletableFuture replace(K key, CompletableFuture value) {
      CompletableFuture prior = asyncCache.cache().replace(key, value);
      long startTime = asyncCache.cache().statsTicker().read();
      if (prior != null) {
        asyncCache.handleCompletion(key, value, startTime, /* recordMiss */ false);
      }
      return prior;
    }
    @Override
    public boolean replace(K key, CompletableFuture oldValue, CompletableFuture newValue) {
      boolean replaced = asyncCache.cache().replace(key, oldValue, newValue);
      long startTime = asyncCache.cache().statsTicker().read();
      if (replaced) {
        asyncCache.handleCompletion(key, newValue, startTime, /* recordMiss */ false);
      }
      return replaced;
    }
    @Override public CompletableFuture remove(Object key) {
      return asyncCache.cache().remove(key);
    }
    @Override public boolean remove(Object key, Object value) {
      return asyncCache.cache().remove(key, value);
    }
    @SuppressWarnings("FutureReturnValueIgnored")
    @Override public @Nullable CompletableFuture computeIfAbsent(K key,
        Function> mappingFunction) {
      requireNonNull(mappingFunction);
      @SuppressWarnings({"rawtypes", "unchecked"})
      CompletableFuture[] result = new CompletableFuture[1];
      long startTime = asyncCache.cache().statsTicker().read();
      CompletableFuture future = asyncCache.cache().computeIfAbsent(key, k -> {
        result[0] = mappingFunction.apply(k);
        return result[0];
      }, /* recordStats */ false, /* recordLoad */ false);

      if (result[0] == null) {
        if ((future != null) && asyncCache.cache().isRecordingStats()) {
          future.whenComplete((r, e) -> {
            if ((r != null) || (e == null)) {
              asyncCache.cache().statsCounter().recordHits(1);
            }
          });
        }
      } else {
        asyncCache.handleCompletion(key, result[0], startTime, /* recordMiss */ true);
      }
      return future;
    }
    @Override public CompletableFuture computeIfPresent(K key, BiFunction, ? extends CompletableFuture> remappingFunction) {
      requireNonNull(remappingFunction);

      @SuppressWarnings({"rawtypes", "unchecked"})
      CompletableFuture[] result = new CompletableFuture[1];
      long startTime = asyncCache.cache().statsTicker().read();
      asyncCache.cache().compute(key, (k, oldValue) -> {
        result[0] = (oldValue == null) ? null : remappingFunction.apply(k, oldValue);
        return result[0];
      }, asyncCache.cache().expiry(), /* recordLoad */ false, /* recordLoadFailure */ false);

      if (result[0] != null) {
        asyncCache.handleCompletion(key, result[0], startTime, /* recordMiss */ false);
      }
      return result[0];
    }
    @Override public CompletableFuture compute(K key, BiFunction, ? extends CompletableFuture> remappingFunction) {
      requireNonNull(remappingFunction);

      @SuppressWarnings({"rawtypes", "unchecked"})
      CompletableFuture[] result = new CompletableFuture[1];
      long startTime = asyncCache.cache().statsTicker().read();
      asyncCache.cache().compute(key, (k, oldValue) -> {
        result[0] = remappingFunction.apply(k, oldValue);
        return result[0];
      }, asyncCache.cache().expiry(), /* recordLoad */ false, /* recordLoadFailure */ false);

      if (result[0] != null) {
        asyncCache.handleCompletion(key, result[0], startTime, /* recordMiss */ false);
      }
      return result[0];
    }
    @Override public CompletableFuture merge(K key, CompletableFuture value,
        BiFunction, ? super CompletableFuture,
            ? extends CompletableFuture> remappingFunction) {
      requireNonNull(value);
      requireNonNull(remappingFunction);

      @SuppressWarnings({"rawtypes", "unchecked"})
      CompletableFuture[] result = new CompletableFuture[1];
      long startTime = asyncCache.cache().statsTicker().read();
      asyncCache.cache().compute(key, (k, oldValue) -> {
        result[0] = (oldValue == null) ? value : remappingFunction.apply(oldValue, value);
        return result[0];
      }, asyncCache.cache().expiry(), /* recordLoad */ false, /* recordLoadFailure */ false);

      if (result[0] != null) {
        asyncCache.handleCompletion(key, result[0], startTime, /* recordMiss */ false);
      }
      return result[0];
    }
    @Override public void forEach(BiConsumer> action) {
      asyncCache.cache().forEach(action);
    }
    @Override public Set keySet() {
      return asyncCache.cache().keySet();
    }
    @Override public Collection> values() {
      return asyncCache.cache().values();
    }
    @Override public Set>> entrySet() {
      return asyncCache.cache().entrySet();
    }
    @Override public boolean equals(Object o) {
      return asyncCache.cache().equals(o);
    }
    @Override public int hashCode() {
      return asyncCache.cache().hashCode();
    }
    @Override public String toString() {
      return asyncCache.cache().toString();
    }
  }

  /* --------------- Synchronous view --------------- */
  final class CacheView extends AbstractCacheView {
    private static final long serialVersionUID = 1L;

    @SuppressWarnings("serial")
    final LocalAsyncCache asyncCache;

    CacheView(LocalAsyncCache asyncCache) {
      this.asyncCache = requireNonNull(asyncCache);
    }
    @Override LocalAsyncCache asyncCache() {
      return asyncCache;
    }
  }

  abstract class AbstractCacheView implements Cache, Serializable {
    private static final long serialVersionUID = 1L;

    transient @Nullable ConcurrentMap asMapView;

    abstract LocalAsyncCache asyncCache();

    @Override
    public @Nullable V getIfPresent(K key) {
      CompletableFuture future = asyncCache().cache().getIfPresent(key, /* recordStats */ true);
      return Async.getIfReady(future);
    }

    @Override
    public Map getAllPresent(Iterable keys) {
      var result = new LinkedHashMap(calculateHashMapCapacity(keys));
      for (K key : keys) {
        result.put(key, null);
      }

      int uniqueKeys = result.size();
      for (var iter = result.entrySet().iterator(); iter.hasNext();) {
        Map.Entry entry = iter.next();

        CompletableFuture future = asyncCache().cache().get(entry.getKey());
        V value = Async.getIfReady(future);
        if (value == null) {
          iter.remove();
        } else {
          entry.setValue(value);
        }
      }
      asyncCache().cache().statsCounter().recordHits(result.size());
      asyncCache().cache().statsCounter().recordMisses(uniqueKeys - result.size());

      return Collections.unmodifiableMap(result);
    }

    @Override
    public V get(K key, Function mappingFunction) {
      return resolve(asyncCache().get(key, mappingFunction));
    }

    @Override
    public Map getAll(Iterable keys,
        Function, ? extends Map> mappingFunction) {
      return resolve(asyncCache().getAll(keys, mappingFunction));
    }

    @SuppressWarnings({"PMD.AvoidThrowingNullPointerException", "PMD.PreserveStackTrace"})
    protected static  T resolve(CompletableFuture future) {
      try {
        return future.join();
      } catch (NullMapCompletionException e) {
        throw new NullPointerException("null map");
      } catch (CompletionException e) {
        if (e.getCause() instanceof RuntimeException) {
          throw (RuntimeException) e.getCause();
        } else if (e.getCause() instanceof Error) {
          throw (Error) e.getCause();
        }
        throw e;
      }
    }

    @Override
    public void put(K key, V value) {
      requireNonNull(value);
      asyncCache().cache().put(key, CompletableFuture.completedFuture(value));
    }

    @Override
    public void putAll(Map map) {
      map.forEach(this::put);
    }

    @Override
    public void invalidate(K key) {
      asyncCache().cache().remove(key);
    }

    @Override
    public void invalidateAll(Iterable keys) {
      asyncCache().cache().invalidateAll(keys);
    }

    @Override
    public void invalidateAll() {
      asyncCache().cache().clear();
    }

    @Override
    public long estimatedSize() {
      return asyncCache().cache().estimatedSize();
    }

    @Override
    public CacheStats stats() {
      return asyncCache().cache().statsCounter().snapshot();
    }

    @Override
    public void cleanUp() {
      asyncCache().cache().cleanUp();
    }

    @Override
    public Policy policy() {
      return asyncCache().policy();
    }

    @Override
    public ConcurrentMap asMap() {
      return (asMapView == null) ? (asMapView = new AsMapView<>(asyncCache().cache())) : asMapView;
    }
  }

  final class AsMapView implements ConcurrentMap {
    final LocalCache> delegate;

    @Nullable Collection values;
    @Nullable Set> entries;

    AsMapView(LocalCache> delegate) {
      this.delegate = delegate;
    }

    @Override
    public boolean isEmpty() {
      return delegate.isEmpty();
    }

    @Override
    public int size() {
      return delegate.size();
    }

    @Override
    public void clear() {
      delegate.clear();
    }

    @Override
    public boolean containsKey(Object key) {
      return delegate.containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
      requireNonNull(value);

      for (CompletableFuture valueFuture : delegate.values()) {
        if (value.equals(Async.getIfReady(valueFuture))) {
          return true;
        }
      }
      return false;
    }

    @Override
    public @Nullable V get(Object key) {
      return Async.getIfReady(delegate.get(key));
    }

    @Override
    public @Nullable V putIfAbsent(K key, V value) {
      requireNonNull(value);

      // Keep in sync with BoundedVarExpiration.putIfAbsentAsync(key, value, duration, unit)
      CompletableFuture priorFuture = null;
      for (;;) {
        priorFuture = (priorFuture == null)
            ? delegate.get(key)
            : delegate.getIfPresentQuietly(key);
        if (priorFuture != null) {
          if (!priorFuture.isDone()) {
            Async.getWhenSuccessful(priorFuture);
            continue;
          }

          V prior = Async.getWhenSuccessful(priorFuture);
          if (prior != null) {
            return prior;
          }
        }

        boolean[] added = { false };
        CompletableFuture computed = delegate.compute(key, (k, valueFuture) -> {
          added[0] = (valueFuture == null)
              || (valueFuture.isDone() && (Async.getIfReady(valueFuture) == null));
          return added[0] ? CompletableFuture.completedFuture(value) : valueFuture;
        }, delegate.expiry(), /* recordLoad */ false, /* recordLoadFailure */ false);

        if (added[0]) {
          return null;
        } else {
          V prior = Async.getWhenSuccessful(computed);
          if (prior != null) {
            return prior;
          }
        }
      }
    }

    @Override
    public void putAll(Map map) {
      map.forEach(this::put);
    }

    @Override
    public @Nullable V put(K key, V value) {
      requireNonNull(value);
      CompletableFuture oldValueFuture =
          delegate.put(key, CompletableFuture.completedFuture(value));
      return Async.getWhenSuccessful(oldValueFuture);
    }

    @Override
    public @Nullable V remove(Object key) {
      CompletableFuture oldValueFuture = delegate.remove(key);
      return Async.getWhenSuccessful(oldValueFuture);
    }

    @Override
    public boolean remove(Object key, Object value) {
      requireNonNull(key);
      if (value == null) {
        return false;
      }

      @SuppressWarnings("unchecked")
      K castedKey = (K) key;
      boolean[] done = { false };
      boolean[] removed = { false };
      CompletableFuture future = null;
      for (;;) {
        future = (future == null)
            ? delegate.get(castedKey)
            : delegate.getIfPresentQuietly(castedKey);
        if ((future == null) || future.isCompletedExceptionally()) {
          return false;
        }

        Async.getWhenSuccessful(future);
        delegate.compute(castedKey, (k, oldValueFuture) -> {
          if (oldValueFuture == null) {
            done[0] = true;
            return null;
          } else if (!oldValueFuture.isDone()) {
            return oldValueFuture;
          }

          done[0] = true;
          V oldValue = Async.getIfReady(oldValueFuture);
          removed[0] = value.equals(oldValue);
          return (oldValue == null) || removed[0] ? null : oldValueFuture;
        }, delegate.expiry(), /* recordLoad */ false, /* recordLoadFailure */ true);

        if (done[0]) {
          return removed[0];
        }
      }
    }

    @Override
    public @Nullable V replace(K key, V value) {
      requireNonNull(value);

      @SuppressWarnings({"rawtypes", "unchecked"})
      V[] oldValue = (V[]) new Object[1];
      boolean[] done = { false };
      for (;;) {
        CompletableFuture future = delegate.getIfPresentQuietly(key);
        if ((future == null) || future.isCompletedExceptionally()) {
          return null;
        }

        Async.getWhenSuccessful(future);
        delegate.compute(key, (k, oldValueFuture) -> {
          if (oldValueFuture == null) {
            done[0] = true;
            return null;
          } else if (!oldValueFuture.isDone()) {
            return oldValueFuture;
          }

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

        if (done[0]) {
          return oldValue[0];
        }
      }
    }

    @Override
    public boolean replace(K key, V oldValue, V newValue) {
      requireNonNull(oldValue);
      requireNonNull(newValue);

      boolean[] done = { false };
      boolean[] replaced = { false };
      for (;;) {
        CompletableFuture future = delegate.getIfPresentQuietly(key);
        if ((future == null) || future.isCompletedExceptionally()) {
          return false;
        }

        Async.getWhenSuccessful(future);
        delegate.compute(key, (k, oldValueFuture) -> {
          if (oldValueFuture == null) {
            done[0] = true;
            return null;
          } else if (!oldValueFuture.isDone()) {
            return oldValueFuture;
          }

          done[0] = true;
          replaced[0] = oldValue.equals(Async.getIfReady(oldValueFuture));
          return replaced[0] ? CompletableFuture.completedFuture(newValue) : oldValueFuture;
        }, delegate.expiry(), /* recordLoad */ false, /* recordLoadFailure */ false);

        if (done[0]) {
          return replaced[0];
        }
      }
    }

    @Override
    public @Nullable V computeIfAbsent(K key, Function mappingFunction) {
      requireNonNull(mappingFunction);

      CompletableFuture priorFuture = null;
      for (;;) {
        priorFuture = (priorFuture == null)
            ? delegate.get(key)
            : delegate.getIfPresentQuietly(key);
        if (priorFuture != null) {
          if (!priorFuture.isDone()) {
            Async.getWhenSuccessful(priorFuture);
            continue;
          }

          V prior = Async.getWhenSuccessful(priorFuture);
          if (prior != null) {
            delegate.statsCounter().recordHits(1);
            return prior;
          }
        }

        @SuppressWarnings({"rawtypes", "unchecked"})
        CompletableFuture[] future = new CompletableFuture[1];
        CompletableFuture computed = delegate.compute(key, (k, valueFuture) -> {
          if ((valueFuture != null) && valueFuture.isDone()
              && (Async.getIfReady(valueFuture) != null)) {
            return valueFuture;
          }

          V newValue = delegate.statsAware(mappingFunction, /* recordLoad */ true).apply(key);
          if (newValue == null) {
            return null;
          }
          future[0] = CompletableFuture.completedFuture(newValue);
          return future[0];
        }, delegate.expiry(), /* recordLoad */ false, /* recordLoadFailure */ false);

        V result = Async.getWhenSuccessful(computed);
        if ((computed == future[0]) || (result != null)) {
          return result;
        }
      }
    }

    @Override
    public @Nullable V computeIfPresent(K key,
        BiFunction remappingFunction) {
      requireNonNull(remappingFunction);

      @SuppressWarnings({"rawtypes", "unchecked"})
      V[] newValue = (V[]) new Object[1];
      for (;;) {
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));

        CompletableFuture valueFuture = delegate.computeIfPresent(key, (k, oldValueFuture) -> {
          if (!oldValueFuture.isDone()) {
            return oldValueFuture;
          }

          V oldValue = Async.getIfReady(oldValueFuture);
          if (oldValue == null) {
            return null;
          }

          newValue[0] = remappingFunction.apply(key, oldValue);
          return (newValue[0] == null) ? null : CompletableFuture.completedFuture(newValue[0]);
        });

        if (newValue[0] != null) {
          return newValue[0];
        } else if (valueFuture == null) {
          return null;
        }
      }
    }

    @Override
    public @Nullable V compute(K key,
        BiFunction remappingFunction) {
      // Keep in sync with BoundedVarExpiration.computeAsync(key, remappingFunction, expiry)
      requireNonNull(remappingFunction);

      @SuppressWarnings({"rawtypes", "unchecked"})
      V[] newValue = (V[]) new Object[1];
      for (;;) {
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));

        CompletableFuture valueFuture = delegate.compute(key, (k, oldValueFuture) -> {
          if ((oldValueFuture != null) && !oldValueFuture.isDone()) {
            return oldValueFuture;
          }

          V oldValue = Async.getIfReady(oldValueFuture);
          BiFunction function = delegate.statsAware(
              remappingFunction, /* recordLoad */ true, /* recordLoadFailure */ true);
          newValue[0] = function.apply(key, oldValue);
          return (newValue[0] == null) ? null : CompletableFuture.completedFuture(newValue[0]);
        }, delegate.expiry(), /* recordLoad */ false, /* recordLoadFailure */ false);

        if (newValue[0] != null) {
          return newValue[0];
        } else if (valueFuture == null) {
          return null;
        }
      }
    }

    @Override
    public @Nullable V merge(K key, V value,
        BiFunction remappingFunction) {
      requireNonNull(value);
      requireNonNull(remappingFunction);

      CompletableFuture newValueFuture = CompletableFuture.completedFuture(value);
      boolean[] merged = { false };
      for (;;) {
        Async.getWhenSuccessful(delegate.getIfPresentQuietly(key));

        CompletableFuture mergedValueFuture = delegate.merge(
            key, newValueFuture, (oldValueFuture, valueFuture) -> {
          if ((oldValueFuture != null) && !oldValueFuture.isDone()) {
            return oldValueFuture;
          }

          merged[0] = true;
          V oldValue = Async.getIfReady(oldValueFuture);
          if (oldValue == null) {
            return valueFuture;
          }
          V mergedValue = remappingFunction.apply(oldValue, value);
          if (mergedValue == null) {
            return null;
          } else if (mergedValue == oldValue) {
            return oldValueFuture;
          } else if (mergedValue == value) {
            return valueFuture;
          }
          return CompletableFuture.completedFuture(mergedValue);
        });

        if (merged[0] || (mergedValueFuture == newValueFuture)) {
          return Async.getWhenSuccessful(mergedValueFuture);
        }
      }
    }

    @Override
    public Set keySet() {
      return delegate.keySet();
    }

    @Override
    public Collection values() {
      return (values == null) ? (values = new Values()) : values;
    }

    @Override
    public Set> entrySet() {
      return (entries == null) ? (entries = new EntrySet()) : entries;
    }

    /** See {@link BoundedLocalCache#equals(Object)} for semantics. */
    @Override
    public boolean equals(Object o) {
      if (o == this) {
        return true;
      } else if (!(o instanceof Map)) {
        return false;
      }

      var map = (Map) o;
      int expectedSize = size();
      if (map.size() != expectedSize) {
        return false;
      }

      int count = 0;
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
        var entry = iterator.next();
        var value = map.get(entry.getKey());
        if ((value == null) || ((value != entry.getValue()) && !value.equals(entry.getValue()))) {
          return false;
        }
        count++;
      }
      return (count == expectedSize);
    }

    @Override
    public int hashCode() {
      int hash = 0;
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
        var entry = iterator.next();
        hash += entry.hashCode();
      }
      return hash;
    }

    @Override
    public String toString() {
      var result = new StringBuilder(50).append('{');
      for (var iterator = new EntryIterator(); iterator.hasNext();) {
        var entry = iterator.next();
        result.append((entry.getKey() == this) ? "(this Map)" : entry.getKey())
            .append('=')
            .append((entry.getValue() == this) ? "(this Map)" : entry.getValue());

        if (iterator.hasNext()) {
          result.append(", ");
        }
      }
      return result.append('}').toString();
    }

    private final class Values extends AbstractCollection {

      @Override
      public boolean isEmpty() {
        return AsMapView.this.isEmpty();
      }

      @Override
      public int size() {
        return AsMapView.this.size();
      }

      @Override
      public void clear() {
        AsMapView.this.clear();
      }

      @Override
      public boolean contains(Object o) {
        return AsMapView.this.containsValue(o);
      }

      @Override
      public boolean removeAll(Collection collection) {
        requireNonNull(collection);
        boolean modified = false;
        for (var entry : delegate.entrySet()) {
          V value = Async.getIfReady(entry.getValue());
          if ((value != null) && collection.contains(value)
              && AsMapView.this.remove(entry.getKey(), value)) {
            modified = true;
          }
        }
        return modified;
      }

      @Override
      public boolean remove(Object o) {
        if (o == null) {
          return false;
        }
        for (var entry : delegate.entrySet()) {
          V value = Async.getIfReady(entry.getValue());
          if ((value != null) && value.equals(o) && AsMapView.this.remove(entry.getKey(), value)) {
            return true;
          }
        }
        return false;
      }

      @Override
      public boolean removeIf(Predicate filter) {
        requireNonNull(filter);
        return delegate.values().removeIf(future -> {
          V value = Async.getIfReady(future);
          return (value != null) && filter.test(value);
        });
      }

      @Override
      public boolean retainAll(Collection collection) {
        requireNonNull(collection);
        boolean modified = false;
        for (var entry : delegate.entrySet()) {
          V value = Async.getIfReady(entry.getValue());
          if ((value != null) && !collection.contains(value)
              && AsMapView.this.remove(entry.getKey(), value)) {
            modified = true;
          }
        }
        return modified;
      }

      @Override
      public void forEach(Consumer action) {
        requireNonNull(action);
        delegate.values().forEach(future -> {
          V value = Async.getIfReady(future);
          if (value != null) {
            action.accept(value);
          }
        });
      }

      @Override
      public Iterator iterator() {
        return new Iterator() {
          final Iterator> iterator = entrySet().iterator();

          @Override
          public boolean hasNext() {
            return iterator.hasNext();
          }

          @Override
          public V next() {
            return iterator.next().getValue();
          }

          @Override
          public void remove() {
            iterator.remove();
          }
        };
      }
    }

    private final class EntrySet extends AbstractSet> {

      @Override
      public boolean isEmpty() {
        return AsMapView.this.isEmpty();
      }

      @Override
      public int size() {
        return AsMapView.this.size();
      }

      @Override
      public void clear() {
        AsMapView.this.clear();
      }

      @Override
      public boolean contains(Object o) {
        if (!(o instanceof Entry)) {
          return false;
        }
        Entry entry = (Entry) o;
        var key = entry.getKey();
        var value = entry.getValue();
        if ((key == null) || (value == null)) {
          return false;
        }
        V cachedValue = AsMapView.this.get(key);
        return (cachedValue != null) && cachedValue.equals(value);
      }

      @Override
      public boolean removeAll(Collection collection) {
        requireNonNull(collection);
        boolean modified = false;
        if ((collection instanceof Set) && (collection.size() > size())) {
          for (var entry : this) {
            if (collection.contains(entry)) {
              modified |= remove(entry);
            }
          }
        } else {
          for (var o : collection) {
            modified |= remove(o);
          }
        }
        return modified;
      }

      @Override
      public boolean remove(Object obj) {
        if (!(obj instanceof Entry)) {
          return false;
        }
        Entry entry = (Entry) obj;
        var key = entry.getKey();
        return (key != null) && AsMapView.this.remove(key, entry.getValue());
      }

      @Override
      public boolean removeIf(Predicate> filter) {
        requireNonNull(filter);
        boolean modified = false;
        for (Entry entry : this) {
          if (filter.test(entry)) {
            modified |= AsMapView.this.remove(entry.getKey(), entry.getValue());
          }
        }
        return modified;
      }

      @Override
      public boolean retainAll(Collection collection) {
        requireNonNull(collection);
        boolean modified = false;
        for (var entry : this) {
          if (!collection.contains(entry) && remove(entry)) {
            modified = true;
          }
        }
        return modified;
      }

      @Override
      public Iterator> iterator() {
        return new EntryIterator();
      }
    }

    private final class EntryIterator implements Iterator> {
      final Iterator>> iterator;
      @Nullable Entry cursor;
      @Nullable K removalKey;

      EntryIterator() {
        iterator = delegate.entrySet().iterator();
      }

      @Override
      public boolean hasNext() {
        while ((cursor == null) && iterator.hasNext()) {
          Entry> entry = iterator.next();
          V value = Async.getIfReady(entry.getValue());
          if (value != null) {
            cursor = new WriteThroughEntry<>(AsMapView.this, entry.getKey(), value);
          }
        }
        return (cursor != null);
      }

      @Override
      public Entry next() {
        if (!hasNext()) {
          throw new NoSuchElementException();
        }
        @SuppressWarnings("NullAway")
        K key = cursor.getKey();
        Entry entry = cursor;
        removalKey = key;
        cursor = null;
        return entry;
      }

      @Override
      public void remove() {
        requireState(removalKey != null);
        delegate.remove(removalKey);
        removalKey = null;
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy