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

net.sf.kdgcommons.collections.CollectionUtil Maven / Gradle / Ivy

Go to download

A collection of utility classes for Java, supplementing and in some cases replacing Jakarta Commons and Google Guava.

The newest version!
// Copyright Keith D Gregory
//
// 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 net.sf.kdgcommons.collections;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.RandomAccess;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.regex.Pattern;


/**
 *  Static utility methods for working with collections -- particularly
 *  parameterized collections.
 */
public class CollectionUtil
{
    /**
     *  Returns a set (HashSet) containing the passed elements.
     */
    public static  HashSet asSet(T... elems)
    {
        HashSet result = new HashSet();
        for (T elem : elems)
            result.add(elem);
        return result;
    }


    /**
     *  Returns a map (HashMap) built from the passed elements. Elements
     *  alternate between keys and values, and if given an odd number of elements the
     *  last will be used as the key for a null value. Elements will be added in the
     *  order they appear as parameters, so repeating a key will mean that only the
     *  last value is stored.
     *  

* Note that the result is parameterized as Object,Object; as there * is no way to differentiate between keys and values with varargs, we have to * use the lowest common denominator. * * @since 1.0.15 */ public static HashMap asMap(Object... elems) { HashMap result = new HashMap(); for (int ii = 0 ; ii < elems.length ; ii += 2) { Object key = elems[ii]; Object value = (ii < elems.length - 1) ? elems[ii + 1] : null; result.put(key, value); } return result; } /** * Appends an arbitrary number of explicit elements to an existing collection. * Primarily useful when writing testcases. */ public static void addAll(Collection coll, T... elems) { for (T elem : elems) coll.add(elem); } /** * Appends the values returned by an iterator to the passed collection. */ public static void addAll(Collection coll, Iterator src) { while (src.hasNext()) coll.add(src.next()); } /** * Appends the contents of an iterable object to the passed collection. */ public static void addAll(Collection coll, Iterable src) { addAll(coll, src.iterator()); } /** * Adds a value to the collection if the boolean expression is true. * Returns the collection as a convenience for chained invocations. * * @since 1.0.8 */ public static Collection addIf(Collection coll, T value, boolean expr) { if (expr) coll.add(value); return coll; } /** * Adds a value to the collection if it's not null. Returns the collection * as a convenience for chained invocations. * * @since 1.0.11 */ public static Collection addIfNotNull(Collection coll, T value) { return addIf(coll, value, value != null); } /** * Stores an entry in a map if the boolean expression is true. Returns the * map as a convenience for chained invocations. * * @since 1.0.15 */ public static Map putIf(Map map, K key, V value, boolean expr) { if (expr) map.put(key, value); return map; } /** * Stores an entry in a map if the value is not null. Returns the * map as a convenience for chained invocations. * * @since 1.0.15 */ public static Map putIfNotNull(Map map, K key, V value) { return putIf(map, key, value, value != null); } /** * Adds the specified item to a map if it does not already exist. Returns * either the added item or the existing mapping. *

* Note: The return behavior differs from Map.put(), * in that it returns the new value if there was no prior * mapping. I find this more useful, as I typically want * to do something with the mapping. *

* Warning: This operation is not synchronized. In most cases, a * better approach is to use {@link DefaultMap}, with a * functor to generate new entries. * * @since 1.0.12 */ public static V putIfAbsent(Map map, K key, V value) { if (! map.containsKey(key)) { map.put(key,value); return value; } return map.get(key); } /** * Adds entries from add to base where there is not * already a mapping with the same key. * * @since 1.0.14 */ public static void putIfAbsent(Map base, Map add) { for (Map.Entry entry : add.entrySet()) putIfAbsent(base, entry.getKey(), entry.getValue()); } /** * Returns the first element of the passed list, null if * the list is empty or null. */ public static T first(List list) { return isNotEmpty(list) ? list.get(0) : null; } /** * Returns the last element of the passed list, null if * the list is empty or null. Uses an indexed get unless the list is * a subclass of java.util.LinkedList */ public static T last(List list) { if (isEmpty(list)) return null; if (list instanceof LinkedList) return ((LinkedList)list).getLast(); return list.get(list.size() - 1); } /** * Verifies that the passed list contains only elements of the given type, * and returns it as a parameterized list (does not create a new list). *

* This function exists to avoid suppressing warnings in application code. * * @throws ClassCastException if any element is a different type. */ @SuppressWarnings("unchecked") public static List cast(List list, Class klass) { for (Object obj : list) { klass.cast(obj); } return (List)list; } /** * Verifies that the passed set contains only elements of the given type, * and returns it as a parameterized set (does not create a new set). *

* This function exists to avoid suppressing warnings in application code. * * @throws ClassCastException if any element is a different type. */ @SuppressWarnings("unchecked") public static Set cast(Set set, Class klass) { for (Object obj : set) { klass.cast(obj); } return (Set)set; } /** * Verifies that the passed map contains only keys and values of the given * types, and returns it as a parameterized map (does not create a new map). *

* This function exists to avoid suppressing warnings in application code. * * @throws ClassCastException if any key/value is a different type. * * @since 1.0.15 */ @SuppressWarnings("unchecked") public static Map cast(Map map, Class keyClass, Class valueClass) { for (Map.Entry entry : map.entrySet()) { keyClass.cast(entry.getKey()); valueClass.cast(entry.getValue()); } return (Map)map; } /** * Resizes the passed list to N entries. Will add the specified object if * the list is smaller than the desired size, discard trailing entries if * larger. This is primarily used to presize a list that will be accessed * by index, but it may also be used to truncate a list for display. * * @return The list, as a convenience for callers * * @throws UnsupportedOperationException if the list does not implement * RandomAccess and its list iterator does not * support the remove() operation */ public static List resize(List list, int newSize, T obj) { if (list instanceof ArrayList) ((ArrayList)list).ensureCapacity(newSize); if (list.size() < newSize) { for (int ii = list.size() ; ii < newSize ; ii++) list.add(obj); } else if (list.size() > newSize) { if (list instanceof RandomAccess) { for (int ii = list.size() - 1 ; ii >= newSize ; ii--) list.remove(ii); } else { ListIterator itx = list.listIterator(newSize); while (itx.hasNext()) { itx.next(); itx.remove(); } } } return list; } /** * Resizes the passed list to N entries. Will add nulls if the list is * smaller than the desired size, discard trailing entries if larger. * This is primarily used to presize a list that will be accessed by * index, but it may also be used to truncate a list for display. * * @return The list, as a convenience for callers * @throws UnsupportedOperationException if the list does not implement * RandomAccess and its list iterator does not * support the remove() operation */ public static List resize(List list, int newSize) { return resize(list, newSize, null); } /** * Iterates the passed collection, converts its elements to strings, then * concatenates those strings with the specified delimiter between them. * Nulls are converted to empty strings. * * @since 1.0.2 */ public static String join(Iterable coll, String delim) { if (coll == null) return ""; boolean isFirst = true; StringBuilder buf = new StringBuilder(1024); for (T item : coll) { if (isFirst) isFirst = false; else buf.append(delim); if (item != null) buf.append(String.valueOf(item)); } return buf.toString(); } /** * Adds all elements of the src collections to dest, * returning dest. This is typically used when you need to combine * collections temporarily for a method argument. * *

Warning: this mutates the provided list. In version 2.0 * it will create a new list, with a variant of addAll() that mutates * the list. * * @since 1.0.7 */ public static List combine(List dest, Collection... src) { for (Collection cc : src) { dest.addAll(cc); } return dest; } /** * Adds all elements of the src collections to dest, * returning dest. This is typically used when you need to combine * collections temporarily for a method argument. * *

Warning: this mutates the provided set. In version 2.0 * it will create a new set, with a variant of addAll() that mutates * the set. * * @since 1.0.7 */ public static Set combine(Set dest, Collection... src) { for (Collection cc : src) { dest.addAll(cc); } return dest; } /** * Adds all elements of the src collections to dest, * returning dest. This is typically used when you need to combine * collections temporarily for a method argument. *

* Note: source maps are added in order; if the same keys are present in multiple * sources, the last one wins. * *

Warning: this mutates the provided map. In version 2.0 * it will create a new map, with a variant of putAll() that mutates * the map. * * @since 1.0.7 */ public static Map combine(Map dest, Map... src) { for (Map cc : src) { dest.putAll(cc); } return dest; } /** * Returns true if the passed collection is either null * or has size 0. */ public static boolean isEmpty(Collection c) { return (c == null) ? true : (c.size() == 0); } /** * Returns true if the passed collection is not null * and has size > 0. */ public static boolean isNotEmpty(Collection c) { return (c != null) && (c.size() > 0); } /** * Returns true if the passed map is either null * or has size 0. */ public static boolean isEmpty(Map m) { return (m == null) ? true : (m.size() == 0); } /** * Returns true if the passed map is not null * and has size > 0. */ public static boolean isNotEmpty(Map m) { return (m != null) && (m.size() > 0); } /** * Compares two collections of Comparable elements. The two collections are * iterated, and the first not-equal compareTo() result is returned. If the * collections are of equal length and contain the same elements in iteration order, they * are considered equal. If they are of unequal length but contain the same elements in * iteration order, the shorter is considered less than the longer. *

* Note that two collections that are equal based on their intrinsic equals() * method, but iterate in a different order (ie, hash-based collections) are not considered * equal by this method. * * @since 1.0.14 */ @SuppressWarnings("rawtypes") public static int compare(Collection c1, Collection c2) { Iterator itx1 = c1.iterator(); Iterator itx2 = c2.iterator(); while (itx1.hasNext()) { if (! itx2.hasNext()) return 1; Comparable v1 = itx1.next(); Comparable v2 = itx2.next(); int cmp = v1.compareTo(v2); if (cmp != 0) return cmp; } if (itx2.hasNext()) return -1; else return 0; } /** * Returns the second iterable if the first is null. This is used for a null-safe * for loop. */ public static Iterable defaultIfNull(Iterable reg, Iterable def) { return (reg == null) ? def : reg; } /** * Returns the default collection if the regular object is null or empty. */ public static Collection defaultIfEmpty(Collection reg, Collection def) { return ((reg == null) || (reg.size() == 0)) ? def : reg; } /** * Retrieves a value from a nested collection hierarchy by following a sequence * of keys. Supports arbitrary nesting of maps, arrays, and lists. *

* Example; given: *

     *      Map<String,Object> bottom = new HashMap<>();
     *      bottom.put("argle", "bargle");
     *
     *      List<Object> middle = new ArrayList<>();
     *      middle.add(bottom);
     *
     *      Map<String,Object> top = new HashMap<>();
     *      top.put("foo", middle);
     *  
* Then: *

*

    *
  • CollectionUtil.getVia(top, "foo", 0, "argle"); returns "bargle". *
  • CollectionUtil.getVia(top, "bar", 0, "argle"); returns null. *
  • CollectionUtil.getVia(top, "foo", "junk", "argle"); throws IllegalArgumentException. *
* * @param root The root object. * @param keys One or more keys. The first key is applied to the root object, * the second key is applied to the result of that, and so on. * Arrays and lists may only be accessed via numeric keys; maps * may be accessed via any type of key. * * @return The object located via the sequence of keys, null if * any key along the path does not resolve to an object. * * @throws IllegalArgumentException if any object found during the traversal is * not a valid collection type, or if the key is not appropriate for the * collection. * * @since 1.0.15 */ public static Object getVia(Object root, Object... keys) { Object current = root; // I iterate by index rather than for-in so that I can construct exceptions for (int ii = 0 ; ii < keys.length ; ii++) { try { Object key = keys[ii]; if (current == null) { return null; } if (current instanceof Map) { current = ((Map)current).get(key); } else if (current instanceof List) { int index = ((Number)key).intValue(); List list = (List)current; current = (index < list.size()) ? list.get(index) : null; } else if (current.getClass().isArray()) { int index = ((Number)key).intValue(); current = (index < Array.getLength(current)) ? Array.get(current, index) : null; } else { List currentKeys = Arrays.asList(keys).subList(0, ii+1); throw new IllegalArgumentException( "attempted to get from " + current.getClass().getName() + " (path: " + currentKeys + ")"); } } catch (ClassCastException ex) { // I believe this is the only way that we can get here ... List currentKeys = Arrays.asList(keys).subList(0, ii+1); throw new IllegalArgumentException( "attempted to get from " + current.getClass().getName() + " with non-numeric index" + " (path: " + currentKeys + ")"); } } return current; } /** * Applies the specified functor to every element of the given collection, in * its natural iteration order, and returns a list of the results. *

* If the functor throws, it will be rethrown in a {@link CollectionUtil.MapException}, * which provides detailed information and partial work. * * @since 1.0.10 * * @deprecated * This function has been made obsolete by Java8. It will be removed in version 2.0. */ @Deprecated public static List map(Collection coll, IndexValueMapFunctor functor) { List result = new ArrayList(coll.size()); int index = 0; for (V value : coll) { try { result.add(functor.invoke(index, value)); index++; } catch (Throwable ex) { throw new MapException(ex, index, value, result); } } return result; } /** * Performs a parallel map operation. For each element in the source collection, * a callable is dispatched to the passed ExecutorService to invoke * the specified functor. The results are accumulated and returned as a list, in * the order of the original collection's iterator. *

* If any element causes an exception, this method throws {@link CollectionUtil.MapException}. * While that exception returns partial results, there is no guarantee that the * results represent a particular range of the source collection. *

* This method will wait until all of the elements of the collection have been * processed, unless it is interrupted. If multiple invocations threw, one will * be chosen arbitrarily; there is no guarantee that it represents the first * collection element to cause an exception. * * @since 1.0.10 * * @deprecated * This function has been made obsolete by Java8. It will be removed in version 2.0. */ @Deprecated public static List map(ExecutorService threadpool, Collection values, final IndexValueMapFunctor functor) throws InterruptedException { int count = values.size(); ArrayList values2 = new ArrayList(values); ArrayList> futures = new ArrayList>(count); ArrayList results = new ArrayList(); resize(results, count); for (int ii = 0 ; ii < count ; ii++) { final int index = ii; final V value = values2.get(ii); futures.add(threadpool.submit(new Callable() { public R call() throws Exception { return functor.invoke(index, value); } })); } int failureIndex = 0; Throwable failureException = null; for (int ii = 0 ; ii < count ; ii++) { Future future = futures.get(ii); try { results.set(ii, future.get()); } catch (CancellationException ex) { // I don't think we can ever get this exception, since we // don't let the Future escape (and immediate shutdown of // the pool should create an ExecutionException); but, we // should treat it differently if we ever do get it failureIndex = ii; failureException = ex; } catch (ExecutionException ex) { failureIndex = ii; failureException = ex.getCause(); } } if (failureException != null) throw new MapException(failureException, failureIndex, values2.get(failureIndex), results); else return results; } /** * Applies the specified functor to every element in the given collection, with * the expectation that it will return a single value based on the item and any * previous value. * * @since 1.0.10 * * @deprecated * This function has been made obsolete by Java8. It will be removed in version 2.0. */ @Deprecated public static R reduce(Collection coll, IndexValueReduceFunctor functor) { R pendingResult = null; int index = 0; for (V value : coll) { try { pendingResult = functor.invoke(index, value, pendingResult); index++; } catch (Throwable ex) { throw new ReduceException(ex, index, value, pendingResult); } } return pendingResult; } /** * Applies the specified predicate functor to every element of a collection, * in its natural iteration order, and returns a list containing only those * elements for which the predicate returned true. *

* If the functor throws, it will be rethrown in a {@link CollectionUtil.FilterException}, * which provides detailed information and partial work. * * @since 1.0.11 * * @deprecated * This function has been made obsolete by Java8. It will be removed in version 2.0. */ @Deprecated public static List filter(Collection coll, Predicate predicate) { List result = new ArrayList(coll.size()); int index = 0; for (V value : coll) { try { if (predicate.invoke(index, value)) { result.add(value); } index++; } catch (Throwable ex) { throw new FilterException(ex, index, value, result); } } return result; } /** * Applies the given regex to the string value of every item in the passed * list, building a new list from those value that either match or do not * match. Null entries are treated as an empty string for matching, but * will be returned as null. * * @since 1.0.3 * * @param list The source list; this is unmodified. * @param regex Regex applied to every string in the list. * @param include If true, strings that match are copied * to the output list; if false, strings * that don't match are copied. * * @deprecated * This function has been made obsolete by Java8. It will be removed in version 2.0. */ @Deprecated public static List filter(List list, String regex, final boolean include) { final Pattern pattern = Pattern.compile(regex); return filter(list, new Predicate() { public boolean invoke(int index, T value) throws Exception { String str = (value == null) ? "" : value.toString(); return pattern.matcher(str).matches() == include; } }); } /** * Partitions the passed iterable into N sublists, each of which has * at most maxSize elements. */ public static List> partition(Iterable source, int maxSize) { if (source == null) return Collections.emptyList(); List> result = new ArrayList>(); List sublist = new ArrayList(maxSize); int count = 0; for (T item : source) { sublist.add(item); count++; if (count >= maxSize) { result.add(sublist); sublist = new ArrayList(maxSize); count = 0; } } if (sublist.size() > 0) { result.add(sublist); } return result; } /** * Returns a map that contains all keys in the specified collection. *

* The returned map is a HashMap; see variant for choosing map type. */ public static Map submap(Map src, Collection keys) { return submap(src, keys, new HashMap()); } /** * Extracts all mappings from the source map that correspond to the passed * keys, and stores them in the destination map. Returns the destination * map as a convenience. */ public static Map submap(Map src, Collection keys, Map dest) { if ((src == null) || (keys == null) || (dest == null)) { return dest; } for (K key : keys) { if (src.containsKey(key)) { dest.put(key, src.get(key)); } } return dest; } //---------------------------------------------------------------------------- // Supporting Objects //---------------------------------------------------------------------------- /** * A functor interface for {@link #map}. The {@link #invoke} function is * called for every element in the collection, and is passed the element * value and its position (0-based) in the iteration order. *

* The implementation is permitted to throw anything, checked or not. * * @since 1.0.10 * * @deprecated * This interface has been made obsolete by Java8. It will be removed in version 2.0. */ @Deprecated public interface IndexValueMapFunctor { public R invoke(int index, V value) throws Exception; } /** * An exception wrapper for {@link #map}. Contains the wrapped exception, * the value and index that caused the exception, and the results-to-date. *

* Note: because Java does not allow parameterization of Throwable * subclasses (JLS 8.1.2), the value and results are held as Objects. * * @since 1.0.10 * * @deprecated * This class has been made obsolete by Java8. It will be removed in version 2.0. */ @Deprecated public static class MapException extends RuntimeException { private static final long serialVersionUID = 1; private int _index; private Object _value; private List _partialResults; public MapException(Throwable cause, int index, Object value, List partialResults) { super(cause); _index = index; _value = value; _partialResults = partialResults; } /** * Returns the position (0-based) in the original collection's iteration where the * wrapped exception was thrown. */ public int getIndex() { return _index; } /** * Returns the value that caused the exception. */ public Object getValue() { return _value; } /** * Returns any partial results from the map operation. *

* Warning: the contents of this list are undefined in the case of a parallel map * operation. */ public List getPartialResults() { return _partialResults; } } /** * A functor used for the {@link #reduce} operation. The {@link #invoke} * function is called for every element of a collection, and is responsible * for aggregating the results. On the first invocation, the "pending" * result is null; on subsequent invocations, it is the value * returned from the previous invocation. * * @since 1.0.10 * * @deprecated * This interface has been made obsolete by Java8. It will be removed in version 2.0. */ @Deprecated public interface IndexValueReduceFunctor { public R invoke(int index, V value, R pendingResult) throws Exception; } /** * An exception wrapper for {@link #reduce}. Contains the wrapped exception, * the value and index that caused the exception, and the results-to-date. *

* Note: because Java does not allow parameterization of Throwable * subclasses (JLS 8.1.2), the value and results are held as Objects. * * @since 1.0.10 * * @deprecated * This class has been made obsolete by Java8. It will be removed in version 2.0. */ @Deprecated public static class ReduceException extends RuntimeException { private static final long serialVersionUID = 1; private int _index; private Object _value; private Object _partialResults; public ReduceException(Throwable cause, int index, Object value, Object partialResults) { super(cause); _index = index; _value = value; _partialResults = partialResults; } /** * Returns the position (0-based) in the original collection's iteration where the * wrapped exception was thrown. */ public int getIndex() { return _index; } /** * Returns the value that caused the exception. */ public Object getValue() { return _value; } /** * Returns any partial results. This is the pendingResult * value passed to the functor at the time the exception was thrown. */ public Object getPartialResults() { return _partialResults; } } /** * Implement this for the {@link #filter} operation: return true to * include an element in the result, false to skip it. Implementations * may throw exceptions; these will cause the filter to stop processing. *

* Purists may object, but each invocation is given the element index, in iteration * order. * * @since 1.0.11 * * @deprecated * This interface has been made obsolete by Java8. It will be removed in version 2.0. */ @Deprecated public interface Predicate { public boolean invoke(int index, V value) throws Exception; } /** * An exception wrapper for {@link #filter}. Contains the wrapped exception, * the value and index that caused the exception, and the results-to-date. *

* Note: because Java does not allow parameterization of Throwable * subclasses (JLS 8.1.2), the value and results are held as Objects. * * @deprecated * This class has been made obsolete by Java8. It will be removed in version 2.0. * * @since 1.0.11 */ @Deprecated public static class FilterException extends RuntimeException { private static final long serialVersionUID = 1; private int _index; private Object _value; private List _partialResults; public FilterException(Throwable cause, int index, Object value, List partialResults) { super(cause); _index = index; _value = value; _partialResults = partialResults; } /** * Returns the position (0-based) in the original collection's iteration where the * wrapped exception was thrown. */ public int getIndex() { return _index; } /** * Returns the value that caused the exception. */ public Object getValue() { return _value; } /** * Returns any partial results from the map operation. *

* Warning: the contents of this list are undefined in the case of a parallel map * operation. */ public List getPartialResults() { return _partialResults; } } }