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

ch.inftec.ju.util.JuCollectionUtils Maven / Gradle / Ivy

package ch.inftec.ju.util;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ObjectUtils;

import ch.inftec.ju.util.comparison.DefaultComparator;
import ch.inftec.ju.util.comparison.EqualityTester;
import ch.inftec.ju.util.function.Function;

/**
 * Contains utility methods regarding collections, maps and the like.
 * @author Martin
 *
 */
public final class JuCollectionUtils {
	/**
	 * Don't instantiate.
	 */
	private JuCollectionUtils() {
		throw new AssertionError("use only statically");
	}
	
	/**
	 * Compares two maps. The maps are considered equal if they contain the same keys
	 * and the corresponding keys have the same values (using equals on the keys
	 * and the specified EqualityTester on the values for comparison).
	 * @param m1 Map 1
	 * @param m2 Map 2
	 * @param equalityTester EqualityTester instance to test for equality. If null, a
	 * DefaultComparator will be used
	 * @return True if the maps are equal, false otherwise
	 */
	public static  boolean mapEquals(Map m1, Map m2, EqualityTester equalityTester) {
		// Handle null
		if (m1 == null || m2 == null) return m1 == m2;
		
		if (m1.size() != m2.size()) return false;
		
		// Use DefaultComparator if tester is null
		if (equalityTester == null) equalityTester = new DefaultComparator<>();

		for (K k : m1.keySet()) {
			// Make sure m2 contains the key too
			if (!m2.containsKey(k)) return false;
			
			// Make sure m2 has the same value for the key
			if (!equalityTester.equals(m1.get(k), m2.get(k))) return false;
		}
		
		return true;
	}
	
	/**
	 * Compares two maps. The maps are considered equal if they contain the same keys
	 * and the corresponding keys have the same values (using equals on the keys
	 * and values for comparison).
	 * @param m1 Map 1
	 * @param m2 Map 2
	 * @return True if the maps are equal, false otherwise
	 */
	public static  boolean mapEquals(Map m1, Map m2) {
		return JuCollectionUtils.mapEquals(m1, m2, null);
	}
	
	/**
	 * Compares to collections. The collections are considered equal if they contain
	 * the same elements in the same order.
	 * @param c1 Collection 1
	 * @param c2 Collection 2
	 * @return True if the collections are equal, false otherwise
	 */
	public static  boolean collectionEquals(Collection c1, Collection c2) {
		// Handle special cases
		if (c1 == c2) {
			return true;
		} else if (c1 == null || c2 == null) {
			// Collections are not identical, so one of them will not be null...
			return false;
		} else if (c1.size() != c2.size()) {
			return false;
		} else {
			// Collections are not null and have same length

			// Convert the collections to List to have indexed access...
			List l1 = asList(c1);
			List l2 = asList(c2);
			for (int i = 0; i < l1.size(); i++) {
				if (!ObjectUtils.equals(l1.get(i), l2.get(i))) return false;
			}

			// All elements are equal
			return true;
		}
	}
	
	/**
	 * Checks if two collections are equal ignoring the order of the elements, i.e. contain
	 * the same elements, regardless of their order.
	 * @param c1 Collection 1
	 * @param c2 Collection 2
	 * @return True if both collections contain the same elements in arbitrary order
	 */
	public static  boolean collectionEqualsIgnoreOrder(List c1, List c2) {
		return intersection(JuCollectionUtils.asList(c1), JuCollectionUtils.asList(c2)).size() == c1.size();
	}

	/**
	 * Returns the intersection of the two specified collections, i.e. a new collection that contains all elements
	 * that are present in both collections.
	 * @return List containing all elements present in both collections. In case of a no match (or null parameters given),
	 * an empty list is returned. This method always returns a new collection, even if the result is equal to one of the input collections
	 */
	public static  List intersection(Collection c1, Collection c2) {
		// Null handling
		if (c1 == null || c2 == null) {
			return Collections.emptyList();
		}

		List intersection = new ArrayList<>();

		for (T item : c1) {
			if (collectionContains(c2, item)) {
				intersection.add(item);
			}
		}

		return intersection;
	}
	
