All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opencastproject.util.data.Collections Maven / Gradle / Ivy

There is a newer version: 16.7
Show newest version
/**
 * Licensed to The Apereo Foundation under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 *
 * The Apereo Foundation licenses this file to you under the Educational
 * Community 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://opensource.org/licenses/ecl2.txt
 *
 * 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 org.opencastproject.util.data;

import static org.opencastproject.util.data.Option.option;
import static org.opencastproject.util.data.Option.some;

import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * This class provides functions to ease and secure the handling of collections by supporting a type safe -- at least to
 * the extent Java's type system allows -- immutable and more functional style.
 *
 * Note that all functions do not mutate input collections unless otherwise stated.
 */
public final class Collections {
  private Collections() {
  }

  // TODO check all clients of this method since it potentially breaks!
  @SuppressWarnings("unchecked")
  private static  Collection buildFrom(Collection as) {
    try {
      return as.getClass().newInstance();
    } catch (Exception e) {
      throw new IllegalArgumentException("Type " + as.getClass() + " needs a parameterless constructor");
    }
  }

  /**
   * Get a value from a map, creating and adding a new one, if the value is missing, i.e. it is null.
   *
   * @param c
   *          creates the missing value
   */
  public static  V getOrCreate(Map map, K key, Creator c) {
    V v = map.get(key);
    if (v == null) {
      v = c.create();
      map.put(key, v);
    }
    return v;
  }

  /**
   * Get a value from a map, creating and adding a new one, if the value is missing, i.e. it is null.
   */
  public static  V getOrCreate(Map map, K key, Function0 f) {
    V v = map.get(key);
    if (v == null) {
      v = f.apply();
      map.put(key, v);
    }
    return v;
  }

  /**
   * Apply a function f to all elements of collection as to produce a new collection
   * bs.
   *
   * An (empty) instance of the target collection has to be provided explicitly.
   *
   * @param as
   *          the source collection
   * @param bs
   *          the (empty) target collection
   * @param f
   *          the function to apply to each element of as
   * @deprecated use {@link Monadics}
   */
  @Deprecated
  public static > M map(Collection as, M bs, Function f) {
    for (A x : as) {
      bs.add(f.apply(x));
    }
    return bs;
  }

  /**
   * Apply a binary function (operator) to a start value and all elements of the list in turn.
   *
   * Example: (+) 0 [1, 2, 3] -> (((0 + 1) + 2) + 3)
   *
   * @deprecated use {@link Monadics}
   */
  @Deprecated
  public static  B foldl(Collection as, B start, Function2 f) {
    B fold = start;
    for (A a : as) {
      fold = f.apply(fold, a);
    }
    return fold;
  }

  /**
   * Apply a function f to all elements of collection as to produce a new collection
   * bs.
   *
   * The type of collection as needs a parameterless constructor.
   *
   * Please note that since java does not support higher-order polymorphism -- which is needed to capture the type of
   * the collection -- some casting on the client side may still be necessary.
   *
   * @throws RuntimeException
   *           if the target collection cannot be created
   * @deprecated use {@link Monadics}
   */
  @Deprecated
  public static  Collection map(Collection as, Function f) {
    Collection b = buildFrom(as);
    for (A x : as) {
      b.add(f.apply(x));
    }
    return b;
  }

  /**
   * Apply a function f to all elements of collection as to produce a new collection
   * bs by concatenating the results.
   *
   * The type of collection as needs a parameterless constructor.
   *
   * Please note that since java does not support higher-order polymorphism -- which is needed to capture the type of
   * the collection -- some casting on the client side may still be necessary.
   *
   * @throws RuntimeException
   *           if the result collection cannot be created
   * @deprecated use {@link Monadics}
   */
  @Deprecated
  public static  Collection flatMap(Collection as, Function> f) {
    Collection bs = buildFrom(as);
    for (A a : as) {
      bs.addAll(f.apply(a));
    }
    return bs;
  }

