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

org.conqat.lib.commons.collections.CollectionUtils Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) CQSE GmbH
 *
 * 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 org.conqat.lib.commons.collections;

import java.lang.reflect.Array;
import java.util.AbstractSet;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.RandomAccess;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;

import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.conqat.lib.commons.assertion.CCSMAssert;
import org.conqat.lib.commons.function.BiConsumerWithException;
import org.conqat.lib.commons.function.ConsumerWithException;
import org.conqat.lib.commons.function.FunctionWithException;
import org.conqat.lib.commons.function.PredicateWithException;
import org.conqat.lib.commons.string.StringUtils;
import org.jetbrains.annotations.Contract;

import com.google.common.collect.Sets;

/**
 * This class offers utility methods for collections. In can be seen as an extension to
 * {@link Collections}.
 */
public class CollectionUtils {

	/** Empty unmodifiable list. */
	private static final UnmodifiableList EMPTY_LIST = new UnmodifiableList<>(Collections.emptyList());

	/** Empty unmodifiable map. */
	private static final UnmodifiableMap EMPTY_MAP = new UnmodifiableMap<>(Collections.emptyMap());

	/** Empty unmodifiable set. */
	private static final UnmodifiableSet EMPTY_SET = new UnmodifiableSet<>(Collections.emptySet());

	/** Delimiter used in strings to separate list values. */
	public static final Character MULTI_VALUE_DELIMITER = ',';

	/**
	 * Prefix used to indicate the start of a line comment inside a multi-value connector configuration
	 * text field.
	 */
	private static final String CONNECTOR_CONFIG_MULTI_VALUE_INPUT_LINE_COMMENT_PREFIX = "##";

	/**
	 * Create a hashed set from an array.
	 *
	 * @param 
	 *            type
	 * @param elements
	 *            elements in the set.
	 * @return the set.
	 * @see Arrays#asList(Object[])
	 */
	@SafeVarargs
	public static  HashSet asHashSet(T... elements) {
		HashSet result = new HashSet<>(elements.length);
		Collections.addAll(result, elements);
		return result;
	}

	/** Creates a new unmodifiable {@link HashMap} based on given pairs. */
	@SafeVarargs
	public static  UnmodifiableMap asMap(Pair... pairs) {
		return asMap(new HashMap<>(), pairs);
	}

	/**
	 * Extends the given map with the pairs and returns an unmodifiable version.
	 */
	@SafeVarargs
	public static  UnmodifiableMap asMap(Map baseMap, Pair... pairs) {
		for (Pair pair : pairs) {
			baseMap.put(pair.getFirst(), pair.getSecond());
		}
		return asUnmodifiable(baseMap);
	}

	/** Creates an unmodifiable hash set from an array. */
	@SafeVarargs
	public static  UnmodifiableSet asUnmodifiableHashSet(T... elements) {
		return new UnmodifiableSet<>(CollectionUtils.asHashSet(elements));
	}

	/**
	 * Returns a new unmodifiable collection wrapping the given collection. This method is here to avoid
	 * retyping the generic argument for the collection class.
	 */
	public static  UnmodifiableCollection asUnmodifiable(Collection c) {
		return new UnmodifiableCollection<>(c);
	}

	/**
	 * Returns a new unmodifiable list wrapping the given list. This method is here to avoid retyping
	 * the generic argument for the list class.
	 */
	public static  UnmodifiableList asUnmodifiable(List l) {
		return new UnmodifiableList<>(l);
	}

	/**
	 * Returns a new unmodifiable map wrapping the given map. This method is here to avoid retyping the
	 * generic arguments for the map class.
	 */
	public static  UnmodifiableMap asUnmodifiable(Map m) {
		return new UnmodifiableMap<>(m);
	}

	/**
	 * Returns a new unmodifiable set wrapping the given set. This method is here to avoid retyping the
	 * generic argument for the set class.
	 */
	public static  UnmodifiableSet asUnmodifiable(Set s) {
		return new UnmodifiableSet<>(s);
	}

	/**
	 * Returns a new unmodifiable sorted map wrapping the given sorted map. This method is here to avoid
	 * retyping the generic arguments for the sorted map class.
	 */
	public static  UnmodifiableSortedMap asUnmodifiable(SortedMap m) {
		return new UnmodifiableSortedMap<>(m);
	}

	/**
	 * Returns a new unmodifiable sorted set wrapping the given sorted set. This method is here to avoid
	 * retyping the generic argument for the sorted set class.
	 */
	public static  UnmodifiableSortedSet asUnmodifiable(SortedSet s) {
		return new UnmodifiableSortedSet<>(s);
	}

