content: package lambdasinaction.chap11.v1;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  import java.util.concurrent.Callable;
  import java.util.concurrent.CompletableFuture;
  import java.util.concurrent.ExecutionException;
  import java.util.concurrent.Executor;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
  import java.util.concurrent.Future;
  import java.util.concurrent.ThreadFactory;
  import java.util.stream.Collectors;
  import java.util.stream.Stream;
  
  import lambdasinaction.chap11.ExchangeService;
  import lambdasinaction.chap11.ExchangeService.Money;
  
  public class BestPriceFinder {
  
      private final List<Shop> shops = Arrays.asList(new Shop("BestPrice"),
                                                     new Shop("LetsSaveBig"),
                                                     new Shop("MyFavoriteShop"),
                                                     new Shop("BuyItAll")/*,
                                                     new Shop("ShopEasy")*/);
  
      private final Executor executor = Executors.newFixedThreadPool(shops.size(), new ThreadFactory() {
          @Override
          public Thread newThread(Runnable r) {
              Thread t = new Thread(r);
              t.setDaemon(true);
              return t;
          }
      });
  
      public List<String> findPricesSequential(String product) {
          return shops.stream()
                  .map(shop -> shop.getName() + " price is " + shop.getPrice(product))
                  .collect(Collectors.toList());
      }
  
      public List<String> findPricesParallel(String product) {
          return shops.parallelStream()
                  .map(shop -> shop.getName() + " price is " + shop.getPrice(product))
                  .collect(Collectors.toList());
      }
  
      public List<String> findPricesFuture(String product) {
          List<CompletableFuture<String>> priceFutures =
                  shops.stream()
                  .map(shop -> CompletableFuture.supplyAsync(() -> shop.getName() + " price is "
                          + shop.getPrice(product), executor))
                  .collect(Collectors.toList());
  
          List<String> prices = priceFutures.stream()
                  .map(CompletableFuture::join)
                  .collect(Collectors.toList());
          return prices;
      }
  
      public List<String> findPricesInUSD(String product) {
          List<CompletableFuture<Double>> priceFutures = new ArrayList<>();
          for (Shop shop : shops) {
              // Start of Listing 10.20.
              // Only the type of futurePriceInUSD has been changed to
              // CompletableFuture so that it is compatible with the
              // CompletableFuture::join operation below.
              CompletableFuture<Double> futurePriceInUSD = 
                  CompletableFuture.supplyAsync(() -> shop.getPrice(product))
                  .thenCombine(
                      CompletableFuture.supplyAsync(
                          () ->  ExchangeService.getRate(Money.EUR, Money.USD)),
                      (price, rate) -> price * rate
                  );
              priceFutures.add(futurePriceInUSD);
          }
          // Drawback: The shop is not accessible anymore outside the loop,
          // so the getName() call below has been commented out.
          List<String> prices = priceFutures
                  .stream()
                  .map(CompletableFuture::join)
                  .map(price -> /*shop.getName() +*/ " price is " + price)
                  .collect(Collectors.toList());
          return prices;
      }
  
      public List<String> findPricesInUSDJava7(String product) {
          ExecutorService executor = Executors.newCachedThreadPool();
          List<Future<Double>> priceFutures = new ArrayList<>();
          for (Shop shop : shops) {
              final Future<Double> futureRate = executor.submit(new Callable<Double>() { 
                  public Double call() {
                      return ExchangeService.getRate(Money.EUR, Money.USD);
                  }
              });
              Future<Double> futurePriceInUSD = executor.submit(new Callable<Double>() { 
                  public Double call() {
                      try {
                          double priceInEUR = shop.getPrice(product);
                          return priceInEUR * futureRate.get();
                      } catch (InterruptedException | ExecutionException e) {
                          throw new RuntimeException(e.getMessage(), e);
                      }
                  }
              });
              priceFutures.add(futurePriceInUSD);
          }
          List<String> prices = new ArrayList<>();
          for (Future<Double> priceFuture : priceFutures) {
              try {
                  prices.add(/*shop.getName() +*/ " price is " + priceFuture.get());
              }
              catch (ExecutionException | InterruptedException e) {
                  e.printStackTrace();
              }
          }
          return prices;
      }
  
      public List<String> findPricesInUSD2(String product) {
          List<CompletableFuture<String>> priceFutures = new ArrayList<>();
          for (Shop shop : shops) {
              // Here, an extra operation has been added so that the shop name
              // is retrieved within the loop. As a result, we now deal with
              // CompletableFuture<String> instances.
              CompletableFuture<String> futurePriceInUSD = 
                  CompletableFuture.supplyAsync(() -> shop.getPrice(product))
                  .thenCombine(
                      CompletableFuture.supplyAsync(
                          () -> ExchangeService.getRate(Money.EUR, Money.USD)),
                      (price, rate) -> price * rate
                  ).thenApply(price -> shop.getName() + " price is " + price);
              priceFutures.add(futurePriceInUSD);
          }
          List<String> prices = priceFutures
                  .stream()
                  .map(CompletableFuture::join)
                  .collect(Collectors.toList());
          return prices;
      }
  
      public List<String> findPricesInUSD3(String product) {
          // Here, the for loop has been replaced by a mapping function...
          Stream<CompletableFuture<String>> priceFuturesStream = shops
              .stream()
              .map(shop -> CompletableFuture
                  .supplyAsync(() -> shop.getPrice(product))
                  .thenCombine(
                      CompletableFuture.supplyAsync(() -> ExchangeService.getRate(Money.EUR, Money.USD)),
                      (price, rate) -> price * rate)
                  .thenApply(price -> shop.getName() + " price is " + price));
          // However, we should gather the CompletableFutures into a List so that the asynchronous
          // operations are triggered before being "joined."
          List<CompletableFuture<String>> priceFutures = priceFuturesStream.collect(Collectors.toList());
          List<String> prices = priceFutures
              .stream()
              .map(CompletableFuture::join)
              .collect(Collectors.toList());
          return prices;
      }
  
  }
  