public static <T> CompletionStage<Collection<T>> flattenFutures(
      Collection<CompletionStage<T>> inputFutures) {
    CompletableFuture<T>[] futures = inputFutures.toArray(new CompletableFuture[0]);

    return CompletableFuture.allOf(futures)
        .thenApply(
            ignored -> {
              final List<T> result =
                  Stream.of(futures).map(CompletableFuture::join).collect(Collectors.toList());
              return result;
            });
  }
@Test
  public void testWaitForAllFutures() {
    CountDownLatch latch = new CountDownLatch(1);
    CompletionStage<Collection<String>> resultFuture =
        FutureUtils.flattenFutures(
            ImmutableList.of(
                CompletableFuture.supplyAsync(
                    () -> {
                      try {
                        latch.await();
                      } catch (InterruptedException e) {
                        return "";
                      }

                      return "hello";
                    }),
                CompletableFuture.supplyAsync(
                    () -> {
                      latch.countDown();
                      return "world";
                    })));

    CompletionStage<Void> validationFuture =
        resultFuture.thenAccept(
            actualResults -> {
              Assert.assertEquals(
                  "Expected flattened results to contain {hello, world}", RESULTS, actualResults);
            });

    validationFuture.toCompletableFuture().join();
  }