  /**
   * Exactly like {@link #flatMap(java.util.Collection, Function)} but you have to provide the target collection
   * yourself.
   *
   * @deprecated use {@link Monadics}
   */
  @Deprecated
  public static > M flatMap(Collection as, M bs, Function> f) {
    for (A a : as) {
      bs.addAll(f.apply(a));
    }
    return bs;
  }

  /**
   * Returns the first element in as that satisfies a predicate p.
   *
   * @deprecated use {@link Monadics}
   */
  @Deprecated
  public static  Option find(Collection as, Predicate p) {
    for (A x : as) {
      if (p.apply(x))
        return some(x);
    }
    return Option.none();
  }

  /**
   * Tests if at least one element in as satisfies predicate p.
   *
   * @deprecated use {@link Monadics}
   */
  @Deprecated
  public static  boolean exists(Collection as, Predicate p) {
    for (A a : as) {
      if (p.apply(a))
        return true;
    }
    return false;
  }

  /**
   * Return a new collection containing only the elements that satisfy predicate p.
   *
   * The type of collection as needs a parameterless constructor.
   *
   * @deprecated use {@link Monadics}
   */
  @Deprecated
  public static > M filter(M as, Predicate p) {
    @SuppressWarnings("unchecked")
    final M filtered = (M) buildFrom(as);
    for (A a : as) {
      if (p.apply(a))
        filtered.add(a);
    }
    return filtered;
  }

  /** Return the head of list as or none. */
  public static  Option head(List as) {
    if (!as.isEmpty()) {
      return some(as.get(0));
    } else {
      return Option.none();
    }
  }

  /** Return the last element of the list. */
  public static  Option last(List as) {
    return as.size() > 0 ? some(as.get(as.size() - 1)) : Option. none();
  }

  /** Return the last element of the array. */
  public static  Option last(A[] as) {
    return as.length > 0 ? some(as[as.length - 1]) : Option. none();
  }

  /** Make a string from a collection separating each element by sep. */
  public static String mkString(Collection as, String sep) {
    final StringBuilder b = new StringBuilder();
    for (Object a : as)
      b.append(a).append(sep);
    return b.substring(0, Math.max(b.length() - sep.length(), 0));
  }

  /** Append source collection as to target. */
  public static , S extends Iterable> T appendTo(T target, S as) {
    for (A a : as)
      target.add(a);
    return target;
  }

  /** Append source collections as to target. */
  @SafeVarargs
  public static , S extends Iterable> T appendToM(T target, S... as) {
    for (S s : as) {
      for (A a : s)
        target.add(a);
    }
    return target;
  }

  /** Append source collections as to target. */
  @SafeVarargs
  public static , X extends A> T appendToA(T target, X... as) {
    java.util.Collections.addAll(target, as);
    return target;
  }

  /** Concatenates two iterables into a new list. */
  public static > List concat(M as, M bs) {
    List x = new ArrayList<>();
    for (A a : as)
      x.add(a);
    for (A b : bs)
      x.add(b);
    return x;
  }

  /** Concatenates two lists. */
  public static  List concat(List as, List bs) {
    List x = new ArrayList<>();
    for (A a : as)
      x.add(a);
    for (A b : bs)
      x.add(b);
    return x;
  }

  /**
   * Merge two maps where b takes precedence.
   *
   * @return a new immutable map
   */
  public static  Map merge(Map a, Map b) {
    final Map x = new HashMap<>();
    x.putAll(a);
    x.putAll(b);
    return java.util.Collections.unmodifiableMap(x);
  }

  /**
   * Merge two sets into one. b takes precedence over a.
   *
   * @return a new immutable set
   */
  public static  Set merge(Set a, Set b) {
    final Set x = new HashSet<>();
    x.addAll(a);
    x.addAll(b);
    return java.util.Collections.unmodifiableSet(x);
  }

