// 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;
                                future.complete(tryValue.get());
                            } else {
                                stats.incrementLoadErrorCount();
                                future.completeExceptionally(tryValue.getThrowable());
                                clearCacheKeys.add(keys.get(idx));
                            }
                        } else {
                            future.complete(value);
                        }
                    }
                    possiblyClearCacheEntriesOnExceptions(clearCacheKeys);
                    return values;
                }).exceptionally(ex -> {
                    stats.incrementBatchLoadExceptionCount();
                    if (ex instanceof CompletionException) {
                        ex = ex.getCause();
                    }
                    for (int idx = 0; idx < queuedFutures.size(); idx++) {
                        K key = keys.get(idx);
                        CompletableFuture<V> future = queuedFutures.get(idx);
                        future.completeExceptionally(ex);
                        // clear any cached view of this key because they all failed
                        dataLoader.clear(key);
                    }
                    return emptyList();
                });
    }


    private void assertResultSize(List<K> keys, List<V> values) {
// 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;

                                future.complete(tryValue.get());
                            } else {
                                stats.incrementLoadErrorCount();
                                future.completeExceptionally(tryValue.getThrowable());
                                clearCacheKeys.add(keys.get(idx));
                            }
                        } else {
                            future.complete(value);
                        }
                    }
                    possiblyClearCacheEntriesOnExceptions(clearCacheKeys);
                    return values;
                }).exceptionally(ex -> {
                    stats.incrementBatchLoadExceptionCount();
                    if (ex instanceof CompletionException) {
                        ex = ex.getCause();
                    }
                    for (int idx = 0; idx < queuedFutures.size(); idx++) {
                        K key = keys.get(idx);
                        CompletableFuture<V> future = queuedFutures.get(idx);
                        future.completeExceptionally(ex);
                        // clear any cached view of this key because they all failed
                        dataLoader.clear(key);
                    }
                    return emptyList();
                });
    }


    private void assertResultSize(List<K> keys, List<V> values) {
        assertState(keys.size() == values.size(), () -> "The size of the promised values MUST be the same size as the key list");