CollectingIterable.java

  1. package no.motif.iter;

  2. import static java.util.Collections.sort;
  3. import static java.util.Collections.unmodifiableList;
  4. import static no.motif.Base.toString;
  5. import static no.motif.Iterate.by;
  6. import static no.motif.Singular.optional;
  7. import static no.motif.Strings.concat;

  8. import java.io.Serializable;
  9. import java.util.ArrayList;
  10. import java.util.Collection;
  11. import java.util.Comparator;
  12. import java.util.LinkedHashMap;
  13. import java.util.List;
  14. import java.util.Map;

  15. import no.motif.Singular;
  16. import no.motif.Strings;
  17. import no.motif.f.Do;
  18. import no.motif.f.Fn;
  19. import no.motif.f.Fn2;
  20. import no.motif.f.Predicate;
  21. import no.motif.single.Optional;
  22. import no.motif.types.Elements;

  23. /**
  24.  * This iterable offers operations which requires collecting (i.e.
  25.  * iterating) the containing elements of the iterable. In
  26.  * particular, this class holds the methods which "bridges" back
  27.  * to the non-lazy Java Collection Framework classes.
  28.  *
  29.  * @param <T> The type of elements in this iterable.
  30.  */
  31. abstract class CollectingIterable<T> implements Elements<T>, Serializable {

  32.     @Override
  33.     public final <O> O reduce(O unit, Fn2<? super O, ? super T, ? extends O> reducer) {
  34.         O reduced = unit;
  35.         for (T element : this) reduced = reducer.$(reduced, element);
  36.         return reduced;
  37.     }


  38.     @Override
  39.     public final List<T> collect() {
  40.         return unmodifiableList(collectIn(new ArrayList<T>()));
  41.     }


  42.     @Override
  43.     public final <C extends Collection<T>> C collectIn(C collection) {
  44.         for (T t : this) {
  45.             collection.add(t);
  46.         }
  47.         return collection;
  48.     }



  49.     @Override
  50.     public final <P extends Comparable<P>> List<T> sortedBy(Fn<? super T, P> property) {
  51.         return sorted(by(property));
  52.     }



  53.     @Override
  54.     public final List<T> sorted(Comparator<? super T> comparator) {
  55.         List<T> elements = collectIn(new ArrayList<T>());
  56.         sort(elements, comparator);
  57.         return unmodifiableList(elements);
  58.     }


  59.     @Override
  60.     public void each(Do<? super T> sideEffect) {
  61.         for (T element : this) sideEffect.with(element);
  62.     }


  63.     @Override
  64.     public <P> Map<P, List<T>> groupBy(Fn<? super T, P> property) {
  65.         Map<P, List<T>> map = new LinkedHashMap<>();
  66.         for (T elem : this) {
  67.             P key = property.$(elem);
  68.             List<T> list;
  69.             if (!map.containsKey(key)) {
  70.                 list = new ArrayList<>();
  71.                 map.put(key, list);
  72.             } else {
  73.                 list = map.get(key);
  74.             }
  75.             list.add(elem);
  76.         }
  77.         return map;
  78.     }


  79.     @Override
  80.     public <P> Map<P, T> mapBy(Fn<? super T, P> uniqueProperty) {
  81.         Map<P, T> map = new LinkedHashMap<>();
  82.         for (T elem : this) {
  83.             P key = uniqueProperty.$(elem);
  84.             if (!map.containsKey(key)) map.put(key, elem);
  85.             else throw new IllegalStateException(
  86.                     "Cannot create the map since both '" + map.get(key) + "' and '" + elem + "' yields " +
  87.                     "the same key: '" + key + "'. This is either due to an inconsistency in " +
  88.                     "your data, or you should use .groupBy(Fn) instead to allow multiple elements " +
  89.                     "being mapped by the same key.");
  90.         }
  91.         return map;
  92.     }


  93.     @Override
  94.     public boolean isEmpty() {
  95.         return !iterator().hasNext();
  96.     }


  97.     @Override
  98.     public boolean exists(Predicate<? super T> predicate) {
  99.         for (T t : this) if (predicate.$(t)) return true;
  100.         return false;
  101.     }


  102.     @Override
  103.     public Optional<T> head() {
  104.         return !isEmpty() ? optional(iterator().next()) : Singular.<T>none();
  105.     }


  106.     @Override
  107.     public Optional<T> last() {
  108.         T last = null;
  109.         for (T elem : this) last = elem;
  110.         return optional(last);
  111.     }


  112.     @Override
  113.     public String join() {
  114.         return reduce("", concat);
  115.     }


  116.     @Override
  117.     public String join(String separator) {
  118.         if (isEmpty()) return "";
  119.         return head().map(toString).append(tail().map(Strings.prepend(separator))).reduce("", concat);
  120.     }


  121.     /**
  122.      * Textual description of the contents of the iterable. Have in mind that for lazy
  123.      * implementations, using {@link #toString()} will iterate over the elements to
  124.      * actually be able to create the description.
  125.      */
  126.     @Override
  127.     public String toString() {
  128.         return collect().toString();
  129.     }

  130. }