  public static > List diff(M as, M bs) {
    final List diff = toList(as.iterator());
    for (A b : bs) {
      diff.remove(b);
    }
    return diff;
  }

  /** Drain all elements of as into a list. */
  public static  List toList(Iterator as) {
    final List t = new ArrayList<>();
    while (as.hasNext()) {
      t.add(as.next());
    }
    return t;
  }

  /** Create a list of tuples (K, V) from a map. */
  public static  List> toList(Map map) {
    List> list = new ArrayList<>();
    for (Entry entry : map.entrySet()) {
      list.add(Tuple.tuple(entry.getKey(), entry.getValue()));
    }
    return list;
  }

  @SafeVarargs
  public static  Map toList(Tuple... ts) {
    final Map map = new HashMap<>(ts.length);
    for (Tuple t : ts) {
      map.put(t.getA(), t.getB());
    }
    return map;
  }

  /** Drain all elements of as into a list. */
  public static  List toList(Collection as) {
    return new ArrayList<>(as);
  }

  /** Return nil if a is null or a list containing a otherwise. */
  @SuppressWarnings("unchecked")
  public static  List toList(A a) {
    return a != null ? list(a) : Collections. nil();
  }

  /**
   * Return the list as is or nil, if as is null.
   * 
   * @deprecated use {@link #nullToNil(java.util.List)}
   */
  @Deprecated
  public static  List mkList(List as) {
    return as != null ? as : Collections. nil();
  }

  /** Return the list as is or nil, if as is null. */
  public static  List nullToNil(List as) {
    return as != null ? as : Collections. nil();
  }

  /** Create a list from an array. */
  @SafeVarargs
  public static  List list(A... as) {
    final List t = new ArrayList<>();
    java.util.Collections.addAll(t, as);
    return t;
  }

  /** Create a list from an array. */
  @SafeVarargs
  public static  List nonNullList(A... as) {
    final List t = new ArrayList<>();
    for (A a : as) {
      if (null != a) {
        t.add(a);
      }
    }
    return t;
  }

  /** The empty list. */
  @SuppressWarnings("unchecked")
  public static  List nil() {
    return java.util.Collections.EMPTY_LIST;
  }

  /** The empty list. */
  @SuppressWarnings("unchecked")
  public static  List nil(Class type) {
    return java.util.Collections.EMPTY_LIST;
  }

  /** Construct a new list by prepending an element to a given list. */
  public static  List cons(A a, List as) {
    final List target = new ArrayList<>(as.size() + 1);
    target.add(a);
    target.addAll(as);
    return target;
  }

  /** Create a set from an array. */
  @SafeVarargs
  public static  Set set(A... as) {
    final Set t = new HashSet<>(as.length);
    java.util.Collections.addAll(t, as);
    return t;
  }

  /** Create a set from a list. */
  public static  Set toSet(List as) {
    Set r = new HashSet<>(as.size());
    for (A a : as)
      r.add(a);
    return r;
  }

  /** Create a map from a list of tuples (K, V). */
  @SafeVarargs
  public static  Map map(Tuple... ts) {
    final Map map = new HashMap<>(ts.length);
    for (Tuple t : ts) {
      map.put(t.getA(), t.getB());
    }
    return map;
  }

  /** Create a sorted map from a list of tuples (K, V) based on the natural ordering of K. */
  @SafeVarargs
  public static  SortedMap smap(Tuple... ts) {
    final SortedMap map = new TreeMap<>();
    for (Tuple t : ts) {
      map.put(t.getA(), t.getB());
    }
    return map;
  }

  /** Create a dictionary from a list of tuples (K, V). */
  @SafeVarargs
  public static  Dictionary dict(Tuple... ts) {
    final Dictionary dict = new Hashtable<>(ts.length);
    for (Tuple t : ts) {
      dict.put(t.getA(), t.getB());
    }
    return dict;
  }

