Java Stream How to - Create Forked Stream








The following code shows how to create Forked Stream.

Example

//revised from/*from  ww w  . jav a 2s  .c  o  m*/
//https://github.com/java8/Java8InAction

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Spliterator;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

public class Main {
  public static void main(String...args){
    Stream<Food> menuStream = Food.menu.stream();

    StreamForker.Results results = new StreamForker<Food>(menuStream)
            .fork("shortMenu", s -> s.map(Food::getName).collect(Collectors.joining(", ")))
            .fork("totalCalories", s -> s.mapToInt(Food::getCalories).sum())
            .fork("mostCaloricFood", s -> s.collect(
                    Collectors.reducing((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2))
                    .get())
            .fork("dishesByType", s -> s.collect(Collectors.groupingBy(Food::getType)))
            .getResults();

    String shortMeny = results.get("shortMenu");
    int totalCalories = results.get("totalCalories");
    Food mostCaloricFood = results.get("mostCaloricFood");
    Map<Food.Type, List<Food>> dishesByType = results.get("dishesByType");

    System.out.println("Short menu: " + shortMeny);
    System.out.println("Total calories: " + totalCalories);
    System.out.println("Most caloric dish: " + mostCaloricFood);
    System.out.println("Foodes by type: " + dishesByType);    

  }
}
class Food {

  private final String name;
  private final boolean vegetarian;
  private final int calories;
  private final Type type;

  public Food(String name, boolean vegetarian, int calories, Type type) {
      this.name = name;
      this.vegetarian = vegetarian;
      this.calories = calories;
      this.type = type;
  }

  public String getName() {
      return name;
  }

  public boolean isVegetarian() {
      return vegetarian;
  }

  public int getCalories() {
      return calories;
  }

  public Type getType() {
      return type;
  }

  public enum Type { MEAT, FISH, OTHER }

  @Override
  public String toString() {
      return name;
  }

  public static final List<Food> menu =
          Arrays.asList( new Food("pork", false, 800, Food.Type.MEAT),
                         new Food("beef", false, 700, Food.Type.MEAT),
                         new Food("chicken", false, 400, Food.Type.MEAT),
                         new Food("french fries", true, 530, Food.Type.OTHER),
                         new Food("rice", true, 350, Food.Type.OTHER),
                         new Food("season fruit", true, 120, Food.Type.OTHER),
                         new Food("pizza", true, 550, Food.Type.OTHER),
                         new Food("prawns", false, 400, Food.Type.FISH),
                         new Food("salmon", false, 450, Food.Type.FISH));
}


/**
 * Adapted from http://mail.openjdk.java.net/pipermail/lambda-dev/2013-November/011516.html
 */
 class StreamForker<T> {

    private final Stream<T> stream;
    private final Map<Object, Function<Stream<T>, ?>> forks = new HashMap<>();

    public StreamForker(Stream<T> stream) {
        this.stream = stream;
    }

    public StreamForker<T> fork(Object key, Function<Stream<T>, ?> f) {
        forks.put(key, f);
        return this;
    }

    public Results getResults() {
        ForkingStreamConsumer<T> consumer = build();
        try {
            stream.sequential().forEach(consumer);
        } finally {
            consumer.finish();
        }
        return consumer;
    }

    private ForkingStreamConsumer<T> build() {
        List<BlockingQueue<T>> queues = new ArrayList<>();

        Map<Object, Future<?>> actions =
                forks.entrySet().stream().reduce(
                        new HashMap<Object, Future<?>>(),
                        (map, e) -> {
                            map.put(e.getKey(),
                                    getOperationResult(queues, e.getValue()));
                            return map;
                        },
                        (m1, m2) -> {
                            m1.putAll(m2);
                            return m1;
                        });

        return new ForkingStreamConsumer<>(queues, actions);
    }

    private Future<?> getOperationResult(List<BlockingQueue<T>> queues, Function<Stream<T>, ?> f) {
        BlockingQueue<T> queue = new LinkedBlockingQueue<>();
        queues.add(queue);
        Spliterator<T> spliterator = new BlockingQueueSpliterator<>(queue);
        Stream<T> source = StreamSupport.stream(spliterator, false);
        return CompletableFuture.supplyAsync( () -> f.apply(source) );
    }

    public static interface Results {
        public <R> R get(Object key);
    }

    private static class ForkingStreamConsumer<T> implements Consumer<T>, Results {
        static final Object END_OF_STREAM = new Object();

        private final List<BlockingQueue<T>> queues;
        private final Map<Object, Future<?>> actions;

        ForkingStreamConsumer(List<BlockingQueue<T>> queues, Map<Object, Future<?>> actions) {
            this.queues = queues;
            this.actions = actions;
        }

        @Override
        public void accept(T t) {
            queues.forEach(q -> q.add(t));
        }

        @Override
        public <R> R get(Object key) {
            try {
                return ((Future<R>) actions.get(key)).get();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        void finish() {
            accept((T) END_OF_STREAM);
        }
    }

    private static class BlockingQueueSpliterator<T> implements Spliterator<T> {
        private final BlockingQueue<T> q;

        BlockingQueueSpliterator(BlockingQueue<T> q) {
            this.q = q;
        }

        @Override
        public boolean tryAdvance(Consumer<? super T> action) {
            T t;
            while (true) {
                try {
                    t = q.take();
                    break;
                }
                catch (InterruptedException e) {
                }
            }

            if (t != ForkingStreamConsumer.END_OF_STREAM) {
                action.accept(t);
                return true;
            }

            return false;
        }

        @Override
        public Spliterator<T> trySplit() {
            return null;
        }

        @Override
        public long estimateSize() {
            return 0;
        }

        @Override
        public int characteristics() {
            return 0;
        }
    }
}

The code above generates the following result.