// Path: src/main/java/org/dataloader/impl/CompletableFutureKit.java
// @Internal
// public class CompletableFutureKit {
// 
//     public static <V> CompletableFuture<V> failedFuture(Exception e) {
//         CompletableFuture<V> future = new CompletableFuture<>();
//         future.completeExceptionally(e);
//         return future;
//     }
// 
//     public static <V> Throwable cause(CompletableFuture<V> completableFuture) {
//         if (!completableFuture.isCompletedExceptionally()) {
//             return null;
//         }
//         try {
//             completableFuture.get();
//             return null;
//         } catch (InterruptedException e) {
//             Thread.currentThread().interrupt();
//             return e;
//         } catch (ExecutionException e) {
//             Throwable cause = e.getCause();
//             if (cause != null) {
//                 return cause;
//             }
//             return e;
//         }
//     }
// 
//     public static <V> boolean succeeded(CompletableFuture<V> future) {
//         return future.isDone() && !future.isCompletedExceptionally();
//     }
// 
//     public static <V> boolean failed(CompletableFuture<V> future) {
//         return future.isDone() && future.isCompletedExceptionally();
//     }
// 
//     public static <T> CompletableFuture<List<T>> allOf(List<CompletableFuture<T>> cfs) {
//         return CompletableFuture.allOf(cfs.toArray(new CompletableFuture[0]))
//                 .thenApply(v -> cfs.stream()
//                         .map(CompletableFuture::join)
//                         .collect(toList())
//                 );
//     }
// }
// 
// Path: src/main/java/org/dataloader/stats/StatisticsCollector.java
// @PublicSpi
// public interface StatisticsCollector {
// 
//     /**
//      * Called to increment the number of loads
//      *
//      * @return the current value after increment
//      */
//     long incrementLoadCount();
// 
//     /**
//      * Called to increment the number of loads that resulted in an object deemed in error
//      *
//      * @return the current value after increment
//      */
//     long incrementLoadErrorCount();
// 
//     /**
//      * Called to increment the number of batch loads
//      *
//      * @param delta how much to add to the count
//      *
//      * @return the current value after increment
//      */
//     long incrementBatchLoadCountBy(long delta);
// 
//     /**
//      * Called to increment the number of batch loads exceptions
//      *
//      * @return the current value after increment
//      */
//     long incrementBatchLoadExceptionCount();
// 
//     /**
//      * Called to increment the number of cache hits
//      *
//      * @return the current value after increment
//      */
//     long incrementCacheHitCount();
// 
//     /**
//      * @return the statistics that have been gathered up to this point in time
//      */
//     Statistics getStatistics();
// }
// 
// Path: src/main/java/org/dataloader/impl/Assertions.java
// public static void assertState(boolean state, Supplier<String> message) {
//     if (!state) {
//         throw new DataLoaderAssertionException(message.get());
//     }
// }
// 
// Path: src/main/java/org/dataloader/impl/Assertions.java
// public static <T> T nonNull(T t) {
//     return nonNull(t, () -> "nonNull object required");
// }