  /** Create properties from a list of tuples (K, V). */
  @SafeVarargs
  public static Properties properties(Tuple... ts) {
    Properties a = new Properties();
    for (Tuple t : ts) {
      a.setProperty(t.getA(), t.getB());
    }
    return a;
  }

  /** Convert a properties object into a typed immutable map. */
  public static Map toMap(final Properties p) {
    final Map m = new HashMap<>();
    for (Map.Entry e : p.entrySet()) {
      m.put(e.getKey().toString(), e.getValue().toString());
    }
    return java.util.Collections.unmodifiableMap(m);
  }

  /**
   * Partition a list after some predicate group into map.
   *
   * Use e.g. ArrayListMultimap.create() to create a multimap.
   *
   * @see #groupBy(Iterable, Function)
   */
  public static  Multimap groupBy(Multimap map,
                                              Iterable values,
                                              Function group) {
    for (V value : values) {
      final K key = group.apply(value);
      map.put(key, value);
    }
    return map;
  }

  /**
   * Partition a list after some predicate group into map.
   *
   * @return an {@link ImmutableMultimap}
   * @see #groupBy(com.google.common.collect.Multimap, Iterable, Function)
   */
  public static  ImmutableMultimap groupBy(Iterable values,
                                                       Function group) {
    final ImmutableMultimap.Builder map = ImmutableMultimap.builder();
    for (V value : values) {
      final K key = group.apply(value);
      map.put(key, value);
    }
    return map.build();
  }

  /**
   * Partition a list after some predicate group into map.
   */
  public static  Multimap makeMap(Multimap map,
                                                 Iterable values,
                                                 Function> group) {
    for (X value : values) {
      final Tuple entry = group.apply(value);
      map.put(entry.getA(), entry.getB());
    }
    return map;
  }

  /** Partition a list in chunks of size size. The last chunk may be smaller. */
  public static  List> grouped(List as, int size) {
    final List> grouped = new ArrayList<>((as.size() / size) + 1);
    List group = new ArrayList<>(size);
    grouped.add(group);
    int count = size;
    for (A a : as) {
      if (count == 0) {
        group = new ArrayList<>(size);
        grouped.add(group);
        count = size;
      }
      group.add(a);
      count--;
    }
    return grouped;
  }

  /** Create a list of unique elements determined by a given criteria. */
  public static  Collection unique(List as, Function criteria) {
    final Map unique = new HashMap<>();
    for (A a : as) {
      unique.put(criteria.apply(a), a);
    }
    return unique.values();
  }

  /**
   * Partition a list after some predicate keyGen. The partition function has to make sure that keys are
   * unique per list element because each key holds only one value. Later values overwrite newer ones.
   *
   * The resulting map is an immutable {@link java.util.HashMap}.
   *
   * @see #asMap(java.util.Map, java.util.List, Function)
   */
  public static  Map asMap(List values, Function keyGen) {
    return java.util.Collections.unmodifiableMap(asMap(new HashMap(), values, keyGen));
  }

  /**
   * Partition a list after some predicate keyGen into map. The partition function has to make
   * sure that keys are unique per list element because each key holds only one value. Later values overwrite newer
   * ones.
   *
   * @see #asMap(java.util.List, Function)
   */
  public static  Map asMap(Map map, List values, Function keyGen) {
    for (V value : values) {
      final K key = keyGen.apply(value);
      map.put(key, value);
    }
    return map;
  }

  /** Create an array from a collection. */
  @SuppressWarnings("unchecked")
  public static  A[] toArray(Class elemType, Collection a) {
    return a.toArray((A[]) Array.newInstance(elemType, a.size()));
  }

  /** Convert a collection of {@link Double}s into an array of primitive type. */
  public static double[] toDoubleArray(Collection as) {
    final double[] target = new double[as.size()];
    int i = 0;
    for (Double a : as) {
      target[i] = a;
      i++;
    }
    return target;
  }

