flying3615
8/18/2017 - 11:36 AM

List input to Completable future join result

List input to Completable future join result

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.stream.Collectors;


public class CompletableFutureTest {

    public static void main(String[] args) {
        List<Shop> shops = Arrays.asList(
                new Shop("New Save"),
                new Shop("Pak n Save"),
                new Shop("New World"),
                new Shop("PB"),
                new Shop("Norman"),
                new Shop("Countdown")
        );

        long start = System.nanoTime();
        System.out.println(findPricesAsyn("IPhone7", shops));
        long duration = (System.nanoTime() - start) / 1_000_000;
        System.out.println("Done in completable future " + duration + " msecs");


        start = System.nanoTime();
        System.out.println(findPricesSync("IPhone6", shops));
        duration = (System.nanoTime() - start) / 1_000_000;
        System.out.println("Done in parallel stream " + duration + " msecs");

    }


    /**
     * Note that you use two separate stream pipelines,
     * instead of putting the two map operations one after the other in the same stream-processing pipeline—and for a very good reason.
     * Given the lazy nature of intermediate stream operations,
     * if you had processed the stream in a single pipeline, you would have succeeded only in executing all the requests to different shops synchronously and sequentially.
     * This is because the creation of each CompletableFuture to interrogate a given shop would start only when the computation of the previous one had completed,
     * letting the join method return the result of that computation.
     *
     * @param product
     * @param shops
     * @return
     */
    public static List<String> findPricesAsyn(String product, List<Shop> shops) {

//      more flexible than parallel as it allows you to specify a different Executor to submit their tasks to
        List<CompletableFuture<String>> priceFutures = shops.stream()
                .map(shop -> CompletableFuture.supplyAsync(
                        () -> shop.getPrice(product)))
                .map(future -> future.thenApply(Quote::parse))
                .map(future -> future.thenCompose(quote ->
                        CompletableFuture.supplyAsync(() -> //create another future to be composed...
                                Discount.applyDiscount(quote))))
                .collect(Collectors.toList());

        return priceFutures.stream().map(CompletableFuture::join).collect(Collectors.toList());
    }


    public static List<String> findPricesSync(String product, List<Shop> shops) {
        return shops.stream()
                .map(shop -> shop.getPrice(product))
                .map(Quote::parse)
                .map(Discount::applyDiscount)
                .collect(Collectors.toList());
    }


//    public static List<String> findPricesParall(String product, List<Shop> shops) {
//        return shops.parallelStream()
//                .map(shop -> shop.getPrice(product))
//                .collect(Collectors.toList());
//    }

}


class Shop {

    private String name;

    Random random = new Random();

    public Shop(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public static void delay() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private double calculatePrice(String product) {
        delay();
        return random.nextDouble() * product.charAt(0) + product.charAt(1);
    }

    public String getPrice(String product) {
        double price = calculatePrice(product);
        Discount.Code code = Discount.Code.values()[random.nextInt(Discount.Code.values().length)];
        return String.format("%s:%.2f:%s", name, price, code);
    }

    public Future<Double> getPriceAsync(String product) {
        return CompletableFuture.supplyAsync(() -> calculatePrice(product));
    }
}

class Quote {
    private final String shopName;
    private double price;
    private final Discount.Code discountCode;


    public Quote(String shopName, double price, Discount.Code discountCode) {
        this.shopName = shopName;
        this.price = price;
        this.discountCode = discountCode;
    }

    public static Quote parse(String s) {
        String[] split = s.split(":");
        String shopName = split[0];
        double price = Double.parseDouble(split[1]);
        Discount.Code discountCode = Discount.Code.valueOf(split[2]);
        return new Quote(shopName, price, discountCode);
    }

    public String getShopName() {
        return shopName;
    }

    public double getPrice() {
        return price;
    }

    public Discount.Code getDiscountCode() {
        return discountCode;
    }
}

class Discount {
    public enum Code {
        A(10), B(20), C(30);
        int percentage;

        Code(int percentage) {
            this.percentage = percentage;
        }

        int getPercentage() {
            return percentage;
        }

    }

    public static String applyDiscount(Quote quote) {
        return quote.getShopName() + " price is " + Discount.apply(quote.getPrice(), quote.getDiscountCode());
    }

    public static void delay() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static double apply(double price, Code discountCode) {
        delay();
        return price * (100 - discountCode.getPercentage() / 100);
    }
}