Thursday, August 31, 2017

Java 8 - CompletableFuture to do asynchromous crawlling


Asynchrnous calling is done previous in Java 7 by Future , But I think you don't want to use that if you have used that before. because it's blocking there.... So in Java 8, A more powerful utility class is invented to solve this problem. it's called CompletableFuture.



Bad way to encapsulate : below is the traditional way to write the completableFurture(cf), it will wait and complete to save value into cf and it also will handle any exception in between the calling.But This code is too long and redundant...


public Future<Double> getPriceAsyncBad(String product) {
   // create a cf,which will return the result later
CompletableFuture<Double> futurePrice = new CompletableFuture<>();
   new Thread( () -> {
    try {
 double price = calculatePrice(product); // this function will sleep for 1 second
 System.out.println("api call has finished");
 futurePrice.complete(price); // return the value to cf after a long wait, cf stops
    } catch (Exception ex) {
 futurePrice.completeExceptionally(ex); // throw exception, and stop the cf
    }
    }).start(); // asynchronous computation 
    return futurePrice;
}
 

Good way to write : In java 8, there is a clean way to write. It has threadpool + exception + complete . All in one.


//new way ,CompletableFuture factory 
public Future<Double> getPriceAsyncGood(String product) {
    return CompletableFuture.supplyAsync(() -> calculatePrice(product));
}

Main Method :


public static void main(String[] args) {
   Shopp shop = new Shopp("BestShop"); 
   Future<Double> futurePrice = shop.getPriceAsyncGood("my favorite product");
  
   System.out.println("Main Thread continue...");
  
   try {
 double price = futurePrice.get(); 
        // conjested and waiting for future, like a semophore!! code can't continue
 System.out.println(price);
   } catch (Exception e) {}
}

Main Thread continue...
api call has finished
142.40742856631343


Alright , This is one small example showcasing a crawler fetching one product price asynchronously from one shop. what if it's from multiple shops . for example, I want to know what's the price of Iphone8  in different shops .


List<CompletableFuture<String>> priceFutures = shops.stream()
          .map(shop -> CompletableFuture.supplyAsync
                    () -> String.format("%s price is %.2f", shop.getName(),
                                                            shop.getPrice(product))))
          .collect(Collectors.toList());

But this return List<CompletableFuture<String>> which is not what we want. we want List<String> join() will get the result from completableFuture == get(), the only difference is join() won't throw any exception. 




public List<String> findPrices(String product) {
   List<CompletableFuture<String>> priceFutures = shops.stream()
        .map(shop -> CompletableFuture.supplyAsync(() -> shop.getName() + " price is " +
                                                         shop.getPrice(product)))
        .collect(Collectors.toList());
  return priceFutures.stream()
                     .map(CompletableFuture::join)  //wait all processes to finish and get
                     .collect(toList());
}

Ok, It seems perfect for our application. because it fulltils our target. However if you do it in this way, it will be very slow. Becuase internally , its threadpool size is preconfigured commonly which is not optimised for different cases. So Let's configure our threadpool size to speed it up.


