content: /*
   * Copyright 2005 JBoss Inc
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package lambdasinaction.dsl;
  
  import lambdasinaction.chap6.Dish;
  
  import java.util.List;
  import java.util.Map;
  import java.util.function.Function;
  import java.util.stream.Collector;
  
  import static java.util.stream.Collectors.groupingBy;
  import static lambdasinaction.chap6.Dish.menu;
  import static lambdasinaction.dsl.Grouping.GroupingBuilder.groupOn;
  
  public class Grouping {
  
      enum CaloricLevel { DIET, NORMAL, FAT };
  
      public static void main(String ... args) {
          System.out.println("Dishes grouped by type and caloric level: " + groupDishedByTypeAndCaloricLevel2());
          System.out.println("Dishes grouped by type and caloric level: " + groupDishedByTypeAndCaloricLevel3());
      }
  
      private static CaloricLevel getCaloricLevel( Dish dish ) {
          if (dish.getCalories() <= 400) return CaloricLevel.DIET;
          else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
          else return CaloricLevel.FAT;
      }
  
      private static Map<Dish.Type, Map<CaloricLevel, List<Dish>>> groupDishedByTypeAndCaloricLevel2() {
          return menu.stream().collect(
                  twoLevelGroupingBy(Dish::getType, dish -> getCaloricLevel( dish ) )
                                      );
      }
  
      public static <A, B, T> Collector<T, ?, Map<A, Map<B, List<T>>>> twoLevelGroupingBy(Function<? super T, ? extends A> f1, Function<? super T, ? extends B> f2) {
          return groupingBy(f1, groupingBy(f2));
      }
  
      private static Map<Dish.Type, Map<CaloricLevel, List<Dish>>> groupDishedByTypeAndCaloricLevel3() {
          Collector<? super Dish, ?, Map<Dish.Type, Map<CaloricLevel, List<Dish>>>> c = groupOn( ( Dish dish ) -> getCaloricLevel( dish ) ).after( Dish::getType ).get();
          return menu.stream().collect( c );
      }
  
      public static class GroupingBuilder<T, D, K> {
          private final Collector<? super T, ?, Map<K, D>> collector;
  
          public GroupingBuilder( Collector<? super T, ?, Map<K, D>> collector ) {
              this.collector = collector;
          }
  
          public Collector<? super T, ?, Map<K, D>> get() {
              return collector;
          }
  
          public <J> GroupingBuilder<T, Map<K, D>, J> after(Function<? super T, ? extends J> classifier) {
              return new GroupingBuilder<>( groupingBy( classifier, collector ) );
          }
  
          public static <T, D, K> GroupingBuilder<T, List<T>, K> groupOn(Function<? super T, ? extends K> classifier) {
              return new GroupingBuilder<>( groupingBy( classifier )  );
          }
      }
  }
  