import org.dataloader.annotations.GuardedBy;
import org.dataloader.annotations.Internal;
import org.dataloader.impl.CompletableFutureKit;
import org.dataloader.stats.StatisticsCollector;
import java.time.Clock;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.concurrent.CompletableFuture.allOf;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static java.util.stream.Collectors.toList;
import static org.dataloader.impl.Assertions.assertState;
import static org.dataloader.impl.Assertions.nonNull;
                     Object batchLoadFunction,
                     DataLoaderOptions loaderOptions,
                     CacheMap<Object, V> futureCache,
                     ValueCache<K, V> valueCache,
                     StatisticsCollector stats,
                     Clock clock) {
        this.dataLoader = dataLoader;
        this.batchLoadFunction = batchLoadFunction;
        this.loaderOptions = loaderOptions;
        this.futureCache = futureCache;
        this.valueCache = valueCache;
        this.loaderQueue = new ArrayList<>();
        this.stats = stats;
        this.clock = clock;
        this.lastDispatchTime = new AtomicReference<>();
        this.lastDispatchTime.set(now());
    }

    Instant now() {
        return clock.instant();
    }

    public Instant getLastDispatchTime() {
        return lastDispatchTime.get();
    }

    Optional<CompletableFuture<V>> getIfPresent(K key) {
        synchronized (dataLoader) {
            boolean cachingEnabled = loaderOptions.cachingEnabled();
            if (cachingEnabled) {
// Path: src/main/java/org/dataloader/impl/CompletableFutureKit.java
// @Internal
// public class CompletableFutureKit {
// 
//     public static <V> CompletableFuture<V> failedFuture(Exception e) {
//         CompletableFuture<V> future = new CompletableFuture<>();
//         future.completeExceptionally(e);
//         return future;
//     }
// 
//     public static <V> Throwable cause(CompletableFuture<V> completableFuture) {
//         if (!completableFuture.isCompletedExceptionally()) {
//             return null;
//         }
//         try {
//             completableFuture.get();
//             return null;
//         } catch (InterruptedException e) {
//             Thread.currentThread().interrupt();
//             return e;
//         } catch (ExecutionException e) {
//             Throwable cause = e.getCause();
//             if (cause != null) {
//                 return cause;
//             }
//             return e;
//         }
//     }
// 
//     public static <V> boolean succeeded(CompletableFuture<V> future) {
//         return future.isDone() && !future.isCompletedExceptionally();
//     }
// 
//     public static <V> boolean failed(CompletableFuture<V> future) {
//         return future.isDone() && future.isCompletedExceptionally();
//     }
// 
//     public static <T> CompletableFuture<List<T>> allOf(List<CompletableFuture<T>> cfs) {
//         return CompletableFuture.allOf(cfs.toArray(new CompletableFuture[0]))
//                 .thenApply(v -> cfs.stream()
//                         .map(CompletableFuture::join)
//                         .collect(toList())
//                 );
//     }
// }
// 
// Path: src/main/java/org/dataloader/stats/StatisticsCollector.java
// @PublicSpi
// public interface StatisticsCollector {
// 
//     /**
//      * Called to increment the number of loads
//      *
//      * @return the current value after increment
//      */
//     long incrementLoadCount();
// 
//     /**
//      * Called to increment the number of loads that resulted in an object deemed in error
//      *
//      * @return the current value after increment
//      */
//     long incrementLoadErrorCount();
// 
//     /**
//      * Called to increment the number of batch loads
//      *
//      * @param delta how much to add to the count
//      *
//      * @return the current value after increment
//      */
//     long incrementBatchLoadCountBy(long delta);
// 
//     /**
//      * Called to increment the number of batch loads exceptions
//      *
//      * @return the current value after increment
//      */
//     long incrementBatchLoadExceptionCount();
// 
//     /**
//      * Called to increment the number of cache hits
//      *
//      * @return the current value after increment
//      */
//     long incrementCacheHitCount();
// 
//     /**
//      * @return the statistics that have been gathered up to this point in time
//      */
//     Statistics getStatistics();
// }
// 
// Path: src/main/java/org/dataloader/impl/Assertions.java
// public static void assertState(boolean state, Supplier<String> message) {
//     if (!state) {
//         throw new DataLoaderAssertionException(message.get());
//     }
// }
// 
// Path: src/main/java/org/dataloader/impl/Assertions.java
// public static <T> T nonNull(T t) {
//     return nonNull(t, () -> "nonNull object required");
// }


// Path: src/main/java/org/dataloader/DataLoaderHelper.java
import org.dataloader.annotations.GuardedBy;
import org.dataloader.annotations.Internal;
import org.dataloader.impl.CompletableFutureKit;
import org.dataloader.stats.StatisticsCollector;
import java.time.Clock;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.concurrent.CompletableFuture.allOf;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static java.util.stream.Collectors.toList;
import static org.dataloader.impl.Assertions.assertState;
import static org.dataloader.impl.Assertions.nonNull;

                     Object batchLoadFunction,
                     DataLoaderOptions loaderOptions,
                     CacheMap<Object, V> futureCache,
                     ValueCache<K, V> valueCache,
                     StatisticsCollector stats,
                     Clock clock) {
        this.dataLoader = dataLoader;
        this.batchLoadFunction = batchLoadFunction;
        this.loaderOptions = loaderOptions;
        this.futureCache = futureCache;
        this.valueCache = valueCache;
        this.loaderQueue = new ArrayList<>();
        this.stats = stats;
        this.clock = clock;
        this.lastDispatchTime = new AtomicReference<>();
        this.lastDispatchTime.set(now());
    }

    Instant now() {
        return clock.instant();
    }

    public Instant getLastDispatchTime() {
        return lastDispatchTime.get();
    }

    Optional<CompletableFuture<V>> getIfPresent(K key) {
        synchronized (dataLoader) {
            boolean cachingEnabled = loaderOptions.cachingEnabled();
            if (cachingEnabled) {
                Object cacheKey = getCacheKey(nonNull(key));