// optimised threadpool
// threadpool = the smaller between shop size and 100.
Executors.newFixedThreadPool(Math.min(shopp.size(), 100), new ThreadFactory() { public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setDaemon(true); // use deamon, it won't hinder the application. return t; } });

Use it


CompletableFuture.supplyAsync(() -> shop.getName() + " price is " +
                                    shop.getPrice(product), executor);


Ok,Let's see an exmaple how CompletableFuture process a series of  job flows using thenApply() and thenCompose() :


private final Executor executor = public List<String> findPrices(String product) {
   List<CompletableFuture<String>> priceFutures = shops.stream()
   // asyn to get price
.map(shop -> CompletableFuture.supplyAsync(() -> shop.getPrice(product), executor)) 
    .map(future -> future.thenApply(Quote::parse)) // parse the quote
    .map(future -> future.thenCompose(quote -> 
    // first cf's result as second cf's input in thenCompose
 CompletableFuture.supplyAsync(() -> Discount.applyDiscount(quote), executor))) 
         // asyn to apply quote
   
    .collect(Collectors.toList());
return priceFutures.stream().map(CompletableFuture::join).collect(Collectors.toList());
    
}

you may notice, thenCompose will take the first cf's output as the second cf's input. in other word, these 2 cfs are linked and connected in sequence. So what if our 2 cfs has no relationship , so we purely want 2 unrelated thread to run . then we can use thenCombine()


Future<Double> futurePriceInUSD =
CompletableFuture.supplyAsync(() -> shop.getPrice(product))
                   .thenCombine(
                        CompletableFuture.supplyAsync(
                            () -> exchangeService.getRate(Money.EUR, Money.USD)),
                        (price, rate) -> price * rate
);

Ok, Now, the requirement may change, you are crawlling prices from different websites. you want 2 scenarios one is when all is ready then return . one is any one has value to return , then return. so how to implement this 2 scenarios? we use anyOf() and allOf() , but we must convert into cf array first.


CompletableFuture[] futures = findPricesStream("myPhone")  // convert into cf array  .map(f -> f.thenAccept(System.out::println))
  .toArray(size -> new CompletableFuture[size]);
  
// all threads of cf have return value . then join and return
CompletableFuture.allOf(futures).join();
// any threads of cf have return value . then join and return
CompletableFuture.anyOf(futures).join();  

Ok, if your last cf have no return <Void>, and you still want to do some post processing like : printing. you can use thenApply()


findPricesStream("myPhone").map(f -> f.thenAccept(System.out::println));

That's all.

Wednesday, August 30, 2017

Java 8 - Restructure legacy code


How do we convert the ledgacy code into Lamda Style ?  In Java 8, you may encounter that one function can be done in Java 8 with multiple approaches , So which implementation shall we choose ?



                   Restructure 1 - Anonymous Class to Lamda 

// convert from anonymous class into lamda
Runnable r2 = (Runnable) () -> System.out.println("Hello");

Restructure 2 - Use predefined implementations


// Don't use
int totalCalories = menu.stream().map(Dish::getCalories).reduce(0, (c1, c2) -> c1 + c2);
// recommend 
int totalCalories = menu.stream().collect(Collectors.summingInt(Dish::getCalories));    
// Don't use
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
// recommend 
inventory.sort(comparing(Apple::getWeight));

Restructure 3 - Use Stream 


menu.parallelStream().filter(d -> d.getCalories() > 300).map(Dish::getName)
                     .collect(toList());

The code above is a transfer from normal collection loop into Stream way , However, In our real work, This kind of code is hard to be implemented. as In the collection loopping, we may use break, continue or return , situations are complex . Now, this issue can be solved by some third party library


Restructure 4 - short & frequent data retrival 



public static void main(String[] args) {
   // generateDiagnostic() is frequently triggered
   log(Level.FINER, () -> "Problem: " + generateDiagnostic());
}
 
public static void log(Level level, Supplier<String> msgSupplier) {
 //...
}
 
public static String generateDiagnostic() { return "ok"; }

Restructure 5 - Strategy Pattern


The above Image is the old Strategy Pattern Demo , Now, We don't need it any more and we don't need to create extra classes to implement an interface but can declare and use strategy directly.


// Strategy A
Validator numericValidator = new Validator((String s) -> s.matches("[a-z]+"));
boolean b1 = numericValidator.validate("aaaa");
  
// Strategy B
Validator lowerCaseValidator = new Validator((String s) -> s.matches("\\d+"));
boolean b2 = lowerCaseValidator.validate("bbbb");

Restructure 6 - Template Pattern


The above illustration is a normal template pattern , well what if you need another template with just a samll difference with original template, in Java 7, You need to recreate a new template , In Java 8, It's very simple & dynamic.


public abstract class OnlineBanking {
   // overrall template
   public void processCustomer(int id, Consumer<Customer> makeCustomerHappyBehaviours) {
 Customer c = DataBase.getCustomerWithId(id);
 makeCustomerHappyBehaviours.accept(c); // can accept differnt behaviours
   }
}


public class BankOfTokoyOnlineBanking extends OnlineBanking {
        // bank of tokoy's template
public void myTemplate() { new BankOfTokoyOnlineBanking().processCustomer(1337, 
                       (Customer c) -> System.out.println("Hello " + c.getName()));
 }
}

Restructure  7 - Chain Of Responsibility

This is so StraitForward with the help of Function interface discussed in previous article. 


public String processLineA() {
     UnaryOperator<String> headerProcessing = (String text) -> "From Raoul Alan: " + text;
    
     UnaryOperator<String> spellCheckerProcessing = (String text) -> 
                                                     text.replaceAll("labda", "lambda");
    
     Function<String, String> pipeline = headerProcessing.andThen(spellCheckerProcessing);
    
     String result = pipeline.apply("sexy?!!");
     return result;
}

Restructure 8 - Factory Pattern

we have discussed this previously in method reference.


final static Map<String, Supplier<Product>> map = new HashMap<>();
static {
 map.put("loan", Loan::new);
 map.put("stock", Stock::new);
 map.put("bond", Bond::new);
}
 
public static Product createProduct(String name){
 Supplier<Product> p = map.get(name);
 if(p != null)  {
      return p.get();
 }
 throw new IllegalArgumentException("No such product " + name);
}

Restructure 9 - Debug

Lamda's Stream is a flow/pipleline process. then How do we debug them step by step like what we did previously adding those break point in application ? We use peek()


List<Integer> result = numbers.stream()
  .peek(x -> System.out.println("from stream: " + x))
  .map(x -> x + 17)
  .peek(x -> System.out.println("after map: " + x))
  .filter(x -> x % 2 == 0)
  .peek(x -> System.out.println("after filter: " + x))
  .limit(3)
  .peek(x -> System.out.println("after limit: " + x))
  .collect(toList());

from stream: 3
after map: 20
after filter: 20
after limit: 20
from stream: 4
after map: 21
from stream: 5
after map: 22
after filter: 22
after limit: 22



Tuesday, August 29, 2017

Java 8 - Stream

Stream in java 8 is just a interface to better manipulate the collection in a flow /Streamline way , let's see some demos .


Integration Demo 1 - Basics


public static void main(String[] args) {
  
 List<Dish> menu = new ArrayList<>();
 List<Dish> dishes = menu.stream() // convert into Stream Object
    .filter(d -> d.getCalories() > 300) // == if statement
    .limit(3) // == limit the result size
    .skip(2)  // == abandon the first 2 result
    .sorted(...) // sort by asc, can pass a Comparator as well
    .collect(Collectors.toList()); // convert back to list 
    
}

Integration Demo 2 - Map

Map function is to implement such and such a method upon each element in collection.


List<Dish> menu = new ArrayList<>();
List<Integer> dishNameLengths = menu.stream()
    .map(Dish::getName) // retrive each element's name
    .map(String::length) // further retrive each name's length
    .distinct()
    .collect(Collectors.toList());

flatMap function is to implement such and such a method on each element, then combine multiple Stream into one Stream.


List<String> words = new ArrayList<>();
List<String> uniqueCharacters = words.stream()
   .map(w -> w.split("")) // convert into multple Stream<String[]>
   .flatMap(Arrays::stream) // combine into one Stream<String>
   .distinct()
   .collect(Collectors.toList());

Integration Demo 3 - Match & Find

Match & Find is implemented in Java 7 using regular expression, In Java 8 , it gives you more flexbile way to do it . In Stream API, the following functions are provided :

                          allMatch、anyMatch、noneMatch、findFirst  and findAny


public static void main(String[] args) {
 List<Dish> menu = new ArrayList<>();
 // match
 if (menu.stream().anyMatch(Dish::isVegetarian)) { // at least one matched
      System.out.println("matched");
 }
    
 if (menu.stream().allMatch(d -> d.getCalories() < 1000)) { // all matched
      System.out.println("matched");
 }
 if (menu.stream().noneMatch(d -> d.getCalories() >= 1000)) { // none matched
      System.out.println("matched");
 }
}

Optional<T> interface is the return type of Java 8 Match & Find, Becuase If none is found , It may throw NullPointerException,So this Interface is invented to avoid that . It has 4 inherited methods

  • isPresent()  , if something is returned, return true, if nothing is returned, return false. 
  • ifPresent(Consumer<T> block), if somehting is returned, then run a code block.
  • T get(), if something is retured, then get the returned value, else throw NoSuchElementExceptio
  • T orElse(T other), if nothing is returned, it can set a default value and return back. 


Optional<Dish> dish = menu.stream()
  .filter(Dish::isVegetarian)
  .findAny();
  
menu.stream().filter(Dish::isVegetarian)
         .findAny()
         .ifPresent(d -> System.out.println(d.getName())); // if something is returned, 
                                                              then print 
   
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstSquareDivisibleByThree = someNumbers.stream()
   .map(x -> x * x) // inner product of each element
   .filter(x -> x % 3 == 0)
   .findFirst(); // 9

Integration Demo 4 - Reduce

What's reduce? well , you may hear of mapreduce, reducing is a process of summarizing the result or final math computation.


public static void main(String[] args) {
 List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
  
 int sum = numbers.stream().reduce(0, (a, b) -> a + b); // get the sum
  
 int product = numbers.stream().reduce(1, (a, b) -> a * b); // get the product
  
 Optional<Integer> max = numbers.stream().reduce(Integer::max); // get the max
  
 Optional<Integer> min = numbers.stream().reduce(Integer::min); // get the min 
  
 List<Dish> menu = new ArrayList<>();
 int count = menu.stream()
  .map(d -> 1)
  .reduce(0, (a, b) -> a + b); // count the total , using mapreduce
}

Integration Demo 6 - Number Steam

Stream is not always the boss , sometimes , when it convert into some primitive types to facilitate some computations , it may face autoboxing and unboxing issues, thus , some his little brothers are invented. IntStream、DoubleStream & LongStream


public static void main(String[] args) {
 List<Dish> menu  = new ArrayList<>();
 int calories = menu.stream()
  .mapToInt(Dish::getCalories) // convert Stream into IntStream
  .sum();
  
 IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
 Stream<Integer> stream = intStream.boxed(); // upgrade IntStream back to Stream
   
 OptionalInt maxCalories = menu.stream()
   .mapToInt(Dish::getCalories)
   .max(); // get the max
 int max = maxCalories.orElse(1); // if no max value, then return 1 as default
  
  
 IntStream evenNumbers = IntStream.rangeClosed(1, 100) // do between 1~100
    .filter(n -> n % 2 == 0);
 System.out.println(evenNumbers.count());
    
    
 Stream<int[]> pythagoreanTriples = IntStream.rangeClosed(1, 100)
                        .boxed() // upgrade to Stream
                        .flatMap(a -> IntStream.rangeClosed(a, 100)
                        .filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
                        .mapToObj(b -> new int[]{a, b, (int)Math.sqrt(a * a + b * b)})); 
                        // convert primitive type into Object
}

Integration Demo 7 - Create Steam

Stream Creation as we know is from collection. Yes, That's the commonest way. However, There're still some other ways to create a Stream which are not commonly used.
public static void main(String[] args) {
      // created from values 
      Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action"); 
  
      Stream<String> emptyStream = Stream.empty(); // create a empty Stream
  
      int[] numbers = {2, 3, 5, 7, 11, 13};
      int sum = Arrays.stream(numbers).sum(); // created from array
      
      // created from a file 
      Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset()); 
      // created from function iterate() 
      Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println); 
      
      // created from function generate()
      Stream.generate(Math::random).limit(5).forEach(System.out::println); 
  
      IntStream twos = IntStream.generate(new IntSupplier() {
                  public int getAsInt() {
                         return 2;
      }}); // created from code block   
 }