  /** Convert a collection of {@link Float}s into an array of primitive type. */
  public static float[] toFloatArray(Collection as) {
    final float[] target = new float[as.size()];
    int i = 0;
    for (Float a : as) {
      target[i] = a;
      i++;
    }
    return target;
  }

  /** Convert a collection of {@link Integer}s into an array of primitive type. */
  public static int[] toIntArray(Collection as) {
    final int[] target = new int[as.size()];
    int i = 0;
    for (Integer a : as) {
      target[i] = a;
      i++;
    }
    return target;
  }

  /** Create an iterator form an array. */
  @SafeVarargs
  public static  Iterator iterator(final A... as) {
    return new Iterator() {
      private int i = 0;

      @Override
      public boolean hasNext() {
        return as.length > i;
      }

      @Override
      public A next() {
        if (i < as.length) {
          return as[i++];
        } else {
          throw new NoSuchElementException();
        }
      }

      @Override
      public void remove() {
        throw new UnsupportedOperationException();
      }
    };
  }

  /** Create an iterator that repeats a for the said times. */
  public static  Iterator repeat(final X a, final int times) {
    return new Iterator() {
      private int count = times;

      @Override
      public boolean hasNext() {
        return count > 0;
      }

      @Override
      public A next() {
        count--;
        return a;
      }

      @Override
      public void remove() {
        throw new UnsupportedOperationException();
      }
    };
  }

  /** Join two iterators. */
  public static  Iterator join(final Iterator a, final Iterator b) {
    return new Iterator() {
      @Override
      public boolean hasNext() {
        return a.hasNext() || b.hasNext();
      }

      @Override
      public A next() {
        return a.hasNext() ? a.next() : b.next();
      }

      @Override
      public void remove() {
        throw new UnsupportedOperationException();
      }
    };
  }

  /**
   * Make an Iterator usable in a for comprehension like this:
   *
   * 
   *   Iterator<A> as = ...
   *   for (A a : forc(as)) {
   *     ...
   *   }
   * 
*/ public static
Iterable forc(final Iterator as) { return new Iterable() { @Override public Iterator iterator() { return as; } }; } public static Function, List> optionToList() { return new Function, List>() { @Override public List apply(Option as) { return as.list(); } }; } public static Function> flatMapArrayToList(final Function> f) { return new Function>() { @Override public List apply(A[] as) { return Monadics.mlist(as).bind(f).value(); } }; } /** Turn an option into an iterator. */ public static Function, Iterator> optionToIterator() { return new Function, Iterator>() { @Override public Iterator apply(Option as) { return as.iterator(); } }; } public static Function, Option> head() { return new Function, Option>() { @Override public Option apply(List as) { return Collections.head(as); } }; } /** Sort a list. */ public static Function, List> sort() { return new Function, List>() { @Override public List apply(List as) { List asCopy = new ArrayList<>(as); java.util.Collections.sort(asCopy); return asCopy; } }; } /** Create a function that checks if its argument is contained in as. */ public static Function containedIn(final List as) { return new Function() { @Override public Boolean apply(A a) { return as.contains(a); } }; } /** Curried version of {@link List#contains(Object)}. */ public static Function, Function> containedIn() { return new Function, Function>() { @Override public Function apply(final List as) { return containedIn(as); } }; } public static Function, A> getOrElse(final A a) { return new Function, A>() { @Override public A apply(Option ao) { return ao.getOrElse(a); } }; } /** Concat (aka flatten) a collection of collections by concatenating them all. [[a]] -> [a] */ public static >> List concat(M as) { final List target = new ArrayList<>(as.size()); for (Collection a : as) { target.addAll(a); } return target; } /** Return a function to get data from a map. map -> key -> value */ public static Function> getMap(final Map map) { return new Function>() { @Override public Option apply(A a) { return option(map.get(a)); } }; } }