/* * Copyright 2011 the original author or authors. * * 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.gradle.util; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; import org.gradle.api.Action; import org.gradle.api.Nullable; import org.gradle.api.Transformer; import org.gradle.api.specs.Spec; import org.gradle.internal.Cast; import org.gradle.internal.Factory; import org.gradle.internal.Pair; import org.gradle.internal.Transformers; import java.lang.reflect.Array; import java.util.AbstractList; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import static org.gradle.internal.Cast.cast; public abstract class CollectionUtils { /** * Returns null if the collection is empty otherwise expects a {@link #single(Iterable)} element to be found. */ @Nullable public static <T> T findSingle(Collection<T> source) { return source.isEmpty() ? null : single(source); } /** * Returns the single element in the collection or throws. */ public static <T> T single(Iterable<? extends T> source) { Iterator<? extends T> iterator = source.iterator(); if (!iterator.hasNext()) { throw new NoSuchElementException("Expecting collection with single element, got none."); } T element = iterator.next(); if (iterator.hasNext()) { throw new IllegalArgumentException("Expecting collection with single element, got multiple."); } return element; } public static <T> Collection<? extends T> checkedCast(Class<T> type, Collection<?> input) { for (Object o : input) { cast(type, o); } return Cast.uncheckedCast(input); } public static <T> T findFirst(Iterable<? extends T> source, Spec<? super T> filter) { for (T item : source) { if (filter.isSatisfiedBy(item)) { return item; } } return null; } public static <T> T findFirst(T[] source, Spec<? super T> filter) { for (T thing : source) { if (filter.isSatisfiedBy(thing)) { return thing; } } return null; } public static <T> T first(Iterable<? extends T> source) { return source.iterator().next(); } public static <T> boolean any(Iterable<? extends T> source, Spec<? super T> filter) { return findFirst(source, filter) != null; } public static <T> boolean any(T[] source, Spec<? super T> filter) { return findFirst(source, filter) != null; } public static <T> Set<T> filter(Set<? extends T> set, Spec<? super T> filter) { return filter(set, new LinkedHashSet<T>(), filter); } public static <T> List<T> filter(List<? extends T> list, Spec<? super T> filter) { return filter(list, new LinkedList<T>(), filter); } public static <T> List<T> filter(T[] array, Spec<? super T> filter) { return filter(Arrays.asList(array), new LinkedList<T>(), filter); } /** * Returns a sorted copy of the provided collection of things. Uses the provided comparator to sort. */ public static <T> List<T> sort(Iterable<? extends T> things, Comparator<? super T> comparator) { List<T> copy = toMutableList(things); Collections.sort(copy, comparator); return copy; } /** * Returns a sorted copy of the provided collection of things. Uses the natural ordering of the things. */ public static <T extends Comparable> List<T> sort(Iterable<T> things) { List<T> copy = toMutableList(things); Collections.sort(copy); return copy; } public static <T, C extends Collection<T>> C filter(Iterable<? extends T> source, C destination, Spec<? super T> filter) { for (T item : source) { if (filter.isSatisfiedBy(item)) { destination.add(item); } } return destination; } public static <K, V> Map<K, V> filter(Map<K, V> map, Spec<Map.Entry<K, V>> filter) { return filter(map, new HashMap<K, V>(), filter); } public static <K, V> Map<K, V> filter(Map<K, V> map, Map<K, V> destination, Spec<Map.Entry<K, V>> filter) { for (Map.Entry<K, V> entry : map.entrySet()) { if (filter.isSatisfiedBy(entry)) { destination.put(entry.getKey(), entry.getValue()); } } return destination; } public static <R, I> R[] collectArray(I[] list, Class<R> newType, Transformer<? extends R, ? super I> transformer) { @SuppressWarnings("unchecked") R[] destination = (R[]) Array.newInstance(newType, list.length); return collectArray(list, destination, transformer); } public static <R, I> R[] collectArray(I[] list, R[] destination, Transformer<? extends R, ? super I> transformer) { assert list.length <= destination.length; for (int i = 0; i < list.length; ++i) { destination[i] = transformer.transform(list[i]); } return destination; } public static <R, I> List<R> collect(List<? extends I> list, Transformer<? extends R, ? super I> transformer) { return collect(list, new ArrayList<R>(list.size()), transformer); } public static <R, I> List<R> collect(I[] list, Transformer<? extends R, ? super I> transformer) { return collect(Arrays.asList(list), transformer); } public static <R, I> Set<R> collect(Set<? extends I> set, Transformer<? extends R, ? super I> transformer) { return collect(set, new HashSet<R>(), transformer); } public static <R, I> List<R> collect(Iterable<? extends I> source, Transformer<? extends R, ? super I> transformer) { return collect(source, new LinkedList<R>(), transformer); } public static <R, I, C extends Collection<R>> C collect(Iterable<? extends I> source, C destination, Transformer<? extends R, ? super I> transformer) { for (I item : source) { destination.add(transformer.transform(item)); } return destination; } public static List<String> toStringList(Iterable<?> iterable) { return collect(iterable, new LinkedList<String>(), Transformers.asString()); } /** * Recursively unpacks all the given things into a flat list. * * Nulls are not removed, they are left intact. * * @param things The things to flatten * @return A flattened list of the given things */ public static List<?> flattenCollections(Object... things) { return flattenCollections(Object.class, things); } /** * Recursively unpacks all the given things into a flat list, ensuring they are of a certain type. * * Nulls are not removed, they are left intact. * * If a non null object cannot be cast to the target type, a ClassCastException will be thrown. * * @param things The things to flatten * @param <T> The target type in the flattened list * @return A flattened list of the given things */ public static <T> List<T> flattenCollections(Class<T> type, Object... things) { if (things == null) { return Collections.singletonList(null); } else if (things.length == 0) { return Collections.emptyList(); } else if (things.length == 1) { Object thing = things[0]; if (thing == null) { return Collections.singletonList(null); } // Casts to Class below are to workaround Eclipse compiler bug // See: https://github.com/gradle/gradle/pull/200 if (thing.getClass().isArray()) { Object[] thingArray = (Object[]) thing; List<T> list = new ArrayList<T>(thingArray.length); for (Object thingThing : thingArray) { list.addAll(flattenCollections(type, thingThing)); } return list; } if (thing instanceof Collection) { Collection<?> collection = (Collection<?>) thing; List<T> list = new ArrayList<T>(); for (Object element : collection) { list.addAll(flattenCollections(type, element)); } return list; } return Collections.singletonList(cast(type, thing)); } else { List<T> list = new ArrayList<T>(); for (Object thing : things) { list.addAll(flattenCollections(type, thing)); } return list; } } public static <T> List<T> toList(Iterable<? extends T> things) { if (things instanceof List) { @SuppressWarnings("unchecked") List<T> castThings = (List<T>) things; return castThings; } return toMutableList(things); } public static <T> List<T> toList(Enumeration<? extends T> things) { AbstractList<T> list = new ArrayList<T>(); while (things.hasMoreElements()) { list.add(things.nextElement()); } return list; } private static <T> List<T> toMutableList(Iterable<? extends T> things) { if (things == null) { return new ArrayList<T>(0); } List<T> list = new ArrayList<T>(); for (T thing : things) { list.add(thing); } return list; } public static <T> List<T> intersection(Collection<? extends Collection<T>> availableValuesByDescriptor) { List<T> result = new ArrayList<T>(); Iterator<? extends Collection<T>> iterator = availableValuesByDescriptor.iterator(); if (iterator.hasNext()) { Collection<T> firstSet = iterator.next(); result.addAll(firstSet); while (iterator.hasNext()) { Collection<T> next = iterator.next(); result.retainAll(next); } } return result; } public static <T> List<T> toList(T[] things) { if (things == null || things.length == 0) { return new ArrayList<T>(0); } List<T> list = new ArrayList<T>(things.length); Collections.addAll(list, things); return list; } public static <T> Set<T> toSet(Iterable<? extends T> things) { if (things == null) { return new HashSet<T>(0); } if (things instanceof Set) { @SuppressWarnings("unchecked") Set<T> castThings = (Set<T>) things; return castThings; } Set<T> set = new LinkedHashSet<T>(); for (T thing : things) { set.add(thing); } return set; } public static <E> List<E> compact(List<E> list) { boolean foundAtLeastOneNull = false; List<E> compacted = null; int i = 0; for (E element : list) { if (element == null) { if (!foundAtLeastOneNull) { compacted = new ArrayList<E>(list.size()); if (i > 0) { compacted.addAll(list.subList(0, i)); } } foundAtLeastOneNull = true; } else if (foundAtLeastOneNull) { compacted.add(element); } ++i; } return foundAtLeastOneNull ? compacted : list; } public static <C extends Collection<String>> C stringize(Iterable<?> source, C destination) { return collect(source, destination, Transformers.asString()); } public static List<String> stringize(Collection<?> source) { return stringize(source, new ArrayList<String>(source.size())); } public static <E> boolean replace(List<E> list, Spec<? super E> filter, Transformer<? extends E, ? super E> transformer) { boolean replaced = false; int i = 0; for (E it : list) { if (filter.isSatisfiedBy(it)) { list.set(i, transformer.transform(it)); replaced = true; } ++i; } return replaced; } public static <K, V> void collectMap(Map<K, V> destination, Iterable<? extends V> items, Transformer<? extends K, ? super V> keyGenerator) { for (V item : items) { destination.put(keyGenerator.transform(item), item); } } /** * Given a set of values, derive a set of keys and return a map */ public static <K, V> Map<K, V> collectMap(Iterable<? extends V> items, Transformer<? extends K, ? super V> keyGenerator) { Map<K, V> map = new LinkedHashMap<K, V>(); collectMap(map, items, keyGenerator); return map; } public static <K, V> void collectMapValues(Map<K, V> destination, Iterable<? extends K> keys, Transformer<? extends V, ? super K> keyGenerator) { for (K item : keys) { destination.put(item, keyGenerator.transform(item)); } } /** * Given a set of keys, derive a set of values and return a map */ public static <K, V> Map<K, V> collectMapValues(Iterable<? extends K> keys, Transformer<? extends V, ? super K> keyGenerator) { Map<K, V> map = new LinkedHashMap<K, V>(); collectMapValues(map, keys, keyGenerator); return map; } public static <T> boolean every(Iterable<? extends T> things, Spec<? super T> predicate) { for (T thing : things) { if (!predicate.isSatisfiedBy(thing)) { return false; } } return true; } /** * Utility for adding an iterable to a collection. * * @param t1 The collection to add to * @param t2 The iterable to add each item of to the collection * @param <T> The element type of t1 * @return t1 */ public static <T> Collection<T> addAll(Collection<T> t1, Iterable<? extends T> t2) { for (T t : t2) { t1.add(t); } return t1; } /** * Utility for adding an array to a collection. * * @param t1 The collection to add to * @param t2 The iterable to add each item of to the collection * @param <T> The element type of t1 * @return t1 */ public static <T> Collection<T> addAll(Collection<T> t1, T... t2) { Collections.addAll(t1, t2); return t1; } /** * The result of diffing two sets. * * @param <T> The type of element the sets contain * @see CollectionUtils#diffSetsBy(java.util.Set, java.util.Set, org.gradle.api.Transformer) */ public static class SetDiff<T> { public Set<T> leftOnly = new HashSet<T>(); public Set<Pair<T, T>> common = new HashSet<Pair<T, T>>(); public Set<T> rightOnly = new HashSet<T>(); } /** * Provides a “diff report” of how the two sets are similar and how they are different, comparing the entries by some aspect. * * The transformer is used to generate the value to use to compare the entries by. That is, the entries are not compared by equals by an attribute or characteristic. * * The transformer is expected to produce a unique value for each entry in a single set. Behaviour is undefined if this condition is not met. * * @param left The set on the “left” side of the comparison. * @param right The set on the “right” side of the comparison. * @param compareBy Provides the value to compare entries from either side by * @param <T> The type of the entry objects * @return A representation of the difference */ public static <T> SetDiff<T> diffSetsBy(Set<? extends T> left, Set<? extends T> right, Transformer<?, T> compareBy) { if (left == null) { throw new NullPointerException("'left' set is null"); } if (right == null) { throw new NullPointerException("'right' set is null"); } SetDiff<T> setDiff = new SetDiff<T>(); Map<Object, T> indexedLeft = collectMap(left, compareBy); Map<Object, T> indexedRight = collectMap(right, compareBy); for (Map.Entry<Object, T> leftEntry : indexedLeft.entrySet()) { T rightValue = indexedRight.remove(leftEntry.getKey()); if (rightValue == null) { setDiff.leftOnly.add(leftEntry.getValue()); } else { Pair<T, T> pair = Pair.of(leftEntry.getValue(), rightValue); setDiff.common.add(pair); } } for (T rightValue : indexedRight.values()) { setDiff.rightOnly.add(rightValue); } return setDiff; } /** * Creates a string with {@code toString()} of each object with the given separator. * * <pre> * expect: * join(",", new Object[]{"a"}) == "a" * join(",", new Object[]{"a", "b", "c"}) == "a,b,c" * join(",", new Object[]{}) == "" * </pre> * * The {@code separator} must not be null and {@code objects} must not be null. * * @param separator The string by which to join each string representation * @param objects The objects to join the string representations of * @return The joined string */ public static String join(String separator, Object[] objects) { return join(separator, objects == null ? null : Arrays.asList(objects)); } /** * Creates a string with {@code toString()} of each object with the given separator. * * <pre> * expect: * join(",", ["a"]) == "a" * join(",", ["a", "b", "c"]) == "a,b,c" * join(",", []) == "" * </pre> * * The {@code separator} must not be null and {@code objects} must not be null. * * @param separator The string by which to join each string representation * @param objects The objects to join the string representations of * @return The joined string */ public static String join(String separator, Iterable<?> objects) { if (separator == null) { throw new NullPointerException("The 'separator' cannot be null"); } if (objects == null) { throw new NullPointerException("The 'objects' cannot be null"); } StringBuilder string = new StringBuilder(); Iterator<?> iterator = objects.iterator(); if (iterator.hasNext()) { string.append(iterator.next().toString()); while (iterator.hasNext()) { string.append(separator); string.append(iterator.next().toString()); } } return string.toString(); } /** * Partition given Collection into a Pair of Collections. * * <pre>Left</pre> Collection containing entries that satisfy the given predicate * <pre>Right</pre> Collection containing entries that do NOT satisfy the given predicate */ public static <T> Pair<Collection<T>, Collection<T>> partition(Iterable<T> items, Spec<? super T> predicate) { Preconditions.checkNotNull(items, "Cannot partition null Collection"); Preconditions.checkNotNull(predicate, "Cannot apply null Spec when partitioning"); Collection<T> left = new LinkedList<T>(); Collection<T> right = new LinkedList<T>(); for (T item : items) { if (predicate.isSatisfiedBy(item)) { left.add(item); } else { right.add(item); } } return Pair.of(left, right); } public static class InjectionStep<T, I> { private final T target; private final I item; public InjectionStep(T target, I item) { this.target = target; this.item = item; } public T getTarget() { return target; } public I getItem() { return item; } } public static <T, I> T inject(T target, Iterable<? extends I> items, Action<InjectionStep<T, I>> action) { if (target == null) { throw new NullPointerException("The 'target' cannot be null"); } if (items == null) { throw new NullPointerException("The 'items' cannot be null"); } if (action == null) { throw new NullPointerException("The 'action' cannot be null"); } for (I item : items) { action.execute(new InjectionStep<T, I>(target, item)); } return target; } public static <K, V> Map<K, Collection<V>> groupBy(Iterable<? extends V> iterable, Transformer<? extends K, V> grouper) { ImmutableListMultimap.Builder<K, V> builder = ImmutableListMultimap.builder(); for (V element : iterable) { K key = grouper.transform(element); builder.put(key, element); } return builder.build().asMap(); } public static <T> Iterable<? extends T> unpack(final Iterable<? extends Factory<? extends T>> factories) { return new Iterable<T>() { private final Iterator<? extends Factory<? extends T>> delegate = factories.iterator(); public Iterator<T> iterator() { return new Iterator<T>() { public boolean hasNext() { return delegate.hasNext(); } public T next() { return delegate.next().create(); } public void remove() { throw new UnsupportedOperationException(); } }; } }; } @Nullable public static <T> List<T> nonEmptyOrNull(Iterable<T> iterable) { ImmutableList<T> list = ImmutableList.copyOf(iterable); return list.isEmpty() ? null : list; } public static String asCommandLine(Iterable<String> arguments) { return Joiner.on(" ").join(collect(arguments, Transformers.asSafeCommandLineArgument())); } }