	/**
	 * Checks if all specified values are part of the specified collection.
	 * 

* The collection may contain more than the specified values * @param cCollection Collection * @param values Values the collection must contain, in arbitrary order */ @SafeVarargs public static boolean collectionContains(Collection cCollection, T... values) { for (T val : values) { if (!cCollection.contains(val)) return false; } return true; } /** * Checks if all specified values are part of the specified collection. Comparison * is done case insensitively. *

* The collection may contain more than the specified values. * @param cCollection Collection * @param values Values the collection must contain, in arbitrary order */ @SafeVarargs public static boolean collectionContainsIgnoreCase(Collection cCollection, String... values) { for (String val : values) { boolean containsVal = false; for (String colVal : cCollection) { if (colVal.equalsIgnoreCase(val)) { containsVal = true; break; } } if (!containsVal) return false; } return true; } /** * Returns a map using the provided key (String or Object.toString) and * values (Object) pairs. * @param keyValuePairs Key value pairs */ public static HashMap stringMap(Object... keyValuePairs) { HashMap map = new HashMap<>(); for (int i = 1; i < keyValuePairs.length; i+=2) { Object obj = keyValuePairs[i-1]; String key = obj instanceof String ? (String)obj : obj.toString(); map.put(key, keyValuePairs[i]); } return map; } /** * Returns a new ArrayList containing the specified elements. *
* This method is annotated with SafeVarargs to indicate that no faulty * type conversions are performed. * @param elements Elements * @return ArrayList containing the specified elements */ @SafeVarargs public static ArrayList arrayList(T... elements) { ArrayList list = new ArrayList<>(); Collections.addAll(list, elements); return list; } /** * Returns an unmodifiable list with the specified elements. *
* This method is annotated with SafeVarargs to indicate that no faulty * type conversions are performed. * @param elements Elements * @return Unmodifiable list containing the specified elements */ @SafeVarargs public static List unmodifiableList(T... elements) { return Collections.unmodifiableList(JuCollectionUtils.arrayList(elements)); } /** * Returns an empty collection if the specified collection is null, * otherwise the same reference is returned. *

* Can be used to avoid null pointer issues. * @param col Collection * @return Empty collection is specified collection is null, the same collection otherwise */ public static Collection emptyForNull(Collection col) { @SuppressWarnings("unchecked") Collection emptyList = Collections.EMPTY_LIST; return col == null ? emptyList : col; } /** * Checks if the source array is a subset of the destination array, i.e. all * elements of the source array are contained in the destination array. * @param srcArray Source array * @param dstArray Destination array * @return True if the source array is a subset of the destination array */ public static boolean isSubsetOf(Object[] srcArray, Object[] dstArray) { if (dstArray == null || srcArray == null) { return dstArray == srcArray; } for (Object o : srcArray) { if (!ArrayUtils.contains(dstArray, o)) return false; } return true; } /** * Checks if the the to arrays are equal, i.e. contain the same elements. * Note that the type of the array is not compared, so an empty Long array will be equal to an empty Float * array... * @param a1 Array 1 * @param a2 Array 2 * @return True if the array are equal, false otherwise */ public static boolean arrayEquals(Object[] a1, Object[] a2) { if (a1 == null || a2 == null) return a1 == a2; // If the array have the same dimension and type, compare all elements within if (a1.length == a2.length) { for (int i = 0; i < a1.length; i++) { if (!ObjectUtils.equals(a1[i], a2[i])) return false; } return true; } else { return false; } } /** * Selects all items of the collection that start with the specified start string and returns them as a new collection. * @param inputCollection Input collection * @param startString Start string. If null, null elements are returned * @param caseSensitive Whether the comparison should be case sensitive * @return Collection of strings starting with the specified start strings */ public static Collection selectStartingWith(Collection inputCollection, String startString, final boolean caseSensitive) { final String startStringConv = startString == null ? null : (caseSensitive ? startString.toUpperCase() : startString); return null; // return CollectionUtils.select(inputCollection, new Predicate() { // @Override // public boolean evaluate(String object) { // if (object == null || startStringConv == null) return object == startStringConv; // // String str = caseSensitive ? object.toUpperCase() : object; // return str.startsWith(startStringConv); // } // }); } /** * Converts the specified collection to a list. *

* If the collection already is a list, it is casted. Otherwise, a new ArrayList is created. * @param collection Collection * @return Collection as List */ public static List asList(Collection collection) { if (collection instanceof List) return (List)collection; else return new ArrayList<>(collection); } /** * Adds all elements returned by the specified Iterable to a list. * @param iterable Iterable * @return List */ public static List asList(Iterable iterable) { List l = new ArrayList<>(); for (T e: iterable) { l.add(e); } return l; } /** * Converts the specified collection to an ArrayList. *

* If the collection already is an ArrayList, it is casted. Otherwise, a new ArrayList is created. * @param collection Collection * @return Collection as List */ public static ArrayList asArrayList(Collection collection) { if (collection instanceof ArrayList) return (ArrayList)collection; else return new ArrayList<>(collection); } /** * Converts the specified values to an ArrayList. * @param values List of values / array * @return ArrayList */ @SafeVarargs public static ArrayList asArrayList(T... values) { ArrayList list = new ArrayList<>(); for (T value : values) list.add(value); return list; } /** * Converts the specified values to an ArrayList of the specified type. * @param type Explicit type * @param values List of values / array * @return ArrayList */ @SafeVarargs public static ArrayList asTypedArrayList(Class type, S... values) { ArrayList list = new ArrayList<>(); for (T value : values) list.add(value); return list; } /** * Returns a sorted set with all distinct values of the specified collection in their * natural order * @param collection Collection containing elements * @return Sorted set */ public static Set asSortedSet(Collection collection) { return new TreeSet<>(collection); } /** * Returns a sorted set with all distinct values of the specified collection in the order * they first appear. * @param collection Collection containing elements * @return Sorted set */ public static Set asSameOrderSet(Collection collection) { return new LinkedHashSet<>(collection); } /** * Gets a set of all keys in the specified Properties instance, converted to Strings. *

* This method will return the keys sorted alphabetically as Properties returns their keys non deterministically * @param props Properties * @return Set of key Strings */ public static Set getKeyStrings(Properties props) { Set set = new TreeSet<>(); for (Object key : props.keySet()) { if (key != null) set.add(key.toString()); } return set; } /** * Gets all items from an iterator, stores them in a list and returns it. */ public static List iteratorToList(Iterator iterator) { List list = new ArrayList<>(); while (iterator.hasNext()) { T item = iterator.next(); list.add(item); } return list; } /** * Returns a new iterator backed by the specified iterator that applies the specified transformation on all the iterated values. * @param sourceIterator Source iterator * @param transformation Transformation function to be applied to all items * @param Source type * @param Result type * @return Iterator that returns all items of the source iterator after being transformed using the specified transformation */ public static Iterator iteratorTransformed(Iterator sourceIterator, Function transformation) { return new TransformingIterator<>(sourceIterator, transformation); } private static class TransformingIterator implements Iterator { private final Iterator sourceIterator; private final Function transformer; public TransformingIterator(Iterator sourceIterator, Function transformer) { this.sourceIterator = sourceIterator; this.transformer = transformer; } @Override public boolean hasNext() { return sourceIterator.hasNext(); } @Override public E next() { S nextVal = sourceIterator.next(); return transformer.apply(nextVal); } @Override public void remove() { sourceIterator.remove(); } } /** * Returns a new iterable backed by the specified iterable that applies the specified transformation on all the iterated values. * @param sourceIterable Source iterator * @param transformation Transformation function to be applied to all items * @param Source type * @param Result type * @return Iterator that returns all items of the source iterator after being transformed using the specified transformation */ public static Iterable iterableTransformed(final Iterable sourceIterable, final Function transformation) { return new Iterable() { @Override public Iterator iterator() { Iterator sourceIterator = sourceIterable.iterator(); return new TransformingIterator<>(sourceIterator, transformation); } }; } /** * Creates a new instance of a WeakReferenceIterable. */ public static WeakReferenceIterable newWeakReferenceIterable() { return new WeakReferenceIterableImpl<>(); } static final class WeakReferenceIterableImpl implements WeakReferenceIterable { private final List> list = new ArrayList<>(); private final ReferenceQueue queue = new ReferenceQueue<>(); private int listVersion = 0; private WeakReferenceIterableImpl() { } /** * Gets the size of the internal list. This is package protected to allow unit tests * to check whether empty reference objects are disposed correctly. * @return Size of internal list (which may contain empty references that are not * returned by the iterator) */ int getInternalListSize() { return this.list.size(); } @Override public Iterator iterator() { return new Iter(); } @Override public void add(E element) { this.list.add(new ReferenceWrapper<>(element, false, this.queue, this.list)); this.listVersion++; } @Override public void addWeak(E element) { if (element == null) throw new NullPointerException("Null cannot be weak referenced"); this.list.add(new ReferenceWrapper<>(element, true, this.queue, this.list)); this.listVersion++; } @Override public void remove(E element) { for (Iterator> iter = this.list.iterator(); iter.hasNext(); ) { ReferenceWrapper wrapper = iter.next(); if (ObjectUtils.equals(element, wrapper.get())) { iter.remove(); break; } } if (this.list.size() > 0) this.list.get(0).removeDeadReferences(); this.listVersion++; } @Override public void clear() { this.list.clear(); this.listVersion++; } private final static class ReferenceWrapper { private final T strongRef; private final WeakReference weakRef; private final ReferenceQueue queue; private final List> list; ReferenceWrapper(T ref, boolean weak, ReferenceQueue queue, List> list) { if (weak) { this.weakRef = new WeakReference<>(ref, queue); this.strongRef = null; } else { this.strongRef = ref; this.weakRef = null; } this.queue = queue; this.list = list; this.removeDeadReferences(); } public T get() { return this.weakRef != null ? this.weakRef.get() : this.strongRef; } private void removeDeadReferences() { Reference ref; while ((ref = this.queue.poll()) != null) { this.list.remove(ref); } } } private final class Iter implements Iterator { /** * Index of the current element, i.e. index the remove method will use. * If -1, next hasn't been called yet. */ private int index = -1; /** * If null, hasNext hasn't been called yet. If -1, there is no * next element. Otherwise, the index of the next element. */ private Integer nextIndex = null; /** * Index of the last element that was removed. */ private Integer removedIndex = null; /** * List version to detect concurrent modification problems */ private int lastListVersion = listVersion; /** * Strong reference to the next element to make sure it doesn't get garbage collected. */ private E nextElement; @Override public boolean hasNext() { this.checkConcurrentUpdate(); if (this.nextIndex == null || this.index == this.nextIndex) { // Try to get next element, i.e. set the nextIndex and nextElement for (int i = this.index + 1; i < list.size(); i++) { ReferenceWrapper wrapper = list.get(i); this.nextElement = wrapper.get(); if (this.nextElement == null && wrapper.weakRef != null) { // WeakRef that is null, so skip } else { // Found next element. Set index this.nextIndex = i; return true; } } // No next element found this.nextIndex = -1; } return this.nextIndex >= 0; } @Override public E next() { this.checkConcurrentUpdate(); if (this.nextIndex == null || this.index == this.nextIndex) { this.hasNext(); } if (this.nextIndex == -1) { throw new NoSuchElementException("No more elements in Iterable"); } this.index = this.nextIndex; return list.get(this.index).get(); } @Override public void remove() { this.checkConcurrentUpdate(); if (this.removedIndex != null && this.removedIndex == this.index) { throw new IllegalStateException("Element was already removed"); } if (this.index < 0) { throw new IllegalStateException("Next hasn't been called yet"); } list.remove(this.index); this.index--; this.removedIndex = index; this.nextIndex--; this.lastListVersion = listVersion; } private void checkConcurrentUpdate() { if (this.lastListVersion != listVersion) { throw new ConcurrentModificationException("WeakReferenceIterable has been modified oustide of Iterator"); } } } } }