Well , That's all about the guide of Stream. In Next article , I will demostrate how to make use of different Streams to encapsulate a powerful application.


Monday, August 28, 2017

Java 8 - Compound encapsulation


In previous articles , we know how to write method references and different method interfaces. Well , Those stuffs are only a substitute of java 7 syntax, it seems that doesn't bring much of convenience. 

In this article , I will make use of what we learn about method reference and method interfaces before and talk about how we use it in our real project scenarios.


Compound Comparator

In java 7 , if we want to sort a collection, we can't do so much but write a anonymous class of comparator interface and this can only compare once. like :

Comparator<String> com = new Comparator<String>() {
       @Override
       public int compare(String o1, String o2) {
          return 0;
 }
};

In java 8, you not only can encapsulate the comparator but can cascade the comparsion as well :


List<Apple> inventory = new ArrayList<>();

// if  ur comparator is complex, pick it out to encapsulate  
Comparator<Apple> c = Comparator.comparing(Apple::getWeight);

// if ur comparator is a reverse order of weight   
inventory.sort( Comparator.comparing(Apple::getWeight).reversed() );
  
// if u encounter same weight , u want to sort in country asc order again.
inventory.sort( Comparator.comparing(Apple::getWeight)
                          .reversed()
                          .thenComparing(Apple::getCountry));

