/**
* 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 <i>not</i> 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 <A, B> Collection<A> buildFrom(Collection<B> 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 <K, V> V getOrCreate(Map<K, V> map, K key, Creator<V> 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 <K, V> V getOrCreate(Map<K, V> map, K key, Function0<V> f) {
V v = map.get(key);
if (v == null) {
v = f.apply();
map.put(key, v);
}
return v;
}
/**
* Apply a function <code>f</code> to all elements of collection <code>as</code> to produce a new collection
* <code>bs</code>.
*
* 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 <code>as</code>
* @deprecated use {@link Monadics}
*/
@Deprecated
public static <A, B, M extends Collection<B>> M map(Collection<A> as, M bs, Function<A, B> 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 <A, B> B foldl(Collection<A> as, B start, Function2<B, A, B> f) {
B fold = start;
for (A a : as) {
fold = f.apply(fold, a);
}
return fold;
}
/**
* Apply a function <code>f</code> to all elements of collection <code>as</code> to produce a new collection
* <code>bs</code>.
*
* The type of collection <code>as</code> 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 <A, B> Collection<B> map(Collection<A> as, Function<A, B> f) {
Collection<B> b = buildFrom(as);
for (A x : as) {
b.add(f.apply(x));
}
return b;
}
/**
* Apply a function <code>f</code> to all elements of collection <code>as</code> to produce a new collection
* <code>bs</code> by concatenating the results.
*
* The type of collection <code>as</code> 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 <A, B> Collection<B> flatMap(Collection<A> as, Function<A, Collection<B>> f) {
Collection<B> 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 <A, B, M extends Collection<B>> M flatMap(Collection<A> as, M bs, Function<A, Collection<B>> f) {
for (A a : as) {
bs.addAll(f.apply(a));
}
return bs;
}
/**
* Returns the first element in <code>as</code> that satisfies a predicate <code>p</code>.
*
* @deprecated use {@link Monadics}
*/
@Deprecated
public static <A> Option<A> find(Collection<A> as, Predicate<A> p) {
for (A x : as) {
if (p.apply(x))
return some(x);
}
return Option.none();
}
/**
* Tests if at least one element in <code>as</code> satisfies predicate <code>p</code>.
*
* @deprecated use {@link Monadics}
*/
@Deprecated
public static <A> boolean exists(Collection<A> as, Predicate<A> p) {
for (A a : as) {
if (p.apply(a))
return true;
}
return false;
}
/**
* Return a new collection containing only the elements that satisfy predicate <code>p</code>.
*
* The type of collection <code>as</code> needs a parameterless constructor.
*
* @deprecated use {@link Monadics}
*/
@Deprecated
public static <A, M extends Collection<A>> M filter(M as, Predicate<A> 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 <code>as</code> or <code>none</code>. */
public static <A> Option<A> head(List<A> as) {
if (!as.isEmpty()) {
return some(as.get(0));
} else {
return Option.none();
}
}
/** Return the last element of the list. */
public static <A> Option<A> last(List<A> as) {
return as.size() > 0 ? some(as.get(as.size() - 1)) : Option.<A> none();
}
/** Return the last element of the array. */
public static <A> Option<A> last(A[] as) {
return as.length > 0 ? some(as[as.length - 1]) : Option.<A> none();
}
/** Make a string from a collection separating each element by <code>sep</code>. */
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 <code>as</code> to <code>target</code>. */
public static <A, T extends Collection<A>, S extends Iterable<? extends A>> T appendTo(T target, S as) {
for (A a : as)
target.add(a);
return target;
}
/** Append source collections <code>as</code> to <code>target</code>. */
@SafeVarargs
public static <A, T extends Collection<A>, S extends Iterable<? extends A>> T appendToM(T target, S... as) {
for (S s : as) {
for (A a : s)
target.add(a);
}
return target;
}
/** Append source collections <code>as</code> to <code>target</code>. */
@SafeVarargs
public static <A, T extends Collection<A>, 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 <A, M extends Iterable<? extends A>> List<A> concat(M as, M bs) {
List<A> x = new ArrayList<>();
for (A a : as)
x.add(a);
for (A b : bs)
x.add(b);
return x;
}
/** Concatenates two lists. */
public static <A> List<A> concat(List<? extends A> as, List<? extends A> bs) {
List<A> x = new ArrayList<>();
for (A a : as)
x.add(a);
for (A b : bs)
x.add(b);
return x;
}
/**
* Merge two maps where <code>b</code> takes precedence.
*
* @return a new immutable map
*/
public static <A, B> Map<A, B> merge(Map<? extends A, ? extends B> a, Map<? extends A, ? extends B> b) {
final Map<A, B> x = new HashMap<>();
x.putAll(a);
x.putAll(b);
return java.util.Collections.unmodifiableMap(x);
}
/**
* Merge two sets into one. <code>b</code> takes precedence over <code>a</code>.
*
* @return a new immutable set
*/
public static <A> Set<A> merge(Set<? extends A> a, Set<? extends A> b) {
final Set<A> x = new HashSet<>();
x.addAll(a);
x.addAll(b);
return java.util.Collections.unmodifiableSet(x);
}
public static <A, M extends Iterable<? extends A>> List<A> diff(M as, M bs) {
final List<A> diff = toList(as.iterator());
for (A b : bs) {
diff.remove(b);
}
return diff;
}
/** Drain all elements of <code>as</code> into a list. */
public static <A> List<A> toList(Iterator<? extends A> as) {
final List<A> t = new ArrayList<>();
while (as.hasNext()) {
t.add(as.next());
}
return t;
}
/** Create a list of tuples (K, V) from a map. */
public static <K, V> List<Tuple<K, V>> toList(Map<K, V> map) {
List<Tuple<K, V>> list = new ArrayList<>();
for (Entry<K, V> entry : map.entrySet()) {
list.add(Tuple.tuple(entry.getKey(), entry.getValue()));
}
return list;
}
@SafeVarargs
public static <K, V> Map<K, V> toList(Tuple<? extends K, ? extends V>... ts) {
final Map<K, V> map = new HashMap<>(ts.length);
for (Tuple<? extends K, ? extends V> t : ts) {
map.put(t.getA(), t.getB());
}
return map;
}
/** Drain all elements of <code>as</code> into a list. */
public static <A> List<A> toList(Collection<A> as) {
return new ArrayList<>(as);
}
/** Return nil if <code>a</code> is null or a list containing <code>a</code> otherwise. */
@SuppressWarnings("unchecked")
public static <A> List<A> toList(A a) {
return a != null ? list(a) : Collections.<A> nil();
}
/**
* Return the list as is or nil, if <code>as</code> is null.
*
* @deprecated use {@link #nullToNil(java.util.List)}
*/
@Deprecated
public static <A> List<A> mkList(List<A> as) {
return as != null ? as : Collections.<A> nil();
}
/** Return the list as is or nil, if <code>as</code> is null. */
public static <A> List<A> nullToNil(List<A> as) {
return as != null ? as : Collections.<A> nil();
}
/** Create a list from an array. */
@SafeVarargs
public static <A> List<A> list(A... as) {
final List<A> t = new ArrayList<>();
java.util.Collections.addAll(t, as);
return t;
}
/** Create a list from an array. */
@SafeVarargs
public static <A> List<A> nonNullList(A... as) {
final List<A> t = new ArrayList<>();
for (A a : as) {
if (null != a) {
t.add(a);
}
}
return t;
}
/** The empty list. */
@SuppressWarnings("unchecked")
public static <A> List<A> nil() {
return java.util.Collections.EMPTY_LIST;
}
/** The empty list. */
@SuppressWarnings("unchecked")
public static <A> List<A> nil(Class<A> type) {
return java.util.Collections.EMPTY_LIST;
}
/** Construct a new list by prepending an element to a given list. */
public static <A> List<A> cons(A a, List<? extends A> as) {
final List<A> target = new ArrayList<>(as.size() + 1);
target.add(a);
target.addAll(as);
return target;
}
/** Create a set from an array. */
@SafeVarargs
public static <A> Set<A> set(A... as) {
final Set<A> t = new HashSet<>(as.length);
java.util.Collections.addAll(t, as);
return t;
}
/** Create a set from a list. */
public static <A> Set<A> toSet(List<A> as) {
Set<A> 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 <K, V> Map<K, V> map(Tuple<? extends K, ? extends V>... ts) {
final Map<K, V> map = new HashMap<>(ts.length);
for (Tuple<? extends K, ? extends V> 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 <K, V> SortedMap<K, V> smap(Tuple<? extends K, ? extends V>... ts) {
final SortedMap<K, V> map = new TreeMap<>();
for (Tuple<? extends K, ? extends V> t : ts) {
map.put(t.getA(), t.getB());
}
return map;
}
/** Create a dictionary from a list of tuples (K, V). */
@SafeVarargs
public static <K, V> Dictionary<K, V> dict(Tuple<? extends K, ? extends V>... ts) {
final Dictionary<K, V> dict = new Hashtable<>(ts.length);
for (Tuple<? extends K, ? extends V> 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<String, String>... ts) {
Properties a = new Properties();
for (Tuple<String, String> t : ts) {
a.setProperty(t.getA(), t.getB());
}
return a;
}
/** Convert a properties object into a typed immutable map. */
public static Map<String, String> toMap(final Properties p) {
final Map<String, String> 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 <code>group</code> into <code>map</code>.
*
* Use e.g. <code>ArrayListMultimap.create()</code> to create a multimap.
*
* @see #groupBy(Iterable, Function)
*/
public static <K, V> Multimap<K, V> groupBy(Multimap<K, V> map,
Iterable<? extends V> values,
Function<? super V, ? extends K> group) {
for (V value : values) {
final K key = group.apply(value);
map.put(key, value);
}
return map;
}
/**
* Partition a list after some predicate <code>group</code> into <code>map</code>.
*
* @return an {@link ImmutableMultimap}
* @see #groupBy(com.google.common.collect.Multimap, Iterable, Function)
*/
public static <K, V> ImmutableMultimap<K, V> groupBy(Iterable<? extends V> values,
Function<? super V, ? extends K> group) {
final ImmutableMultimap.Builder<K, V> 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 <code>group</code> into <code>map</code>.
*/
public static <K, V, X> Multimap<K, V> makeMap(Multimap<K, V> map,
Iterable<? extends X> values,
Function<? super X, Tuple<K, V>> group) {
for (X value : values) {
final Tuple<K, V> entry = group.apply(value);
map.put(entry.getA(), entry.getB());
}
return map;
}
/** Partition a list in chunks of size <code>size</code>. The last chunk may be smaller. */
public static <A> List<List<A>> grouped(List<A> as, int size) {
final List<List<A>> grouped = new ArrayList<>((as.size() / size) + 1);
List<A> 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 <A, B> Collection<A> unique(List<A> as, Function<A, B> criteria) {
final Map<B, A> unique = new HashMap<>();
for (A a : as) {
unique.put(criteria.apply(a), a);
}
return unique.values();
}
/**
* Partition a list after some predicate <code>keyGen</code>. 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 <K, V> Map<K, V> asMap(List<V> values, Function<V, K> keyGen) {
return java.util.Collections.unmodifiableMap(asMap(new HashMap<K, V>(), values, keyGen));
}
/**
* Partition a list after some predicate <code>keyGen</code> into <code>map</code>. 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 <K, V> Map<K, V> asMap(Map<K, V> map, List<V> values, Function<V, K> 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, B extends A> A[] toArray(Class<A> elemType, Collection<B> 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<Double> 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<Float> 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<Integer> 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 <A> Iterator<A> iterator(final A... as) {
return new Iterator<A>() {
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 <code>a</code> for the said times. */
public static <A, X extends A> Iterator<A> repeat(final X a, final int times) {
return new Iterator<A>() {
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 <A> Iterator<A> join(final Iterator<A> a, final Iterator<A> b) {
return new Iterator<A>() {
@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:
*
* <pre>
* Iterator<A> as = ...
* for (A a : forc(as)) {
* ...
* }
* </pre>
*/
public static <A> Iterable<A> forc(final Iterator<A> as) {
return new Iterable<A>() {
@Override
public Iterator<A> iterator() {
return as;
}
};
}
public static <A> Function<Option<A>, List<A>> optionToList() {
return new Function<Option<A>, List<A>>() {
@Override
public List<A> apply(Option<A> as) {
return as.list();
}
};
}
public static <A, B> Function<A[], List<B>> flatMapArrayToList(final Function<A, List<B>> f) {
return new Function<A[], List<B>>() {
@Override
public List<B> apply(A[] as) {
return Monadics.mlist(as).bind(f).value();
}
};
}
/** Turn an option into an iterator. */
public static <A> Function<Option<A>, Iterator<A>> optionToIterator() {
return new Function<Option<A>, Iterator<A>>() {
@Override
public Iterator<A> apply(Option<A> as) {
return as.iterator();
}
};
}
public static <A> Function<List<A>, Option<A>> head() {
return new Function<List<A>, Option<A>>() {
@Override
public Option<A> apply(List<A> as) {
return Collections.head(as);
}
};
}
/** Sort a list. */
public static <A extends Comparable> Function<List<A>, List<A>> sort() {
return new Function<List<A>, List<A>>() {
@Override
public List<A> apply(List<A> as) {
List<A> asCopy = new ArrayList<>(as);
java.util.Collections.sort(asCopy);
return asCopy;
}
};
}
/** Create a function that checks if its argument is contained in <code>as</code>. */
public static <A> Function<A, Boolean> containedIn(final List<A> as) {
return new Function<A, Boolean>() {
@Override
public Boolean apply(A a) {
return as.contains(a);
}
};
}
/** Curried version of {@link List#contains(Object)}. */
public static <A> Function<List<A>, Function<A, Boolean>> containedIn() {
return new Function<List<A>, Function<A, Boolean>>() {
@Override
public Function<A, Boolean> apply(final List<A> as) {
return containedIn(as);
}
};
}
public static <A> Function<Option<A>, A> getOrElse(final A a) {
return new Function<Option<A>, A>() {
@Override
public A apply(Option<A> ao) {
return ao.getOrElse(a);
}
};
}
/** Concat (aka flatten) a collection of collections by concatenating them all. [[a]] -> [a] */
public static <A, M extends Collection<? extends Collection<A>>> List<A> concat(M as) {
final List<A> target = new ArrayList<>(as.size());
for (Collection<A> a : as) {
target.addAll(a);
}
return target;
}
/** Return a function to get data from a map. <code>map -> key -> value</code> */
public static <A, B> Function<A, Option<B>> getMap(final Map<A, B> map) {
return new Function<A, Option<B>>() {
@Override
public Option<B> apply(A a) {
return option(map.get(a));
}
};
}
}