	/**
	 * The way this method is defined allows to assign it to all parameterized types, i.e. both of the
	 * following statements are accepted by the compiler without warnings:
	 *
	 * 
	 * 
	 * UnmodifiableList<String> emptyList = CollectionUtils.emptyList();
	 *
	 * UnmodifiableList<Date> emptyList = CollectionUtils.emptyList();
	 * 
* * @return the empty list */ @SuppressWarnings("unchecked") public static UnmodifiableList emptyList() { return (UnmodifiableList) EMPTY_LIST; } /** Returns an empty map. See {@link #emptyList()} for further details. */ @SuppressWarnings("unchecked") public static UnmodifiableMap emptyMap() { return (UnmodifiableMap) EMPTY_MAP; } /** Returns an empty set. See {@link #emptyList()} for further details. */ @SuppressWarnings("unchecked") public static UnmodifiableSet emptySet() { return (UnmodifiableSet) EMPTY_SET; } /** * Sorts the specified list into ascending order, according to the natural ordering of its * elements. *

* All elements in the list must implement the Comparable interface. Furthermore, all elements in * the list must be mutually comparable (that is, e1.compareTo(e2) must not throw a * ClassCastException for any elements e1 and e2 in the list). *

* This method does not modify the original collection. */ public static > ArrayList sort(Collection collection) { ArrayList list = new ArrayList<>(collection); Collections.sort(list); return list; } /** * Returns a list that contains the elements of the specified list in reversed order. *

* This method does not modify the original collection. */ public static ArrayList reverse(Collection list) { ArrayList reversed = new ArrayList<>(list); Collections.reverse(reversed); return reversed; } /** * Returns all values, which occur multiple times in the list. *

* It doesn't tell you how often it occurs, just that it is more than once. */ public static Set getDuplicates(List values) { Set duplicates = new HashSet<>(); Set temp = new HashSet<>(); for (T key : values) { if (!temp.add(key)) { duplicates.add(key); } } return duplicates; } /** * Applies the mapper {@link Function} to all items in the collection and returns the resulting * {@link List}. *

* This method does not modify the original collection. */ public static List map(Collection list, Function mapper) { List result = new ArrayList<>(list.size()); for (T entry : list) { result.add(mapper.apply(entry)); } return result; } /** * Applies the mapper {@link Function} to all items in the collection and returns the resulting * {@link Set} only containing distinct mapped values. *

* This method does not modify the original collection. */ public static Set mapToSet(Collection list, Function mapper) { Set result = new HashSet<>(); for (T entry : list) { result.add(mapper.apply(entry)); } return result; } /** * Applies the key and value mapper {@link Function}s to all keys/values in the collection and * returns the resulting {@link Map}. *

* This method does not modify the original collection. */ public static Map map(Map map, Function keyMapper, Function valueMapper) { Map result = new HashMap<>(map.size()); map.forEach((key, value) -> result.put(keyMapper.apply(key), valueMapper.apply(value))); return result; } /** * Applies the mapper {@link Function} to all items in the array and returns the resulting * {@link List}. *

* This method does not modify the original array. */ public static List map(T[] array, Function mapper) { return map(Arrays.asList(array), mapper); } /** * Applies the mapper {@link Function} to all items in the collection, but only adds the result item * to the return list if it is not already in the list. Returns the resulting {@link List}. *

* This method does not modify the original collection. */ public static List mapDistinct(Collection list, Function mapper) { Set encounteredItems = new HashSet<>(); List result = new ArrayList<>(); for (T entry : list) { R resultItem = mapper.apply(entry); if (encounteredItems.add(resultItem)) { result.add(resultItem); } } return result; } /** * Applies the mapper {@link FunctionWithException} to all items in the list and returns the * resulting {@link List}. *

* This method does not modify the original collection. * * @throws E * if the mapper function throws this exception for any of the elements of the original * list. */ public static List mapWithException(Collection list, FunctionWithException mapper) throws E { List result = new ArrayList<>(list.size()); for (T entry : list) { result.add(mapper.apply(entry)); } return result; } /** * Applies the mapper {@link FunctionWithException} to all items in the array and returns the * resulting {@link List}. *

* This method does not modify the original array. * * @throws E * if the mapper function throws this exception for any of the elements of the original * array. */ public static List mapWithException(T[] array, FunctionWithException mapper) throws E { return mapWithException(Arrays.asList(array), mapper); } /** Returns a new List containing only the elements at the given indices. */ public static ArrayList getIndices(List list, List indices) { ArrayList filteredList = new ArrayList<>(); for (int index : indices) { filteredList.add(list.get(index)); } return filteredList; } /** * Returns a new List containing all elements of the given list except for those with the given * indices. */ public static List returnListWithoutGivenIndices(List elements, Set indices) { List result = new ArrayList<>(); for (int i = 0; i < elements.size(); i++) { if (!indices.contains(i)) { result.add(elements.get(i)); } } return result; } /** * Returns the indices of null elements in the given list. The returned list is strictly ordered * (returnedList.get(x) is greater than returnedList.get(x-1)). */ public static List getNullIndices(List<@Nullable ?> list) { List nullIndices = new ArrayList<>(); for (int i = 0; i < list.size(); i++) { if (list.get(i) == null) { nullIndices.add(i); } } return nullIndices; } /** * Returns whether the given integer is a valid index in the given list (i.e., >=0 and * list) { return index >= 0 && index < list.size(); } /** * Replacement for {@link AbstractSet#removeAll(Collection)} (without the boolean return value). * Using this method mitigates a performance bug in the Java API that occurs when calling * {@link AbstractSet#removeAll(Collection)} with a {@link List} that is larger (or equal size) than * the {@link AbstractSet}. In this case, {@link AbstractSet#removeAll(Collection)} will iterate * over the set and call {@link List#contains(Object)} for each item (expensive). *

* IntelliJ reports potentially affected Statements with the warning * Call to 'set.removeAll(list)' may work slowly. The related Java API bug tickets seem * to be stuck: JDK-6982173 * JDK-6394757 * * @param set * The set from which we remove items. * @param itemsToBeRemoved * The items which should be removed from the set. */ public static void removeAll(Set set, List itemsToBeRemoved) { if (itemsToBeRemoved.size() >= set.size()) { itemsToBeRemoved.forEach(set::remove); } else { set.removeAll(itemsToBeRemoved); } } /** * Returns a new map where the given keys have been removed. In contrast to * {@link Map#replaceAll(BiFunction)} this does not modify the original map. */ @NonNull public static Map copyWithEntriesRemoved(Map map, Set keysToRemove) { return map.entrySet().stream().filter(entry -> !keysToRemove.contains(entry.getKey())) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } /** * Merges the two maps. The mergeFunction determines the resulting object. The arguments of the * function can be null if a value exists only in one of the two maps. *

* The resulting map will contain all keys of both maps. */ @NonNull public static Map merge(Map map1, Map map2, BiFunction mergeFunction) { HashMap result = new HashMap<>(); Set keys = Sets.union(map1.keySet(), map2.keySet()); for (K key : keys) { result.put(key, mergeFunction.apply(map1.get(key), map2.get(key))); } return result; } /** * Performs a {@link Map#computeIfAbsent(Object, Function)} calculation on the provided {@code map}, * where the {@code mappingFunction} may throw a checked Exception. * * @see Map#computeIfAbsent(Object, Function) */ @SuppressWarnings("RedundantThrows") // We want the exception to be mentioned here explicitly public static V computeIfAbsentWithException(Map map, K key, FunctionWithException mappingFunction) throws E { return map.computeIfAbsent(key, asSneakyFunction(mappingFunction)); } /** * Throws the provided checked exception without the compiler knowing that it is a checked * exception. On the JVM (class file) level, all exceptions, checked or not, can be thrown * regardless of the {@code throws} clause of your methods, which is why this works. The solution is * necessary as some commonly used interfaces do not allow checked exception (e.g. {@link Function} * in {@link Map#computeIfAbsent(Object, Function)}). * * @apiNote This method should stay private, because the sneaky throws should only be used in some * rare special cases, where no other option is feasible, and the behaviour is * well-defined. */ @SuppressWarnings("unchecked") private static RuntimeException sneakyThrow(Throwable t) throws T { throw (T) t; } /** * Wraps the provided {@link FunctionWithException} in a {@link Function}, which will * {@link #sneakyThrow(Throwable) sneaky throw} any thrown checked {@link Exception}. * * @apiNote This method should stay private, because the sneaky throws should only be used in some * rare special cases, where no other option is feasible, and the behaviour is * well-defined. */ public static Function asSneakyFunction( FunctionWithException functionWithException) { return value -> { try { return functionWithException.apply(value); } catch (Exception e) { throw sneakyThrow(e); } }; } /** * Filters the collection by testing all items against the {@link Predicate} and returning a * {@link List} of those for which it returns true. * *

* This method does not modify the original collection. */ public static List filter(Collection collection, Predicate filter) { ArrayList result = new ArrayList<>(); for (T entry : collection) { if (filter.test(entry)) { result.add(entry); } } return result; } /** * Filters the collection by testing all items against the given predicate and returning a * {@link List} of those for which it returns true. Propagates checked exceptions * declared by the given predicate. * *

* This method does not modify the original collection. */ public static List filterWithException(Collection collection, FunctionWithException filter) throws E { ArrayList result = new ArrayList<>(); for (T entry : collection) { if (filter.apply(entry)) { result.add(entry); } } return result; } /** * See {@link #filterToSet(Collection, Predicate, Supplier)}; a HashSet is constructed. */ public static Set filterToSet(Collection collection, Predicate filter) { return filterToSet(collection, filter, HashSet::new); } /** * Filters the collection by testing all items against the {@link Predicate} and returning a * {@link Set} of those for which it returns true. *

* This method does not modify the original collection. */ public static Set filterToSet(Collection collection, Predicate filter, Supplier> addToSet) { Set result = addToSet.get(); for (T entry : collection) { if (filter.test(entry)) { result.add(entry); } } return result; } /** * Applies the mapper to all items in the list, for which the filter {@link Predicate} returns * true and returns the results as a {@link List}. *

* This method does not modify the original collection. */ public static List filterAndMap(Collection list, Predicate filter, Function mapper) { return filterAndMapWithException(list, filter::test, mapper::apply); } /** * Applies the mapper to all items in the list, for which the filter {@link Predicate} returns * true and returns the results as a {@link List}. *

* This method does not modify the original collection. */ public static List filterAndMapWithException( Collection list, PredicateWithException filter, FunctionWithException mapper) throws X1, X2 { List result = new ArrayList<>(); for (T entry : list) { if (filter.test(entry)) { result.add(mapper.apply(entry)); } } return result; } /** * Returns a list that contains all elements of the specified list except the element at the * specified index. Removes the index from the new List. *

* This method does not modify the original list. */ public static List remove(List list, int index) { ArrayList result = new ArrayList<>(list); result.remove(index); return result; } /** * Sorts the specified list according to the order induced by the specified comparator. *

* All elements in the list must implement the Comparable interface. Furthermore, all elements in * the list must be mutually comparable (that is, e1.compareTo(e2) must not throw a * {@link ClassCastException} for any elements e1 and e2 in the list). *

* This method does not modify the original collection. */ public static List sort(Collection collection, Comparator comparator) { ArrayList list = new ArrayList<>(collection); list.sort(comparator); return list; } /** Returns a sorted unmodifiable list for a collection. */ public static > UnmodifiableList asSortedUnmodifiableList( Collection collection) { return asUnmodifiable(sort(collection)); } /** * Returns one object from an {@link Iterable} or {@code null} if the iterable is empty. */ public static @Nullable T getAny(@NonNull Iterable iterable) { Iterator iterator = iterable.iterator(); if (!iterator.hasNext()) { return null; } return iterator.next(); } /** * Convert collection to array. This is a bit cleaner version of * {@link Collection#toArray(Object[])}. */ @SuppressWarnings("unchecked") public static T[] toArray(Collection collection, Class type) { T[] result = (T[]) java.lang.reflect.Array.newInstance(type, collection.size()); Iterator it = collection.iterator(); for (int i = 0; i < collection.size(); i++) { result[i] = it.next(); } return result; } /** * Copy an array. This is a shortcut for {@link Arrays#copyOf(Object[], int)} that does not require * to specify the length. */ public static T[] copyArray(T[] original) { return Arrays.copyOf(original, original.length); } /** * Compute list of unordered pairs for all elements contained in a collection. */ public static List> computeUnorderedPairs(Collection collection) { List elements = new ArrayList<>(collection); List> pairs = new ArrayList<>(); int size = elements.size(); for (int firstIndex = 0; firstIndex < size; firstIndex++) { for (int secondIndex = firstIndex + 1; secondIndex < size; secondIndex++) { pairs.add(new ImmutablePair<>(elements.get(firstIndex), elements.get(secondIndex))); } } return pairs; } /** * Returns the last element in list or {@code null}, if list is empty. */ @SuppressWarnings("unchecked") // needed for Deque optimization, we know that elements must have type T public static @Nullable T getLast(@NonNull List list) { if (list.isEmpty()) { return null; } if (list instanceof Deque) { return ((Deque) list).getLast(); } return list.get(list.size() - 1); } /** * Returns the sublist of all but the first element in list or {@code null}, if list is empty. */ public static @Nullable List getRest(List list) { if (list.isEmpty()) { return null; } return list.subList(1, list.size()); } /** Sorts the pair list by first index. */ public static , T> void sortByFirst(PairList list) { sortByFirst(list, Comparator.naturalOrder()); } /** * Sorts the pair list with the given comparator. This is stable sorting, i.e. ordering of same * elements does not change. */ public static void sortByFirst(PairList list, Comparator comparator) { sortBy(list, PairList::getFirst, comparator); } /** * Sorts the provided {@link PairList} with the given {@link Comparator} applied on the * {@link PairList#getSecond(int) second} elements. */ public static void sortBySecond(PairList list, Comparator comparator) { sortBy(list, PairList::getSecond, comparator); } private static void sortBy(PairList list, BiFunction, Integer, C> elementRetriever, Comparator comparator) { SortableDataUtils.sort(new ISortableData() { @Override public void swap(int i, int j) { list.swapEntries(i, j); } @Override public int size() { return list.size(); } @Override public boolean isLess(int i, int j) { int compare = comparator.compare(elementRetriever.apply(list, i), elementRetriever.apply(list, j)); if (compare == 0) { return i < j; } return compare < 0; } }); } /** * Returns a list implementation that allows for efficient random access. *

* If the passed collection already supports random access, it gets returned directly. Otherwise, a * list that supports random access is returned with the same content as the passed list. */ public static List asRandomAccessList(Collection list) { // It is not guaranteed that implementations of RandomAccess also // implement List. Hence, we check for both. if (list instanceof List && list instanceof RandomAccess) { return (List) list; } return new ArrayList<>(list); } @SafeVarargs private static > C unionCollection(Supplier collectionSupplier, @Nullable Collection collection1, @Nullable Collection... furtherCollections) { C result = collectionSupplier.get(); if (collection1 != null) { result.addAll(collection1); } for (Collection collection : furtherCollections) { if (collection != null) { result.addAll(collection); } } return result; } /** * Return a set containing the union of all provided collections. We use a {@link HashSet}, i.e. the * elements should support hashing. *

* We use two separate arguments to ensure on the interface level that at least one collection is * provided. This is transparent for the caller. *

* All arguments can be null. The result will always be non-null and will be an empty set if all * arguments are null. */ @SafeVarargs public static HashSet unionSet(Collection collection1, Collection... furtherCollections) { return unionCollection(HashSet::new, collection1, furtherCollections); } /** * Return a set containing the union of all provided {@link EnumSet}s. *

* We use two separate arguments to ensure on the interface level that at least one collection is * provided. This is transparent for the caller. *

* None of the arguments may be null. */ @SafeVarargs public static > EnumSet enumUnionSet(Set initialSet, Set... otherSets) { EnumSet union = EnumSet.copyOf(initialSet); for (Set other : otherSets) { union.addAll(other); } return union; } /** * Return a list containing the union of all provided collections. We use a {@link ArrayList} and * the result preserves duplicates between and within the collections. *

* We use two separate arguments to ensure on the interface level that at least one collection is * provided. This is transparent for the caller. *

* All arguments can be null. The result will always be non-null and will be an empty set if all * arguments are null. */ @SafeVarargs public static ArrayList unionList(Collection collection1, Collection... furtherCollections) { return unionCollection(ArrayList::new, collection1, furtherCollections); } /** * Return a set containing the union of all elements of the provided sets. We use a {@link HashSet}, * i.e. the elements should support hashing. */ public static HashSet unionSetAll(Collection> sets) { HashSet result = new HashSet<>(); for (Collection set : sets) { result.addAll(set); } return result; } /** * Creates a new set only containing those elements of the given collection that are not in * elementsToRemove. Subtracts elementsToRemove from collection. Both collections may be * {@code null}. */ public static Set subtract(@Nullable Collection collection, @Nullable Collection elementsToRemove) { Set result = new HashSet<>(); if (collection != null) { result.addAll(collection); } if (elementsToRemove != null) { result.removeAll(elementsToRemove); } return result; } /** * Adds all elements of collection2 to collection1. collection2 may be {@code null}, in which case * nothing happens. */ public static void addAllSafe(Collection collection1, @Nullable Collection collection2) { if (collection2 != null) { collection1.addAll(collection2); } } /** * Return a set containing the intersection of all provided collections. We use a {@link HashSet}, * i.e. the elements should support hashing. *

* We use two separate arguments to ensure on the interface level that at least one collection is * provided. This is transparent for the caller. */ @SafeVarargs public static HashSet intersectionSet(Collection collection1, Collection... furtherCollections) { HashSet result = new HashSet<>(collection1); for (Collection collection : furtherCollections) { if (collection instanceof Set) { result.retainAll(collection); } else { // if the collection is not already a set, it will be // significantly faster to first build a set, to speed up the // containment query in the following call. result.retainAll(new HashSet<>(collection)); } } return result; } /** * Returns the set-theoretic difference between the first and the additional collections, i.e. a set * containing all elements that occur in the first, but not in any of the other collections. We use * a {@link HashSet}, so the elements should support hashing. */ @SafeVarargs public static HashSet differenceSet(Collection collection1, Collection... furtherCollections) { HashSet result = new HashSet<>(collection1); for (Collection collection : furtherCollections) { if (collection instanceof Set) { result.removeAll(collection); } else { // if the collection is not already a set, it will be // significantly faster to first build a set, to speed up the // containment query in the following call. result.removeAll(new HashSet<>(collection)); } } return result; } /** * Returns the set-theoretic difference between the first and the additional collections, based on * the output of the provided {@code keyCalculator}. *

* This method supports duplicate keys. In case multiple values from the {@code initialCollection} * are mapped to the same key, they are either all removed or all retained. * * @param keyCalculator * Calculates the key {@code K}, which will be used for element comparison. * @param initialCollection * collection upon which the difference will be calculated. * @param furtherCollections * Collections containing elements that should be removed from the * {@code initialCollection} * @param * collection value type * @param * key type used to distinguish values of {@code T}. Should implement a meaningful * {@link Object#equals(Object) equals()} and {@link Object#hashCode() hashCode()}. * @return difference set, based on the calculated values of {@code K} * @apiNote The result is no {@link Set}, as it is possible that equal elements are evaluated to a * different key. */ @SafeVarargs public static Collection difference(Function keyCalculator, Collection initialCollection, Collection... furtherCollections) { ListMap valuesByKey = initialCollection.stream().collect(ListMapCollector.groupingBy(keyCalculator)); for (Collection collection : furtherCollections) { for (T element : collection) { valuesByKey.removeCollection(keyCalculator.apply(element)); } } return valuesByKey.getValues(); } /** Checks whether collection is null or empty */ public static boolean isNullOrEmpty(@Nullable Collection collection) { return collection == null || collection.isEmpty(); } /** Checks whether a map is null or empty */ public static boolean isNullOrEmpty(@Nullable Map map) { return map == null || map.isEmpty(); } /** Checks whether a {@link PairList} is null or empty */ public static boolean isNullOrEmpty(@Nullable PairList pairs) { return pairs == null || pairs.isEmpty(); } /** * Truncates the given list by removing elements from the end such that numElements entries remain. * If the list has less than numElements entries, it remains unchanged. Thus, this method ensures * that the size of the list is <= numElements. */ public static void truncateEnd(List list, int numElements) { if (list.size() > numElements) { list.subList(numElements, list.size()).clear(); } } /** * Divides the given set randomly but, as far as possible, evenly into k subsets of equal size, * i.e., if set.size() is not a multiple of k, (k-(set.size() modulo k)) of the resulting sets have * one element less. * * @param * must support hashing. */ public static List> divideEvenlyIntoKSubsets(Set set, int k, Random rnd) { int n = set.size(); CCSMAssert.isTrue(n >= k, "The size of the set must be at least k"); CCSMAssert.isTrue(k > 0, "k must be greater than 0"); List shuffled = new ArrayList<>(set); Collections.shuffle(shuffled, rnd); List> result = new ArrayList<>(); // we can fill (n%k) buckets with (n/k+1) elements int subsetSize = n / k + 1; for (int i = 0; i < n % k; i++) { result.add(new HashSet<>(shuffled.subList(i * subsetSize, (i + 1) * subsetSize))); } int offset = n % k * subsetSize; // fill the rest of the buckets with (n/k) elements subsetSize = n / k; for (int i = 0; i < k - n % k; i++) { result.add(new HashSet<>(shuffled.subList(offset + i * subsetSize, offset + (i + 1) * subsetSize))); } return result; } /** Obtain all permutations of the provided elements. */ public static List> getAllPermutations(T... elements) { List> result = new ArrayList<>(); permute(Arrays.asList(elements), 0, result); return result; } /** Recursively creates permutations. */ private static void permute(List list, int index, List> result) { for (int i = index; i < list.size(); i++) { Collections.swap(list, i, index); permute(list, index + 1, result); Collections.swap(list, index, i); } if (index == list.size() - 1) { result.add(new ArrayList<>(list)); } } /** * Returns the power set of the given input list. Note that elements are treated as unique, i.e. we * do not really use set semantics here. Also note that the returned list has 2^n elements for n * input elements, so the input list should not be too large. */ public static List> getPowerSet(List input) { return getPowerSet(input, 0); } /** * Returns the power set of the given input list, only considering elements at or after index * start. */ private static List> getPowerSet(List input, int start) { ArrayList> result = new ArrayList<>(); if (start >= input.size()) { result.add(new ArrayList<>()); } else { T element = input.get(start); for (List list : getPowerSet(input, start + 1)) { List copy = new ArrayList<>(); copy.add(element); copy.addAll(list); result.add(list); result.add(copy); } } return result; } /** * Returns the input list, or {@link #emptyList()} if the input is null. */ public static List emptyIfNull(@Nullable List input) { if (input == null) { return emptyList(); } return input; } /** * Returns the input array as list, or {@link #emptyList()} if the input is null. */ public static List emptyIfNull(T @Nullable [] input) { if (input == null) { return emptyList(); } return Arrays.asList(input); } /** * Returns the input set, or {@link #emptySet()} if the input is null. */ public static Set emptyIfNull(@Nullable Set input) { if (input == null) { return emptySet(); } return input; } /** * Returns the input map, or uses the given supplier to create a new one. */ public static > M emptyIfNull(@Nullable M input, Supplier supplier) { return Optional.ofNullable(input).orElseGet(supplier); } /** * If the given Optional is empty, returns an empty list. Otherwise, returns a single-item list with * the Optional's value. */ public static List emptyListIfEmpty(Optional input) { return input.map(Arrays::asList).orElseGet(CollectionUtils::emptyList); } /** Removes an element from the array and returns the new array. */ @SuppressWarnings("unchecked") public static T[] removeElementFromArray(T element, T[] array) { ArrayList result = new ArrayList<>(Arrays.asList(array)); result.remove(element); return toArray(result, (Class) element.getClass()); } /** * Returns a new list containing only the non-null elements of the given list. */ public static List<@NonNull T> filterNullEntries(Collection<@Nullable T> list) { // noinspection NullableProblems return filter(list, Objects::nonNull); } /** * Returns a new list containing only the non-null elements of the given array. */ public static List filterNullEntries(@Nullable T[] array) { return filterNullEntries(Arrays.asList(array)); } /** * Concatenate two arrays to a new one. * * @see stackoverflow */ public static T[] concatenateArrays(T[] a, T[] b) { int length1 = a.length; int length2 = b.length; @SuppressWarnings("unchecked") T[] newArray = (T[]) Array.newInstance(a.getClass().getComponentType(), length1 + length2); System.arraycopy(a, 0, newArray, 0, length1); System.arraycopy(b, 0, newArray, length1, length2); return newArray; } /** Returns if any element in the collection matches the predicate. */ public static boolean anyMatch(Collection collection, Predicate predicate) { for (T element : collection) { if (predicate.test(element)) { return true; } } return false; } /** Returns if all elements in the collection match the predicate. */ public static boolean allMatch(Collection collection, Predicate predicate) { for (T element : collection) { if (!predicate.test(element)) { return false; } } return true; } /** * Returns a valid {@link #topSort(Collection, Function)} for the inverted getSuccessors function. * * @see */ private static Pair, Set> bottomSort(Collection collection, Function> getSuccessors) { Pair, Set> topSortResult = topSort(collection, getSuccessors); return Pair.createPair(CollectionUtils.reverse(topSortResult.getFirst()), topSortResult.getSecond()); } /** * Variant of {@link #bottomSort(Collection, Function)} which expects no cycles. * * @throws AssertionError * In case a cycle is present. */ public static List bottomSortNoCyclesExpected(Collection collection, Function> getSuccessors) { Pair, Set> topSortResult = bottomSort(collection, getSuccessors); CCSMAssert.isTrue(topSortResult.getSecond().isEmpty(), () -> "Expected no cycles for bottom sort of " + collection + " but encountered cycle " + topSortResult.getSecond()); return topSortResult.getFirst(); } /** * Variant of {@link #topSort(Collection, Function)} which expects no cycles. * * @throws AssertionError * In case a cycle is present. */ public static List topSortNoCyclesExpected(Collection collection, Function> getSuccessors) { Pair, Set> topSortResult = topSort(collection, getSuccessors); CCSMAssert.isTrue(topSortResult.getSecond().isEmpty(), () -> "Expected no cycles for top sort of " + collection + " but encountered cycle " + topSortResult.getSecond()); return topSortResult.getFirst(); } /** * Sorts the given collection topologically. The given getSuccessors function must * establish an order on the elements of the collection (more details below). *

* The sorting is stable if the stream() iterator on the given collection is stable. *

* The result of this function is a Pair. The first item of the pair contains the * sorted collection as a list. The second item contains a set of elements that could not be fit * into the topological order (in case there are cycles in the partial order). This second item is * empty if there are no cycles. Elements of the second item are not contained in the sorted list, * so callers should check whether the second item is empty. *

* Direction of the sorting: if getSuccessors(A).contains(B), then A will * be before B in the sorted list. *

* Details on the order established by getSuccessors: In literature, this should be a * partial order, but we don't seem to require the properties of partial orders (reflexivity, * transitivity, antisymmetry). We just need an order relation that maps each element to its direct * successors. In fact this method will be faster when the order relation is smaller, so forget * reflexivity and transitivity. Antisymmetry would be nice (otherwise you'll get a cycle for sure). *

* If getSuccessors returns null for an element, we will treat this as if it was an * empty collection. * * @param collection * the collection to be sorted * @param getSuccessors * the order relation (described above) * @return a topologically sorted {@link List} and elements of cycles (in a {@link Set} with no * deterministic order). */ public static Pair, Set> topSort(Collection collection, Function> getSuccessors) { UnmodifiableSet inputSet = CollectionUtils.asUnmodifiable(new HashSet<>(collection)); // counts how many unhandled predecessors an element has // elements not in this list have no unhandled predecessors CounterSet numUnhandledPredecessors = new CounterSet<>(); for (T element : collection) { Collection successors = getSuccessors.apply(element); if (successors != null) { successors.stream().filter(inputSet::contains).forEach(numUnhandledPredecessors::inc); } } // elements that have no predecessors (they can be inserted in the // sorted list) Deque freeElements = collection.stream().filter(element -> !numUnhandledPredecessors.contains(element)) .collect(Collectors.toCollection(ArrayDeque::new)); List result = new ArrayList<>(collection.size()); while (!freeElements.isEmpty()) { T element = freeElements.remove(); result.add(element); handleTopSortElementSuccessors(getSuccessors.apply(element), numUnhandledPredecessors, freeElements, inputSet); } return new Pair<>(result, numUnhandledPredecessors.getKeys()); } /** * This method is part of the {@link #topSort(Collection, Function)} algorithm. It handles the * successors of one element that has been inserted in the top-sorted list. *

* This method considers only successors that are part of the given filterSet. For each of these * successors, it decreases the numUnhandledPredecessors entry by one. If a successor has no * predecessors anymore after the decrease, it is added to the given freeElements. */ private static void handleTopSortElementSuccessors(@Nullable Collection successors, CounterSet numUnhandledPredecessors, Deque freeElements, UnmodifiableSet filterSet) { if (successors != null) { successors.stream().filter(filterSet::contains).forEach(successor -> { numUnhandledPredecessors.inc(successor, -1); if (numUnhandledPredecessors.getValue(successor) <= 0) { freeElements.add(successor); numUnhandledPredecessors.remove(successor); } }); } } /** * Sorts the given collection topologically, even if it contains cycles (deterministically random * elements in the cycle are removed). The given getSuccessors function must establish * an order on the elements of the collection (more details below). The given * getPredecessors function must establish the reverse order of * getSuccessors. *

* * The sorting is stable if the stream() iterator on the given collection is stable. *

* The result of this function is a Pair. The first item of the pair contains the * sorted collection as a list. The second item contains a list of cycle elements that were removed * to enable topological sorting. *

* Direction of the sorting: if getSuccessors(A).contains(B), then A will * be before B in the sorted list. *

* For detail requirements on getSuccessors and getPredecessors, see * {@link #topSort(Collection, Function)}. * * @param collection * the collection to be sorted * @param getSuccessors * the order relation (described above) * @return a topologically sorted collection */ public static > Pair, Set> topSortRemoveCycles(Collection collection, Function> getSuccessors, Function> getPredecessors) { // First remove all nodes that have trivial cycle references to themselves Set removedCycleElements = collection.stream().filter(node -> { Collection successors = getSuccessors.apply(node); return successors != null && successors.contains(node); }).collect(Collectors.toSet()); List reducedCollection = new ArrayList<>(collection); reducedCollection.removeIf(removedCycleElements::contains); Pair, Set> topSorted = topSort(reducedCollection, getSuccessors); if (topSorted.getSecond().isEmpty()) { return new Pair<>(topSorted.getFirst(), removedCycleElements); } /* * Implementation idea: After remove non-cycle vertices alternately from "top" and "bottom" and * store them for the result. From the resulting cycle, remove one vertex and repeat. */ List tailElements = new ArrayList<>(); List headElements = topSorted.getFirst(); List cycle = CollectionUtils.sort(topSorted.getSecond()); while (!cycle.isEmpty()) { Pair, Set> inverseTopSortedCycle = topSort(cycle, getPredecessors); tailElements.addAll(inverseTopSortedCycle.getFirst()); cycle = CollectionUtils.sort(inverseTopSortedCycle.getSecond()); if (!cycle.isEmpty()) { removedCycleElements.add(cycle.remove(cycle.size() - 1)); } Pair, Set> topSortedCycle = topSort(cycle, getSuccessors); headElements.addAll(topSortedCycle.getFirst()); cycle = CollectionUtils.sort(topSortedCycle.getSecond()); } headElements.addAll(reverse(tailElements)); return new Pair<>(headElements, removedCycleElements); } /** * Returns a {@link Comparator} that compares lists. Shorter lists are considered "smaller" than * longer lists. If lists have the same length, elements are compared using their compareTo methods * until one is found that is does not return 0 when compared to its counterpart. If list lengths * are equal and all elements return 0 on comparison, the returned {@link Comparator} returns 0. */ public static , T extends Comparable> Comparator getListComparator() { return getListComparator(T::compareTo); } /** * Returns a {@link Comparator} that compares lists. Shorter lists are considered "smaller" than * longer lists. If lists have the same length, elements are compared using the given * elementComparator until one is found that is does not return 0 when compared to its counterpart. * If list lengths are equal and all elements return 0 on comparison, the returned * {@link Comparator} returns 0. */ public static , T> Comparator getListComparator(Comparator elementComparator) { return (o1, o2) -> { if (o1.size() != o2.size()) { return o1.size() - o2.size(); } for (int i = 0; i < o1.size(); i++) { int currentComparison = elementComparator.compare(o1.get(i), o2.get(i)); if (currentComparison != 0) { return currentComparison; } } return 0; }; } /** * Returns a {@link Comparator} that compares {@link Pair}s of {@link Comparable}s. First compares * the first elements and if their comparison returns 0, compares the second elements. If their * comparison also return 0, this {@link Comparator} returns 0. */ public static

, T extends Comparable, S extends Comparable> Comparator

getPairComparator() { return Comparator.comparing((Function) ImmutablePair::getFirst).thenComparing(ImmutablePair::getSecond); } /** Returns an empty {@link Consumer} that does nothing. */ public static Consumer emptyConsumer() { return x -> { // empty }; } /** * Splits a {@link String} representing a list of values to a list of lines and forwards it to * {@link CollectionUtils#parseMultiValueStringToList(List, boolean)}. */ public static List parseMultiValueStringToList(String valueList, boolean filterOutLineComments) { List lines = StringUtils.splitWithEscapeCharacter(valueList, '\n'); return parseMultiValueStringToList(lines, filterOutLineComments); } /** * Parses a {@link List} of {@link String} representing lines of values to a list of the single * values. The value entries must be separated by {@value #MULTI_VALUE_DELIMITER}. */ public static List parseMultiValueStringToList(List valueListLines, boolean filterOutLineComments) { List results = new ArrayList<>(); for (String line : valueListLines) { if (filterOutLineComments && line.startsWith(CONNECTOR_CONFIG_MULTI_VALUE_INPUT_LINE_COMMENT_PREFIX)) { continue; } // Remove trailing and leading ',' line = StringUtils.strip(line, MULTI_VALUE_DELIMITER.toString()); List result = StringUtils.splitWithEscapeCharacter(line, MULTI_VALUE_DELIMITER); for (String value : result) { if (StringUtils.isEmpty(value)) { throw new IllegalArgumentException( "Found duplicate comma (empty value) in list: " + valueListLines); } } results.addAll(result); } return results; } /** * Creates a {@link List} that holds the combination of the values of two collections. * * @see #combine(Collection, Collection, Collection, BiFunction) */ public static List combine(Collection firstValues, Collection secondValues, BiFunction combineFunction) { List resultList = new ArrayList<>(firstValues.size()); return combine(firstValues, secondValues, resultList, combineFunction); } /** * Creates a {@link Collection} that holds the combination of the values of two collections. *

* Both collections must be of the same size. The order of insertion into the new {@link Collection} * is determined by the order imposed by the collections' iterators. */ public static > COLLECTION combine(Collection firstValues, Collection secondValues, COLLECTION resultCollection, BiFunction combineFunction) { CCSMAssert.isTrue(firstValues.size() == secondValues.size(), "Can only combine collections of the same size."); Iterator firstIterator = firstValues.iterator(); Iterator secondIterator = secondValues.iterator(); while (firstIterator.hasNext()) { resultCollection.add(combineFunction.apply(firstIterator.next(), secondIterator.next())); } return resultCollection; } /** * Applies the function to the values of both collections at the same index. *

* Both collections must be of the same size. */ public static void forEach(Iterable firstValues, Iterable secondValues, BiConsumer combineFunction) { Iterator firstIterator = firstValues.iterator(); Iterator secondIterator = secondValues.iterator(); while (firstIterator.hasNext() && secondIterator.hasNext()) { combineFunction.accept(firstIterator.next(), secondIterator.next()); } CCSMAssert.isTrue(firstIterator.hasNext() == secondIterator.hasNext(), "Can only combine Iterables of the same size."); } /** For-each with an exception over an iterable. */ public static void forEach(Iterable values, ConsumerWithException consumer) throws E { for (T1 value : values) { consumer.accept(value); } } /** * Applies the function to the values of both collections at the same index. *

* Both collections must be of the same size. */ public static void forEachWithException(Collection firstValues, Collection secondValues, BiConsumerWithException combineFunction) throws E { CCSMAssert.isTrue(firstValues.size() == secondValues.size(), "Can only combine collections of the same size."); Iterator firstIterator = firstValues.iterator(); Iterator secondIterator = secondValues.iterator(); while (firstIterator.hasNext()) { combineFunction.accept(firstIterator.next(), secondIterator.next()); } } /** * Provides an {@link Iterator}, which iterates over all provided collections while globally * maintaining the sorting order. Duplicates (identified by * {@link Comparator#compare(Object, Object) Comparator.compare}{@code (...) == 0}) are only * returned once. *

* For example consider this: * *

	 *     SortedSet<Integer> s1 = new TreeSet<>(Arrays.asList(1, 5, 10));
	 *     SortedSet<Integer> s2 = new TreeSet<>(Arrays.asList(0, 1, 3, 4, 7, 11));
	 *
	 *     List<Integer< result = new ArrayList<>();
	 *     sortedIterator(s1, s2).forEachRemaining(result::add);
	 *     // Will print 0, 1, 3, 4, 5, 7, 10, 11
	 *     System.out.println(result);
	 * 
*

* It is expected, that all provided collections utilize the same ordering, otherwise the behavior * is undefined. *

* The provided collections must not contain {@code null} elements. *

* The {@link Iterator#remove()} operation is supported if the iterators returned by the various * collections support this. In case the element to remove occurred in multiple of the input sets * (i.e. was a duplicate), it will also be deleted from all of them. */ @SafeVarargs public static Iterator sortedIterator(SortedSet first, SortedSet second, SortedSet... further) { Comparator comparator = first.comparator(); if (comparator == null) { // only happens when natural ordering is used @SuppressWarnings("unchecked") Comparator tmp = (Comparator) Comparator.naturalOrder(); comparator = tmp; } List> collections = new ArrayList<>(2 + further.length); collections.add(first); collections.add(second); collections.addAll(Arrays.asList((Collection[]) further)); return sortedIterator(comparator, collections); } /** * Provides an {@link Iterator}, which iterates over all provided collections while globally * maintaining the sorting order. Duplicates (identified by * {@link Comparator#compare(Object, Object) Comparator.compare}{@code (...) == 0}) are only * returned once. *

* For example consider this: * *

	 *     List<Integer> s1 = Arrays.asList(1, 5, 10);
	 *     List<Integer> s2 = Arrays.asList(0, 1, 3, 4, 7, 11);
	 *
	 *     List<Integer< result = new ArrayList<>();
	 *     sortedIterator(Comparator.naturalOrder(), List.of(s1, s2)).forEachRemaining(result::add);
	 *     // Will print 0, 1, 3, 4, 5, 7, 10, 11
	 *     System.out.println(result);
	 * 
*

* It is expected, that all provided collections utilize the same ordering, otherwise the behavior * is undefined. *

* The provided collections must not contain {@code null} elements. *

* The {@link Iterator#remove()} operation is supported if the iterators returned by the various * collections support this. In case the element to remove occurred in multiple of the input sets * (i.e. was a duplicate), it will also be deleted from all of them. */ @NonNull public static Iterator sortedIterator(Comparator comparator, Collection> collections) { return new SortingIterator<>(comparator, collections); } /** * @return Whether the provided {@code list} ends with the provided {@code elements}. */ @SafeVarargs public static boolean endsWith(List list, T... elements) { return endsWith(list, Function.identity(), elements); } /** * @return Whether the provided {@code list} ends with the provided {@code elements} when mapping * the list content with the {@code listElementMapper}. */ @SafeVarargs public static boolean endsWith(List list, Function listElementMapper, U... elements) { if (list.size() < elements.length) { return false; } ListIterator listIterator = list.listIterator(list.size() - elements.length); for (U element : elements) { if (!Objects.equals(listElementMapper.apply(listIterator.next()), element)) { return false; } } return true; } /** * Iterator implementation for {@link #sortedIterator(Comparator, Collection)}. */ private static class SortingIterator implements Iterator { /** * Comparator to use for the global ordering. */ private final Comparator comparator; /** * Contains all iterators, with their last returned {@link #next()} value, that was not yet returned * from this iterator. */ // Use IdentityHashMap as there is no guarantee for the hashCode of Iterator to // be stable private final Map, Optional> iteratorsWithNextValue = new IdentityHashMap<>(); /** * All iterators that contained the last returned value from {@link #next()}. Is required for the * support of {@link #remove()}. */ private final List> lastResultIterators = new ArrayList<>(); private SortingIterator(Comparator comparator, Collection> collections) { // Support the initial null result in the comparator this.comparator = Comparator.nullsLast(comparator); for (Iterable collection : collections) { iteratorsWithNextValue.put(collection.iterator(), Optional.empty()); } } @Override public boolean hasNext() { return iteratorsWithNextValue.entrySet().stream() .anyMatch(entry -> entry.getValue().isPresent() || entry.getKey().hasNext()); } @Override public T next() { lastResultIterators.clear(); T result = null; for (Iterator, Optional>> iterator = iteratorsWithNextValue.entrySet() .iterator(); iterator.hasNext();) { Map.Entry, Optional> entry = iterator.next(); T nextCandidate = getCandidateFromIterator(entry); if (nextCandidate == null) { // Iterator is exhausted, so no need to keep it anymore iterator.remove(); continue; } int compare = comparator.compare(result, nextCandidate); if (compare > 0) { // new best result found, so remove all already found values lastResultIterators.clear(); lastResultIterators.add(entry.getKey()); result = nextCandidate; } else if (compare == 0) { // Store the iterator, in case the element should get removed lastResultIterators.add(entry.getKey()); } } if (lastResultIterators.isEmpty()) { // No result element could be found, because all iterators are exhausted throw new NoSuchElementException(); } lastResultIterators.forEach(iter -> iteratorsWithNextValue.put(iter, Optional.empty())); return result; } private T getCandidateFromIterator(Map.Entry, Optional> entry) { Optional nextFromIter = entry.getValue(); Iterator iterator = entry.getKey(); if (!nextFromIter.isPresent()) { if (iterator.hasNext()) { nextFromIter = Optional.of(iterator.next()); // We are not sure, whether this value will actually be returned. // So store it in the map for later use. entry.setValue(nextFromIter); } else { return null; } } return nextFromIter.get(); } @Override public void remove() { if (lastResultIterators.isEmpty()) { // Happens when next() was not yet called throw new IllegalStateException(); } lastResultIterators.forEach(Iterator::remove); } } /** * Returns the element index of the first element in the list that matches the given predicate. * Returns -1 if no element matches the predicate. */ public static int indexOfFirstMatch(List list, Predicate predicate) { int listSize = list.size(); for (int i = 0; i < listSize; i++) { if (predicate.test(list.get(i))) { return i; } } return -1; } /** * Returns a map that maps each element of the first list to the corresponding (by index) element of * the second list. The lists must have the same size. */ public static Map zipAsMap(List first, List second) { CCSMAssert.isTrue(first.size() == second.size(), () -> "Need the same number of elements in the given lists. Found " + first.size() + " / " + second.size()); return zipToMap(first.iterator(), second.iterator()); } /** * Returns a map that maps each element of the first iterator to the corresponding (by iteration * order) element of the second iterator. The iterators must return the same amount of elements. */ public static Map zipToMap(Iterator first, Iterator second) { Map resultMap = new HashMap<>(); forEach(asIterable(first), asIterable(second), resultMap::put); return resultMap; } /** * Wrapper for {@link List#subList(int, int)} with only a from parameter. *

* Returns a view of the portion of this list between the specified fromIndex, inclusive, * and the end of the list. The returned list is backed by this list, so non-structural changes in * the returned list are reflected in this list, and vice-versa. * * @param fromIndex * low endpoint (inclusive) of the subList * @return a view of the specified range within this list * @throws IndexOutOfBoundsException * for an illegal endpoint index value * (fromIndex < 0 || fromIndex > size) */ public static List subListFrom(List list, int fromIndex) { return list.subList(fromIndex, list.size()); } /** * Returns an {@link ArrayList} filled with the given count of objects created by the factory * function. The index of the element being created is passed to the factory function. */ public static ArrayList repeat(Function factory, int count) { CCSMAssert.isFalse(count < 0, () -> "Repeating objects for negative number " + count + " of objects is not possible."); ArrayList result = new ArrayList<>(count); for (int i = 0; i < count; i++) { result.add(factory.apply(i)); } return result; } /** * Returns a {@link Predicate} that performs the test of {@code predicate} on the key of a * {@link Map.Entry}. */ public static Predicate> onKey(Predicate predicate) { return entry -> predicate.test(entry.getKey()); } /** * Returns a {@link Predicate} that performs the test of {@code predicate} on the value of a * {@link Map.Entry}. */ public static Predicate> onValue(Predicate predicate) { return entry -> predicate.test(entry.getValue()); } /** * Takes the source map and inverts it. Assumes that the source map represents a bijective mapping * and throws an error if the mappings isn't bijective. * * @return null if the parameter sourceMap is null. */ public static @Nullable Map inverseMap(@Nullable Map sourceMap) { if (sourceMap == null) { return null; } return sourceMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)); } /** * Returns a list consisting of the first (at most) n elements of the input list. * * @return null if the parameter list is null. */ public static @Nullable List limit(@Nullable List list, int n) { if (list == null || list.size() <= n) { return list; } return list.subList(0, n); } /** * Exposes the provided {@link Iterator} in an {@link Iterable}. *

* As always the same {@link Iterator} is provided by the returned {@link Iterable}, it (the * Iterable) should not be reused. */ public static Iterable asIterable(@NonNull Iterator iterator) { CCSMAssert.isNotNull(iterator, () -> String.format("Expected \"%s\" to be not null", "iterator")); return () -> iterator; } /** Exposes the provided {@link Supplier} as {@link Iterable}. */ public static Iterable asIterable(@NonNull Supplier<@NonNull Iterator> iteratorSupplier) { CCSMAssert.isNotNull(iteratorSupplier, () -> String.format("Expected \"%s\" to be not null", "iteratorSupplier")); return iteratorSupplier::get; } /** * Combines the provided iterators into a single iterator of {@link Pair}s. Both iterators must * return the same amount of elements. The resulting iterator supports the {@link Iterator#remove() * remove} operation, if both provided iterators support it. */ public static Iterator> zipIterators(@NonNull Iterator iterator1, @NonNull Iterator iterator2) { CCSMAssert.isNotNull(iterator1, () -> String.format("Expected \"%s\" to be not null", "iterator1")); CCSMAssert.isNotNull(iterator2, () -> String.format("Expected \"%s\" to be not null", "iterator2")); return new Iterator>() { @Override public boolean hasNext() { boolean i1Next = iterator1.hasNext(); boolean i2Next = iterator2.hasNext(); CCSMAssert.isTrue(i1Next == i2Next, "Received iterators with different element count"); return i1Next; } @Override public Pair next() { return new Pair<>(iterator1.next(), iterator2.next()); } @Override public void remove() { iterator1.remove(); iterator2.remove(); } }; } /** * Takes the source map and filters it by using the given predicate on the values. */ public static Map filterMapByValue(@NonNull Map sourceMap, Predicate predicate) { Map filteredMap = new HashMap<>(); for (Map.Entry entry : sourceMap.entrySet()) { if (predicate.test(entry.getValue())) { filteredMap.put(entry.getKey(), entry.getValue()); } } return filteredMap; } /** * Attempts to find the given key in a list of other keys. The other list is represented in an * abstract fashion by a size and accessor method. Returns the index of the key if it is contained * in the key list. Otherwise, returns -(insertion_point) -1. * * @param size * the size of the list to search in * @param accessor * function for accessing the i-th element in the search list * @see Collections#binarySearch(java.util.List, Object) */ public static int binarySearch(T key, int size, IntFunction accessor, Comparator comparator) { int lower = 0; int upper = size; if (upper == 0) { // no data, insert at 0 return -1; } while (lower < upper) { // For next line see // http://googleresearch.blogspot.com/2006/06/extra-extra-read-all-about-it-nearly.html int middle = lower + upper >>> 1; int compare = comparator.compare(accessor.apply(middle), key); if (compare == 0) { return middle; } if (compare < 0) { lower = middle + 1; } else { upper = middle; } } // convert to insertion point return -lower - 1; } /** * Provides a {@link BinaryOperator}, that immediately throws an {@link IllegalStateException} when * called. Should usually be used as {@code mergeFunction} for * {@link Collectors#toMap(Function, Function, BinaryOperator, Supplier)} in order to keep the same * behavior as {@link Collectors#toMap(Function, Function)}. * * @param keyMapper * Mapper to extract the duplicate key of a {@code T} instance. */ public static BinaryOperator throwingMerger(Function keyMapper) { CCSMAssert.isNotNull(keyMapper, () -> String.format("Expected \"%s\" to be not null", "keyMapper")); return (t1, t2) -> { throw new IllegalStateException(String.format("Duplicate key %s (attempted merging values %s and %s)", keyMapper.apply(t1), t1, t2)); }; } /** * Returns a Collector that accumulates the input elements into a new {@link ArrayList}, in * encounter order. *

* In contrast to {@link Collectors#toList()} and {@link java.util.stream.Stream#toList()}, this * method guarantees that the returned list is an instance of {@link ArrayList} and, * therefore, is mutable, serializable (if the elements are), and not thread-safe. */ public static Collector> toArrayList() { return Collectors.toCollection(ArrayList::new); } /** * Returns a Collector that accumulates the input elements into a new {@link UnmodifiableList}, in * encounter order. *

* In contrast to {@link Collectors#toList()} and {@link java.util.stream.Stream#toList()}, this * method guarantees that the returned list is an instance of {@link UnmodifiableList} and, * therefore, is immutable, serializable (if the elements are), and not thread-safe. */ public static Collector> toUnmodifiableList() { return Collectors.collectingAndThen(toArrayList(), CollectionUtils::asUnmodifiable); } /** * @return The result of applying the {@link Collector#finisher()} on the result of the * {@link Collector#supplier()}, without providing any elements to the * {@link Collector#accumulator()}. */ public static R emptyResult(Collector collector) { return collector.finisher().apply(collector.supplier().get()); } /** * Sets the values at the given indices in the given target list to the given values. */ public static void setValuesAtIndices(List targetList, List indicesToSet, List valuesToSet) { for (int i = 0; i < indicesToSet.size(); i++) { targetList.set(indicesToSet.get(i), valuesToSet.get(i)); } } /** * Computes the capacity for a {@link HashMap} given the {@code expectedSize} of the map and its * {@code loadFactor}. This can be used to compute an initial capacity for a known number of map * elements such that the map does not have to grow while adding the elements. */ @Contract(pure = true) public static int computeHashMapCapacity(int expectedSize, float loadFactor) { return (int) Math.ceil(expectedSize / loadFactor); } }