Compound Predicate<T>

Predicate as we all know is a boolean judgement tool in previous article. Normally, we use boolean in if statement with lots of conditions :


if (apple.getWeight() > 120 && apple.getWeight() < 140 
                                                   || apple.getCountry().contains("US")) {
    ...
}

In Java 8 , it can fulfill all your requirements :


public static void main(String[] args) {
  
   // whether it is red apple.
   Predicate<Apple> redApple = Apple::getRedApple;
  
   // whether it is not red apple,  // math negation
   Predicate<Apple> notRedApple = redApple.negate();
  
   // whether it is red and heavy apple,  // math conjunction
   Predicate<Apple> redAndHeavyApple = redApple.and(a -> a.getWeight() > 150);
  
   // whether it is red and heavy or green apple, // math conjunction and disjunction 
   Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(a -> a.getWeight() > 150)                                                    .or(a -> "green".equals(a.getColor()));
}

Note: and and or follow order from left to right , so a.or(b).and(c)  equals (a || b) && c

Compound Function<F,T> 

In previous article , we know that Function interface is used to retrive something from an Object. So of course, we can do some cascading manipulations like : retrive a from b after retrival  & validation from c. it has 2 default methods : andThen and compose.

This is call pipeline , or assembly line or flow line production.

public static void main(String[] args) {
  Function<Integer, Integer> f = x -> x + 1;
  Function<Integer, Integer> g = x -> x * 2;
  Function<Integer, Integer> h = f.andThen(g); // g(f(x)), do f first and g second
  int result = h.apply(1); // return 4 
}


public static void main(String[] args) {
   Function<Integer, Integer> f = x -> x + 1;
   Function<Integer, Integer> g = x -> x * 2;
   Function<Integer, Integer> h = f.compose(g); // f(g(x)), do g first f second
   int result = h.apply(1); // return 3
}

Alright , ok you may realise it is so simple , because in real project, we don't do math computation at all, the example below is how to use it in real project :

public class Letter {
  public static String addHeader(String text){
     return "From Raoul, Mario and Alan: " + text;
  }
  public static String addFooter(String text){
     return text + " Kind regards";
  }
  public static String checkSpelling(String text){
     return text.replaceAll("labda", "lambda");
  }
} 

Ok the above is the letter utility class, let's create our own  first flow line:


Function<String, String> addHeader = Letter::addHeader;
Function<String, String> transformationPipeline = addHeader.andThen(Letter::checkSpelling)
                                                           .andThen(Letter::addFooter);

Ok , we may need another flow line only needing addHeader and footer but without checkSpelling:


Function<String, String> addHeader = Letter::addHeader;
Function<String, String> transformationPipeline = addHeader.andThen(Letter::addFooter);

Conclusion 

From this article , you may find  lamda expression give much of help on the encapsulation of the flow and procedure, you may find it useful or useless, but try to approach the lamda way of encapsulation in your work ,  gradually you will realise the convenience and like it .

It is a process of thinking reformation and it need some time to practice.

Add Loading Spinner for web request.

when web page is busily loading. normally we need to add a spinner for the user to kill their waiting impatience. Here, 2 steps we need to d...