diff --git a/android/guava/src/com/google/common/collect/Comparators.java b/android/guava/src/com/google/common/collect/Comparators.java index 0c79a8c29b34..7ada5e538105 100644 --- a/android/guava/src/com/google/common/collect/Comparators.java +++ b/android/guava/src/com/google/common/collect/Comparators.java @@ -17,10 +17,13 @@ package com.google.common.collect; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; import com.google.common.annotations.GwtCompatible; import java.util.Comparator; import java.util.Iterator; +import java.util.List; +import java.util.stream.Collector; import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -106,6 +109,65 @@ private Comparators() {} return true; } + /** + * Returns a {@code Collector} that returns the {@code k} smallest (relative to the specified + * {@code Comparator}) input elements, in ascending order, as an unmodifiable {@code List}. Ties + * are broken arbitrarily. + * + *

For example: + * + *

{@code
+   * Stream.of("foo", "quux", "banana", "elephant")
+   *     .collect(least(2, comparingInt(String::length)))
+   * // returns {"foo", "quux"}
+   * }
+ * + *

This {@code Collector} uses O(k) memory and takes expected time O(n) (worst-case O(n log + * k)), as opposed to e.g. {@code Stream.sorted(comparator).limit(k)}, which currently takes O(n + * log n) time and O(n) space. + * + * @throws IllegalArgumentException if {@code k < 0} + */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static Collector> least( + int k, Comparator comparator) { + checkNonnegative(k, "k"); + checkNotNull(comparator); + return Collector.of( + () -> TopKSelector.least(k, comparator), + TopKSelector::offer, + TopKSelector::combine, + TopKSelector::topK, + Collector.Characteristics.UNORDERED); + } + + /** + * Returns a {@code Collector} that returns the {@code k} greatest (relative to the specified + * {@code Comparator}) input elements, in descending order, as an unmodifiable {@code List}. Ties + * are broken arbitrarily. + * + *

For example: + * + *

{@code
+   * Stream.of("foo", "quux", "banana", "elephant")
+   *     .collect(greatest(2, comparingInt(String::length)))
+   * // returns {"elephant", "banana"}
+   * }
+ * + *

This {@code Collector} uses O(k) memory and takes expected time O(n) (worst-case O(n log + * k)), as opposed to e.g. {@code Stream.sorted(comparator.reversed()).limit(k)}, which currently + * takes O(n log n) time and O(n) space. + * + * @throws IllegalArgumentException if {@code k < 0} + */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static Collector> greatest( + int k, Comparator comparator) { + return least(k, comparator.reversed()); + } + /** * Returns the minimum of the two values. If the values compare as 0, the first is returned. * diff --git a/android/guava/src/com/google/common/collect/ImmutableBiMap.java b/android/guava/src/com/google/common/collect/ImmutableBiMap.java index b40df1b5eb61..d18d5b6f1ff0 100644 --- a/android/guava/src/com/google/common/collect/ImmutableBiMap.java +++ b/android/guava/src/com/google/common/collect/ImmutableBiMap.java @@ -29,7 +29,12 @@ import java.util.Collection; import java.util.Comparator; import java.util.Map; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; import javax.annotation.CheckForNull; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A {@link BiMap} whose contents will never change, with many other important properties detailed @@ -42,6 +47,24 @@ @ElementTypesAreNonnullByDefault public abstract class ImmutableBiMap extends ImmutableMap implements BiMap { + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableBiMap} whose keys + * and values are the result of applying the provided mapping functions to the input elements. + * Entries appear in the result {@code ImmutableBiMap} in encounter order. + * + *

If the mapped keys or values contain duplicates (according to {@link + * Object#equals(Object)}), an {@code IllegalArgumentException} is thrown when the collection + * operation is performed. (This differs from the {@code Collector} returned by {@link + * Collectors#toMap(Function, Function)}, which throws an {@code IllegalStateException}.) + */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static Collector> toImmutableBiMap( + Function keyFunction, + Function valueFunction) { + return CollectCollectors.toImmutableBiMap(keyFunction, valueFunction); + } + /** * Returns the empty bimap. * @@ -592,5 +615,41 @@ private void readObject(ObjectInputStream stream) throws InvalidObjectException throw new InvalidObjectException("Use SerializedForm"); } + /** + * Not supported. Use {@link #toImmutableBiMap} instead. This method exists only to hide {@link + * ImmutableMap#toImmutableMap(Function, Function)} from consumers of {@code ImmutableBiMap}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableBiMap#toImmutableBiMap}. + */ + @Deprecated + @DoNotCall("Use toImmutableBiMap") + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. This method does not make sense for {@code BiMap}. This method exists only to + * hide {@link ImmutableMap#toImmutableMap(Function, Function, BinaryOperator)} from consumers of + * {@code ImmutableBiMap}. + * + * @throws UnsupportedOperationException always + * @deprecated + */ + @Deprecated + @DoNotCall("Use toImmutableBiMap") + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + throw new UnsupportedOperationException(); + } + private static final long serialVersionUID = 0xdecaf; } diff --git a/android/guava/src/com/google/common/collect/ImmutableCollection.java b/android/guava/src/com/google/common/collect/ImmutableCollection.java index c8167e8f61cc..1da069a2f19b 100644 --- a/android/guava/src/com/google/common/collect/ImmutableCollection.java +++ b/android/guava/src/com/google/common/collect/ImmutableCollection.java @@ -36,8 +36,6 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Spliterator; -import java.util.Spliterators; import javax.annotation.CheckForNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -178,11 +176,6 @@ public abstract class ImmutableCollection extends AbstractCollection imple * These are properties of the collection as a whole; SIZED and SUBSIZED are more properties of * the spliterator implementation. */ - @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) - // @IgnoreJRERequirement is not necessary because this compiles down to a constant. - // (which is fortunate because Animal Sniffer doesn't look for @IgnoreJRERequirement on fields) - static final int SPLITERATOR_CHARACTERISTICS = - Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.ORDERED; ImmutableCollection() {} @@ -190,14 +183,6 @@ public abstract class ImmutableCollection extends AbstractCollection imple @Override public abstract UnmodifiableIterator iterator(); - @Override - @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) - @IgnoreJRERequirement // used only from APIs with Java 8 types in them - // (not used within guava-android as of this writing, but we include it in the jar as a test) - public Spliterator spliterator() { - return Spliterators.spliterator(this, SPLITERATOR_CHARACTERISTICS); - } - private static final Object[] EMPTY_ARRAY = {}; @Override diff --git a/android/guava/src/com/google/common/collect/ImmutableList.java b/android/guava/src/com/google/common/collect/ImmutableList.java index 9bf354580c1c..940bf96c2967 100644 --- a/android/guava/src/com/google/common/collect/ImmutableList.java +++ b/android/guava/src/com/google/common/collect/ImmutableList.java @@ -41,6 +41,7 @@ import java.util.Iterator; import java.util.List; import java.util.RandomAccess; +import java.util.stream.Collector; import javax.annotation.CheckForNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -62,6 +63,16 @@ public abstract class ImmutableList extends ImmutableCollection implements List, RandomAccess { + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableList}, in encounter order. + */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static Collector> toImmutableList() { + return CollectCollectors.toImmutableList(); + } + /** * Returns the empty immutable list. This list behaves and performs comparably to {@link * Collections#emptyList}, and is preferable mainly for consistency and maintainability of your diff --git a/android/guava/src/com/google/common/collect/ImmutableListMultimap.java b/android/guava/src/com/google/common/collect/ImmutableListMultimap.java index f0d107aa8e04..deab19d8a178 100644 --- a/android/guava/src/com/google/common/collect/ImmutableListMultimap.java +++ b/android/guava/src/com/google/common/collect/ImmutableListMultimap.java @@ -33,7 +33,11 @@ import java.util.Comparator; import java.util.Map; import java.util.Map.Entry; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Stream; import javax.annotation.CheckForNull; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A {@link ListMultimap} whose contents will never change, with many other important properties @@ -49,6 +53,78 @@ @ElementTypesAreNonnullByDefault public class ImmutableListMultimap extends ImmutableMultimap implements ListMultimap { + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableListMultimap} + * whose keys and values are the result of applying the provided mapping functions to the input + * elements. + * + *

For streams with defined encounter order (as defined in the Ordering section of the {@link + * java.util.stream} Javadoc), that order is preserved, but entries are grouped by key. + * + *

Example: + * + *

{@code
+   * static final Multimap FIRST_LETTER_MULTIMAP =
+   *     Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
+   *         .collect(toImmutableListMultimap(str -> str.charAt(0), str -> str.substring(1)));
+   *
+   * // is equivalent to
+   *
+   * static final Multimap FIRST_LETTER_MULTIMAP =
+   *     new ImmutableListMultimap.Builder()
+   *         .put('b', "anana")
+   *         .putAll('a', "pple", "sparagus")
+   *         .putAll('c', "arrot", "herry")
+   *         .build();
+   * }
+ */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static + Collector> toImmutableListMultimap( + Function keyFunction, + Function valueFunction) { + return CollectCollectors.toImmutableListMultimap(keyFunction, valueFunction); + } + + /** + * Returns a {@code Collector} accumulating entries into an {@code ImmutableListMultimap}. Each + * input element is mapped to a key and a stream of values, each of which are put into the + * resulting {@code Multimap}, in the encounter order of the stream and the encounter order of the + * streams of values. + * + *

Example: + * + *

{@code
+   * static final ImmutableListMultimap FIRST_LETTER_MULTIMAP =
+   *     Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
+   *         .collect(
+   *             flatteningToImmutableListMultimap(
+   *                  str -> str.charAt(0),
+   *                  str -> str.substring(1).chars().mapToObj(c -> (char) c));
+   *
+   * // is equivalent to
+   *
+   * static final ImmutableListMultimap FIRST_LETTER_MULTIMAP =
+   *     ImmutableListMultimap.builder()
+   *         .putAll('b', Arrays.asList('a', 'n', 'a', 'n', 'a'))
+   *         .putAll('a', Arrays.asList('p', 'p', 'l', 'e'))
+   *         .putAll('c', Arrays.asList('a', 'r', 'r', 'o', 't'))
+   *         .putAll('a', Arrays.asList('s', 'p', 'a', 'r', 'a', 'g', 'u', 's'))
+   *         .putAll('c', Arrays.asList('h', 'e', 'r', 'r', 'y'))
+   *         .build();
+   * }
+   * }
+ */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static + Collector> flatteningToImmutableListMultimap( + Function keyFunction, + Function> valuesFunction) { + return CollectCollectors.flatteningToImmutableListMultimap(keyFunction, valuesFunction); + } /** * Returns the empty multimap. diff --git a/android/guava/src/com/google/common/collect/ImmutableMap.java b/android/guava/src/com/google/common/collect/ImmutableMap.java index 4ec2f834ef33..79b39b7ade7f 100644 --- a/android/guava/src/com/google/common/collect/ImmutableMap.java +++ b/android/guava/src/com/google/common/collect/ImmutableMap.java @@ -46,6 +46,10 @@ import java.util.Map.Entry; import java.util.Set; import java.util.SortedMap; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; import javax.annotation.CheckForNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -66,6 +70,44 @@ @ElementTypesAreNonnullByDefault public abstract class ImmutableMap implements Map, Serializable { + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableMap} whose keys + * and values are the result of applying the provided mapping functions to the input elements. + * Entries appear in the result {@code ImmutableMap} in encounter order. + * + *

If the mapped keys contain duplicates (according to {@link Object#equals(Object)}, an {@code + * IllegalArgumentException} is thrown when the collection operation is performed. (This differs + * from the {@code Collector} returned by {@link Collectors#toMap(Function, Function)}, which + * throws an {@code IllegalStateException}.) + */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction) { + return CollectCollectors.toImmutableMap(keyFunction, valueFunction); + } + + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableMap} whose keys + * and values are the result of applying the provided mapping functions to the input elements. + * + *

If the mapped keys contain duplicates (according to {@link Object#equals(Object)}), the + * values are merged using the specified merging function. If the merging function returns {@code + * null}, then the collector removes the value that has been computed for the key thus far (though + * future occurrences of the key would reinsert it). + * + *

Entries will appear in the encounter order of the first occurrence of the key. + */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + return CollectCollectors.toImmutableMap(keyFunction, valueFunction, mergeFunction); + } + /** * Returns the empty map. This map behaves and performs comparably to {@link * Collections#emptyMap}, and is preferable mainly for consistency and maintainability of your diff --git a/android/guava/src/com/google/common/collect/ImmutableMultiset.java b/android/guava/src/com/google/common/collect/ImmutableMultiset.java index 9a096d286c35..b5e2e347e1ee 100644 --- a/android/guava/src/com/google/common/collect/ImmutableMultiset.java +++ b/android/guava/src/com/google/common/collect/ImmutableMultiset.java @@ -33,6 +33,9 @@ import java.util.Collection; import java.util.Iterator; import java.util.Set; +import java.util.function.Function; +import java.util.function.ToIntFunction; +import java.util.stream.Collector; import javax.annotation.CheckForNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -57,6 +60,33 @@ public abstract class ImmutableMultiset extends ImmutableMultisetGwtSerializationDependencies implements Multiset { + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableMultiset}. Elements iterate in order by the first appearance of that element in + * encounter order. + */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static Collector> toImmutableMultiset() { + return CollectCollectors.toImmutableMultiset(Function.identity(), e -> 1); + } + + /** + * Returns a {@code Collector} that accumulates elements into an {@code ImmutableMultiset} whose + * elements are the result of applying {@code elementFunction} to the inputs, with counts equal to + * the result of applying {@code countFunction} to the inputs. + * + *

If the mapped elements contain duplicates (according to {@link Object#equals}), the first + * occurrence in encounter order appears in the resulting multiset, with count equal to the sum of + * the outputs of {@code countFunction.applyAsInt(t)} for each {@code t} mapped to that element. + */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static Collector> toImmutableMultiset( + Function elementFunction, ToIntFunction countFunction) { + return CollectCollectors.toImmutableMultiset(elementFunction, countFunction); + } + /** * Returns the empty immutable multiset. * diff --git a/android/guava/src/com/google/common/collect/ImmutableRangeMap.java b/android/guava/src/com/google/common/collect/ImmutableRangeMap.java index 532631617116..72c70b1ae9b5 100644 --- a/android/guava/src/com/google/common/collect/ImmutableRangeMap.java +++ b/android/guava/src/com/google/common/collect/ImmutableRangeMap.java @@ -33,7 +33,10 @@ import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.stream.Collector; import javax.annotation.CheckForNull; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A {@link RangeMap} whose contents will never change, with many other important properties @@ -49,6 +52,19 @@ public class ImmutableRangeMap, V> implements RangeMap, Object> EMPTY = new ImmutableRangeMap<>(ImmutableList.>>of(), ImmutableList.of()); + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableRangeMap}. As in {@link Builder}, overlapping ranges are not permitted. + */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static , V> + Collector> toImmutableRangeMap( + Function> keyFunction, + Function valueFunction) { + return CollectCollectors.toImmutableRangeMap(keyFunction, valueFunction); + } + /** * Returns an empty immutable range map. * diff --git a/android/guava/src/com/google/common/collect/ImmutableRangeSet.java b/android/guava/src/com/google/common/collect/ImmutableRangeSet.java index d1f05bc98729..7d08c89e3d39 100644 --- a/android/guava/src/com/google/common/collect/ImmutableRangeSet.java +++ b/android/guava/src/com/google/common/collect/ImmutableRangeSet.java @@ -38,6 +38,7 @@ import java.util.List; import java.util.NoSuchElementException; import java.util.Set; +import java.util.stream.Collector; import javax.annotation.CheckForNull; /** @@ -58,6 +59,18 @@ public final class ImmutableRangeSet extends AbstractRange private static final ImmutableRangeSet> ALL = new ImmutableRangeSet<>(ImmutableList.of(Range.>all())); + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableRangeSet}. As in {@link Builder}, overlapping ranges are not permitted and adjacent + * ranges will be merged. + */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static > + Collector, ?, ImmutableRangeSet> toImmutableRangeSet() { + return CollectCollectors.toImmutableRangeSet(); + } + /** * Returns an empty immutable range set. * diff --git a/android/guava/src/com/google/common/collect/ImmutableSet.java b/android/guava/src/com/google/common/collect/ImmutableSet.java index 2fd833f230c2..c4631804d6cc 100644 --- a/android/guava/src/com/google/common/collect/ImmutableSet.java +++ b/android/guava/src/com/google/common/collect/ImmutableSet.java @@ -38,7 +38,7 @@ import java.util.Iterator; import java.util.Set; import java.util.SortedSet; -import java.util.Spliterator; +import java.util.stream.Collector; import javax.annotation.CheckForNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -52,11 +52,18 @@ @SuppressWarnings("serial") // we're overriding default serialization @ElementTypesAreNonnullByDefault public abstract class ImmutableSet extends ImmutableCollection implements Set { + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableSet}. Elements appear in the resulting set in the encounter order of the stream; if + * the stream contains duplicates (according to {@link Object#equals(Object)}), only the first + * duplicate in encounter order will appear in the result. + */ @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) - // @IgnoreJRERequirement is not necessary because this compiles down to a constant. - // (which is fortunate because Animal Sniffer doesn't look for @IgnoreJRERequirement on fields) - static final int SPLITERATOR_CHARACTERISTICS = - ImmutableCollection.SPLITERATOR_CHARACTERISTICS | Spliterator.DISTINCT; + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static Collector> toImmutableSet() { + return CollectCollectors.toImmutableSet(); + } /** * Returns the empty immutable set. Preferred over {@link Collections#emptySet} for code diff --git a/android/guava/src/com/google/common/collect/ImmutableSetMultimap.java b/android/guava/src/com/google/common/collect/ImmutableSetMultimap.java index b2d80af4fac6..022a3d40910e 100644 --- a/android/guava/src/com/google/common/collect/ImmutableSetMultimap.java +++ b/android/guava/src/com/google/common/collect/ImmutableSetMultimap.java @@ -37,7 +37,11 @@ import java.util.Comparator; import java.util.Map; import java.util.Map.Entry; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Stream; import javax.annotation.CheckForNull; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A {@link SetMultimap} whose contents will never change, with many other important properties @@ -57,6 +61,87 @@ @ElementTypesAreNonnullByDefault public class ImmutableSetMultimap extends ImmutableMultimap implements SetMultimap { + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableSetMultimap} + * whose keys and values are the result of applying the provided mapping functions to the input + * elements. + * + *

For streams with defined encounter order (as defined in the Ordering section of the {@link + * java.util.stream} Javadoc), that order is preserved, but entries are grouped by key. + * + *

Example: + * + *

{@code
+   * static final Multimap FIRST_LETTER_MULTIMAP =
+   *     Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
+   *         .collect(toImmutableSetMultimap(str -> str.charAt(0), str -> str.substring(1)));
+   *
+   * // is equivalent to
+   *
+   * static final Multimap FIRST_LETTER_MULTIMAP =
+   *     new ImmutableSetMultimap.Builder()
+   *         .put('b', "anana")
+   *         .putAll('a', "pple", "sparagus")
+   *         .putAll('c', "arrot", "herry")
+   *         .build();
+   * }
+ */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static + Collector> toImmutableSetMultimap( + Function keyFunction, + Function valueFunction) { + return CollectCollectors.toImmutableSetMultimap(keyFunction, valueFunction); + } + + /** + * Returns a {@code Collector} accumulating entries into an {@code ImmutableSetMultimap}. Each + * input element is mapped to a key and a stream of values, each of which are put into the + * resulting {@code Multimap}, in the encounter order of the stream and the encounter order of the + * streams of values. + * + *

Example: + * + *

{@code
+   * static final ImmutableSetMultimap FIRST_LETTER_MULTIMAP =
+   *     Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
+   *         .collect(
+   *             flatteningToImmutableSetMultimap(
+   *                  str -> str.charAt(0),
+   *                  str -> str.substring(1).chars().mapToObj(c -> (char) c));
+   *
+   * // is equivalent to
+   *
+   * static final ImmutableSetMultimap FIRST_LETTER_MULTIMAP =
+   *     ImmutableSetMultimap.builder()
+   *         .putAll('b', Arrays.asList('a', 'n', 'a', 'n', 'a'))
+   *         .putAll('a', Arrays.asList('p', 'p', 'l', 'e'))
+   *         .putAll('c', Arrays.asList('a', 'r', 'r', 'o', 't'))
+   *         .putAll('a', Arrays.asList('s', 'p', 'a', 'r', 'a', 'g', 'u', 's'))
+   *         .putAll('c', Arrays.asList('h', 'e', 'r', 'r', 'y'))
+   *         .build();
+   *
+   * // after deduplication, the resulting multimap is equivalent to
+   *
+   * static final ImmutableSetMultimap FIRST_LETTER_MULTIMAP =
+   *     ImmutableSetMultimap.builder()
+   *         .putAll('b', Arrays.asList('a', 'n'))
+   *         .putAll('a', Arrays.asList('p', 'l', 'e', 's', 'a', 'r', 'g', 'u'))
+   *         .putAll('c', Arrays.asList('a', 'r', 'o', 't', 'h', 'e', 'y'))
+   *         .build();
+   * }
+   * }
+ */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static + Collector> flatteningToImmutableSetMultimap( + Function keyFunction, + Function> valuesFunction) { + return CollectCollectors.flatteningToImmutableSetMultimap(keyFunction, valuesFunction); + } /** * Returns the empty multimap. diff --git a/android/guava/src/com/google/common/collect/ImmutableSortedMap.java b/android/guava/src/com/google/common/collect/ImmutableSortedMap.java index 2bebbe59f1f4..985624968122 100644 --- a/android/guava/src/com/google/common/collect/ImmutableSortedMap.java +++ b/android/guava/src/com/google/common/collect/ImmutableSortedMap.java @@ -36,6 +36,10 @@ import java.util.NavigableMap; import java.util.SortedMap; import java.util.TreeMap; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; import javax.annotation.CheckForNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -60,6 +64,46 @@ @ElementTypesAreNonnullByDefault public final class ImmutableSortedMap extends ImmutableMap implements NavigableMap { + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableSortedMap} whose + * keys and values are the result of applying the provided mapping functions to the input + * elements. The generated map is sorted by the specified comparator. + * + *

If the mapped keys contain duplicates (according to the specified comparator), an {@code + * IllegalArgumentException} is thrown when the collection operation is performed. (This differs + * from the {@code Collector} returned by {@link Collectors#toMap(Function, Function)}, which + * throws an {@code IllegalStateException}.) + */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static + Collector> toImmutableSortedMap( + Comparator comparator, + Function keyFunction, + Function valueFunction) { + return CollectCollectors.toImmutableSortedMap(comparator, keyFunction, valueFunction); + } + + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableSortedMap} whose + * keys and values are the result of applying the provided mapping functions to the input + * elements. + * + *

If the mapped keys contain duplicates (according to the comparator), the values are merged + * using the specified merging function. Entries will appear in the encounter order of the first + * occurrence of the key. + */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static + Collector> toImmutableSortedMap( + Comparator comparator, + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + return CollectCollectors.toImmutableSortedMap( + comparator, keyFunction, valueFunction, mergeFunction); + } /* * TODO(kevinb): Confirm that ImmutableSortedMap is faster to construct and @@ -1166,6 +1210,43 @@ private void readObject(ObjectInputStream stream) throws InvalidObjectException // warning go away (and suppressing would suppress for all nested classes too) private static final long serialVersionUID = 0; + /** + * Not supported. Use {@link #toImmutableSortedMap}, which offers better type-safety, instead. + * This method exists only to hide {@link ImmutableMap#toImmutableMap} from consumers of {@code + * ImmutableSortedMap}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedMap#toImmutableSortedMap}. + */ + @DoNotCall("Use toImmutableSortedMap") + @Deprecated + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. Use {@link #toImmutableSortedMap}, which offers better type-safety, instead. + * This method exists only to hide {@link ImmutableMap#toImmutableMap} from consumers of {@code + * ImmutableSortedMap}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedMap#toImmutableSortedMap}. + */ + @DoNotCall("Use toImmutableSortedMap") + @Deprecated + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + throw new UnsupportedOperationException(); + } + /** * Not supported. Use {@link #naturalOrder}, which offers better type-safety, instead. This method * exists only to hide {@link ImmutableMap#builder} from consumers of {@code ImmutableSortedMap}. diff --git a/android/guava/src/com/google/common/collect/ImmutableSortedMultiset.java b/android/guava/src/com/google/common/collect/ImmutableSortedMultiset.java index fea0f460e1ac..3660ca596111 100644 --- a/android/guava/src/com/google/common/collect/ImmutableSortedMultiset.java +++ b/android/guava/src/com/google/common/collect/ImmutableSortedMultiset.java @@ -33,7 +33,11 @@ import java.util.Comparator; import java.util.Iterator; import java.util.List; +import java.util.function.Function; +import java.util.function.ToIntFunction; +import java.util.stream.Collector; import javax.annotation.CheckForNull; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A {@link SortedMultiset} whose contents will never change, with many other important properties @@ -57,6 +61,67 @@ public abstract class ImmutableSortedMultiset extends ImmutableMultiset implements SortedMultiset { // TODO(lowasser): GWT compatibility + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableMultiset}. Elements are sorted by the specified comparator. + * + *

Warning: {@code comparator} should be consistent with {@code equals} as + * explained in the {@link Comparator} documentation. + */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static Collector> toImmutableSortedMultiset( + Comparator comparator) { + return toImmutableSortedMultiset(comparator, Function.identity(), e -> 1); + } + + /** + * Returns a {@code Collector} that accumulates elements into an {@code ImmutableSortedMultiset} + * whose elements are the result of applying {@code elementFunction} to the inputs, with counts + * equal to the result of applying {@code countFunction} to the inputs. + * + *

If the mapped elements contain duplicates (according to {@code comparator}), the first + * occurrence in encounter order appears in the resulting multiset, with count equal to the sum of + * the outputs of {@code countFunction.applyAsInt(t)} for each {@code t} mapped to that element. + */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static + Collector> toImmutableSortedMultiset( + Comparator comparator, + Function elementFunction, + ToIntFunction countFunction) { + checkNotNull(comparator); + checkNotNull(elementFunction); + checkNotNull(countFunction); + return Collector.of( + () -> TreeMultiset.create(comparator), + (multiset, t) -> mapAndAdd(t, multiset, elementFunction, countFunction), + (multiset1, multiset2) -> { + multiset1.addAll(multiset2); + return multiset1; + }, + (Multiset multiset) -> copyOfSortedEntries(comparator, multiset.entrySet())); + } + + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // helper for toImmutableSortedMultiset + /* + * If we make these calls inline inside toImmutableSortedMultiset, we get an Animal Sniffer error, + * despite the @IgnoreJRERequirement annotation there. My assumption is that, because javac + * generates a synthetic method for the body of the lambda, the actual method calls that Animal + * Sniffer is flagging don't appear inside toImmutableSortedMultiset but rather inside that + * synthetic method. By moving those calls to a named method, we're able to apply + * @IgnoreJRERequirement somewhere that it will help. + */ + private static void mapAndAdd( + T t, + Multiset multiset, + Function elementFunction, + ToIntFunction countFunction) { + multiset.add(checkNotNull(elementFunction.apply(t)), countFunction.applyAsInt(t)); + } + /** * Returns the empty immutable sorted multiset. * @@ -682,6 +747,39 @@ private void readObject(ObjectInputStream stream) throws InvalidObjectException throw new InvalidObjectException("Use SerializedForm"); } + /** + * Not supported. Use {@link #toImmutableSortedMultiset} instead. This method exists only to hide + * {@link ImmutableMultiset#toImmutableMultiset} from consumers of {@code + * ImmutableSortedMultiset}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedMultiset#toImmutableSortedMultiset}. + */ + @DoNotCall("Use toImmutableSortedMultiset.") + @Deprecated + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static Collector> toImmutableMultiset() { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. Use {@link #toImmutableSortedMultiset} instead. This method exists only to hide + * {@link ImmutableMultiset#toImmutableMultiset} from consumers of {@code + * ImmutableSortedMultiset}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedMultiset#toImmutableSortedMultiset}. + */ + @DoNotCall("Use toImmutableSortedMultiset.") + @Deprecated + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static Collector> toImmutableMultiset( + Function elementFunction, ToIntFunction countFunction) { + throw new UnsupportedOperationException(); + } + /** * Not supported. Use {@link #naturalOrder}, which offers better type-safety, instead. This method * exists only to hide {@link ImmutableMultiset#builder} from consumers of {@code diff --git a/android/guava/src/com/google/common/collect/ImmutableSortedSet.java b/android/guava/src/com/google/common/collect/ImmutableSortedSet.java index 22660c02c5aa..c6a57824f139 100644 --- a/android/guava/src/com/google/common/collect/ImmutableSortedSet.java +++ b/android/guava/src/com/google/common/collect/ImmutableSortedSet.java @@ -36,6 +36,7 @@ import java.util.Iterator; import java.util.NavigableSet; import java.util.SortedSet; +import java.util.stream.Collector; import javax.annotation.CheckForNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -62,6 +63,19 @@ @ElementTypesAreNonnullByDefault public abstract class ImmutableSortedSet extends ImmutableSet implements NavigableSet, SortedIterable { + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableSortedSet}, ordered by the specified comparator. + * + *

If the elements contain duplicates (according to the comparator), only the first duplicate + * in encounter order will appear in the result. + */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static Collector> toImmutableSortedSet( + Comparator comparator) { + return CollectCollectors.toImmutableSortedSet(comparator); + } static RegularImmutableSortedSet emptySet(Comparator comparator) { if (Ordering.natural().equals(comparator)) { @@ -765,6 +779,21 @@ Object writeReplace() { return new SerializedForm(comparator, toArray()); } + /** + * Not supported. Use {@link #toImmutableSortedSet} instead. This method exists only to hide + * {@link ImmutableSet#toImmutableSet} from consumers of {@code ImmutableSortedSet}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedSet#toImmutableSortedSet}. + */ + @DoNotCall("Use toImmutableSortedSet") + @Deprecated + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static Collector> toImmutableSet() { + throw new UnsupportedOperationException(); + } + /** * Not supported. Use {@link #naturalOrder}, which offers better type-safety, instead. This method * exists only to hide {@link ImmutableSet#builder} from consumers of {@code ImmutableSortedSet}. diff --git a/android/guava/src/com/google/common/collect/ImmutableTable.java b/android/guava/src/com/google/common/collect/ImmutableTable.java index 6c8f26dfe0f0..91c9f71a633e 100644 --- a/android/guava/src/com/google/common/collect/ImmutableTable.java +++ b/android/guava/src/com/google/common/collect/ImmutableTable.java @@ -32,7 +32,11 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.stream.Collector; import javax.annotation.CheckForNull; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A {@link Table} whose contents will never change, with many other important properties detailed @@ -49,6 +53,45 @@ public abstract class ImmutableTable extends AbstractTable implements Serializable { + /** + * Returns a {@code Collector} that accumulates elements into an {@code ImmutableTable}. Each + * input element is mapped to one cell in the returned table, with the rows, columns, and values + * generated by applying the specified functions. + * + *

The returned {@code Collector} will throw a {@code NullPointerException} at collection time + * if the row, column, or value functions return null on any input. + */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static + Collector> toImmutableTable( + Function rowFunction, + Function columnFunction, + Function valueFunction) { + return TableCollectors.toImmutableTable(rowFunction, columnFunction, valueFunction); + } + + /** + * Returns a {@code Collector} that accumulates elements into an {@code ImmutableTable}. Each + * input element is mapped to one cell in the returned table, with the rows, columns, and values + * generated by applying the specified functions. If multiple inputs are mapped to the same row + * and column pair, they will be combined with the specified merging function in encounter order. + * + *

The returned {@code Collector} will throw a {@code NullPointerException} at collection time + * if the row, column, value, or merging functions return null on any input. + */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static + Collector> toImmutableTable( + Function rowFunction, + Function columnFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + return TableCollectors.toImmutableTable( + rowFunction, columnFunction, valueFunction, mergeFunction); + } + /** * Returns an empty immutable table. * diff --git a/android/guava/src/com/google/common/collect/Maps.java b/android/guava/src/com/google/common/collect/Maps.java index 9a385204730a..0664f5dabfb3 100644 --- a/android/guava/src/com/google/common/collect/Maps.java +++ b/android/guava/src/com/google/common/collect/Maps.java @@ -65,6 +65,8 @@ import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.function.BinaryOperator; +import java.util.stream.Collector; import javax.annotation.CheckForNull; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -177,6 +179,48 @@ public static , V> ImmutableMap immutableEnumMap( return ImmutableEnumMap.asImmutable(enumMap); } + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableMap} whose keys + * and values are the result of applying the provided mapping functions to the input elements. The + * resulting implementation is specialized for enum key types. The returned map and its views will + * iterate over keys in their enum definition order, not encounter order. + * + *

If the mapped keys contain duplicates, an {@code IllegalArgumentException} is thrown when + * the collection operation is performed. (This differs from the {@code Collector} returned by + * {@link java.util.stream.Collectors#toMap(java.util.function.Function, + * java.util.function.Function) Collectors.toMap(Function, Function)}, which throws an {@code + * IllegalStateException}.) + */ + @J2ktIncompatible + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static , V> + Collector> toImmutableEnumMap( + java.util.function.Function keyFunction, + java.util.function.Function valueFunction) { + return CollectCollectors.toImmutableEnumMap(keyFunction, valueFunction); + } + + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableMap} whose keys + * and values are the result of applying the provided mapping functions to the input elements. The + * resulting implementation is specialized for enum key types. The returned map and its views will + * iterate over keys in their enum definition order, not encounter order. + * + *

If the mapped keys contain duplicates, the values are merged using the specified merging + * function. + */ + @J2ktIncompatible + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static , V> + Collector> toImmutableEnumMap( + java.util.function.Function keyFunction, + java.util.function.Function valueFunction, + BinaryOperator mergeFunction) { + return CollectCollectors.toImmutableEnumMap(keyFunction, valueFunction, mergeFunction); + } + /** * Creates a mutable, empty {@code HashMap} instance. * diff --git a/android/guava/src/com/google/common/collect/MoreCollectors.java b/android/guava/src/com/google/common/collect/MoreCollectors.java new file mode 100644 index 000000000000..02b254d2d54c --- /dev/null +++ b/android/guava/src/com/google/common/collect/MoreCollectors.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2016 The Guava 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 com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Collections.emptyList; + +import com.google.common.annotations.GwtCompatible; +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.stream.Collector; +import javax.annotation.CheckForNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Collectors not present in {@code java.util.stream.Collectors} that are not otherwise associated + * with a {@code com.google.common} type. + * + * @author Louis Wasserman + */ +@GwtCompatible +@ElementTypesAreNonnullByDefault +@SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) +@IgnoreJRERequirement // Users will use this only if they're already using streams. +final class MoreCollectors { + + /* + * TODO(lowasser): figure out if we can convert this to a concurrent AtomicReference-based + * collector without breaking j2cl? + */ + private static final Collector> TO_OPTIONAL = + Collector.of( + ToOptionalState::new, + ToOptionalState::add, + ToOptionalState::combine, + ToOptionalState::getOptional, + Collector.Characteristics.UNORDERED); + + /** + * A collector that converts a stream of zero or one elements to an {@code Optional}. + * + * @throws IllegalArgumentException if the stream consists of two or more elements. + * @throws NullPointerException if any element in the stream is {@code null}. + * @return {@code Optional.of(onlyElement)} if the stream has exactly one element (must not be + * {@code null}) and returns {@code Optional.empty()} if it has none. + */ + @SuppressWarnings("unchecked") + public static Collector> toOptional() { + return (Collector) TO_OPTIONAL; + } + + private static final Object NULL_PLACEHOLDER = new Object(); + + private static final Collector<@Nullable Object, ?, @Nullable Object> ONLY_ELEMENT = + Collector.<@Nullable Object, ToOptionalState, @Nullable Object>of( + ToOptionalState::new, + (state, o) -> state.add((o == null) ? NULL_PLACEHOLDER : o), + ToOptionalState::combine, + state -> { + Object result = state.getElement(); + return (result == NULL_PLACEHOLDER) ? null : result; + }, + Collector.Characteristics.UNORDERED); + + /** + * A collector that takes a stream containing exactly one element and returns that element. The + * returned collector throws an {@code IllegalArgumentException} if the stream consists of two or + * more elements, and a {@code NoSuchElementException} if the stream is empty. + */ + @SuppressWarnings("unchecked") + public static Collector onlyElement() { + return (Collector) ONLY_ELEMENT; + } + + /** + * This atrocity is here to let us report several of the elements in the stream if there were more + * than one, not just two. + */ + private static final class ToOptionalState { + static final int MAX_EXTRAS = 4; + + @CheckForNull Object element; + List extras; + + ToOptionalState() { + element = null; + extras = emptyList(); + } + + IllegalArgumentException multiples(boolean overflow) { + StringBuilder sb = + new StringBuilder().append("expected one element but was: <").append(element); + for (Object o : extras) { + sb.append(", ").append(o); + } + if (overflow) { + sb.append(", ..."); + } + sb.append('>'); + throw new IllegalArgumentException(sb.toString()); + } + + void add(Object o) { + checkNotNull(o); + if (element == null) { + this.element = o; + } else if (extras.isEmpty()) { + // Replace immutable empty list with mutable list. + extras = new ArrayList<>(MAX_EXTRAS); + extras.add(o); + } else if (extras.size() < MAX_EXTRAS) { + extras.add(o); + } else { + throw multiples(true); + } + } + + ToOptionalState combine(ToOptionalState other) { + if (element == null) { + return other; + } else if (other.element == null) { + return this; + } else { + if (extras.isEmpty()) { + // Replace immutable empty list with mutable list. + extras = new ArrayList<>(); + } + extras.add(other.element); + extras.addAll(other.extras); + if (extras.size() > MAX_EXTRAS) { + extras.subList(MAX_EXTRAS, extras.size()).clear(); + throw multiples(true); + } + return this; + } + } + + @IgnoreJRERequirement // see enclosing class (whose annotation Animal Sniffer ignores here...) + Optional getOptional() { + if (extras.isEmpty()) { + return Optional.ofNullable(element); + } else { + throw multiples(false); + } + } + + Object getElement() { + if (element == null) { + throw new NoSuchElementException(); + } else if (extras.isEmpty()) { + return element; + } else { + throw multiples(false); + } + } + } + + private MoreCollectors() {} +} diff --git a/android/guava/src/com/google/common/collect/Multimaps.java b/android/guava/src/com/google/common/collect/Multimaps.java index a1ca78d5ba03..34d56b077c64 100644 --- a/android/guava/src/com/google/common/collect/Multimaps.java +++ b/android/guava/src/com/google/common/collect/Multimaps.java @@ -51,6 +51,8 @@ import java.util.NoSuchElementException; import java.util.Set; import java.util.SortedSet; +import java.util.stream.Collector; +import java.util.stream.Stream; import javax.annotation.CheckForNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -72,6 +74,100 @@ public final class Multimaps { private Multimaps() {} + /** + * Returns a {@code Collector} accumulating entries into a {@code Multimap} generated from the + * specified supplier. The keys and values of the entries are the result of applying the provided + * mapping functions to the input elements, accumulated in the encounter order of the stream. + * + *

Example: + * + *

{@code
+   * static final ListMultimap FIRST_LETTER_MULTIMAP =
+   *     Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
+   *         .collect(
+   *             toMultimap(
+   *                  str -> str.charAt(0),
+   *                  str -> str.substring(1),
+   *                  MultimapBuilder.treeKeys().arrayListValues()::build));
+   *
+   * // is equivalent to
+   *
+   * static final ListMultimap FIRST_LETTER_MULTIMAP;
+   *
+   * static {
+   *     FIRST_LETTER_MULTIMAP = MultimapBuilder.treeKeys().arrayListValues().build();
+   *     FIRST_LETTER_MULTIMAP.put('b', "anana");
+   *     FIRST_LETTER_MULTIMAP.put('a', "pple");
+   *     FIRST_LETTER_MULTIMAP.put('a', "sparagus");
+   *     FIRST_LETTER_MULTIMAP.put('c', "arrot");
+   *     FIRST_LETTER_MULTIMAP.put('c', "herry");
+   * }
+   * }
+ * + *

To collect to an {@link ImmutableMultimap}, use either {@link + * ImmutableSetMultimap#toImmutableSetMultimap} or {@link + * ImmutableListMultimap#toImmutableListMultimap}. + */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static < + T extends @Nullable Object, + K extends @Nullable Object, + V extends @Nullable Object, + M extends Multimap> + Collector toMultimap( + java.util.function.Function keyFunction, + java.util.function.Function valueFunction, + java.util.function.Supplier multimapSupplier) { + return CollectCollectors.toMultimap(keyFunction, valueFunction, multimapSupplier); + } + + /** + * Returns a {@code Collector} accumulating entries into a {@code Multimap} generated from the + * specified supplier. Each input element is mapped to a key and a stream of values, each of which + * are put into the resulting {@code Multimap}, in the encounter order of the stream and the + * encounter order of the streams of values. + * + *

Example: + * + *

{@code
+   * static final ListMultimap FIRST_LETTER_MULTIMAP =
+   *     Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
+   *         .collect(
+   *             flatteningToMultimap(
+   *                  str -> str.charAt(0),
+   *                  str -> str.substring(1).chars().mapToObj(c -> (char) c),
+   *                  MultimapBuilder.linkedHashKeys().arrayListValues()::build));
+   *
+   * // is equivalent to
+   *
+   * static final ListMultimap FIRST_LETTER_MULTIMAP;
+   *
+   * static {
+   *     FIRST_LETTER_MULTIMAP = MultimapBuilder.linkedHashKeys().arrayListValues().build();
+   *     FIRST_LETTER_MULTIMAP.putAll('b', Arrays.asList('a', 'n', 'a', 'n', 'a'));
+   *     FIRST_LETTER_MULTIMAP.putAll('a', Arrays.asList('p', 'p', 'l', 'e'));
+   *     FIRST_LETTER_MULTIMAP.putAll('c', Arrays.asList('a', 'r', 'r', 'o', 't'));
+   *     FIRST_LETTER_MULTIMAP.putAll('a', Arrays.asList('s', 'p', 'a', 'r', 'a', 'g', 'u', 's'));
+   *     FIRST_LETTER_MULTIMAP.putAll('c', Arrays.asList('h', 'e', 'r', 'r', 'y'));
+   * }
+   * }
+ */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static < + T extends @Nullable Object, + K extends @Nullable Object, + V extends @Nullable Object, + M extends Multimap> + Collector flatteningToMultimap( + java.util.function.Function keyFunction, + java.util.function.Function> valueFunction, + java.util.function.Supplier multimapSupplier) { + return CollectCollectors.flatteningToMultimap( + keyFunction, valueFunction, multimapSupplier); + } + /** * Creates a new {@code Multimap} backed by {@code map}, whose internal value collections are * generated by {@code factory}. diff --git a/android/guava/src/com/google/common/collect/Multisets.java b/android/guava/src/com/google/common/collect/Multisets.java index 2611df0277ac..9cebe58f4995 100644 --- a/android/guava/src/com/google/common/collect/Multisets.java +++ b/android/guava/src/com/google/common/collect/Multisets.java @@ -39,6 +39,10 @@ import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.function.ToIntFunction; +import java.util.stream.Collector; import javax.annotation.CheckForNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -59,6 +63,31 @@ public final class Multisets { private Multisets() {} + /** + * Returns a {@code Collector} that accumulates elements into a multiset created via the specified + * {@code Supplier}, whose elements are the result of applying {@code elementFunction} to the + * inputs, with counts equal to the result of applying {@code countFunction} to the inputs. + * Elements are added in encounter order. + * + *

If the mapped elements contain duplicates (according to {@link Object#equals}), the element + * will be added more than once, with the count summed over all appearances of the element. + * + *

Note that {@code stream.collect(toMultiset(function, e -> 1, supplier))} is equivalent to + * {@code stream.map(function).collect(Collectors.toCollection(supplier))}. + * + *

To collect to an {@link ImmutableMultiset}, use {@link + * ImmutableMultiset#toImmutableMultiset}. + */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static > + Collector toMultiset( + Function elementFunction, + ToIntFunction countFunction, + Supplier multisetSupplier) { + return CollectCollectors.toMultiset(elementFunction, countFunction, multisetSupplier); + } + /** * Returns an unmodifiable view of the specified multiset. Query operations on the returned * multiset "read through" to the specified multiset, and attempts to modify the returned multiset diff --git a/android/guava/src/com/google/common/collect/Sets.java b/android/guava/src/com/google/common/collect/Sets.java index cbea9ca3298d..60578e6d03f5 100644 --- a/android/guava/src/com/google/common/collect/Sets.java +++ b/android/guava/src/com/google/common/collect/Sets.java @@ -50,6 +50,7 @@ import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; +import java.util.stream.Collector; import javax.annotation.CheckForNull; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -139,6 +140,17 @@ public static > ImmutableSet immutableEnumSet(Iterable e } } + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code ImmutableSet} + * with an implementation specialized for enums. Unlike {@link ImmutableSet#toImmutableSet}, the + * resulting set will iterate over elements in their enum definition order, not encounter order. + */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static > Collector> toImmutableEnumSet() { + return CollectCollectors.toImmutableEnumSet(); + } + /** * Returns a new, mutable {@code EnumSet} instance containing the given elements in their * natural order. This method behaves identically to {@link EnumSet#copyOf(Collection)}, but also diff --git a/android/guava/src/com/google/common/collect/Tables.java b/android/guava/src/com/google/common/collect/Tables.java index 710b118f0262..cca3d0c3a051 100644 --- a/android/guava/src/com/google/common/collect/Tables.java +++ b/android/guava/src/com/google/common/collect/Tables.java @@ -33,6 +33,8 @@ import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; +import java.util.function.BinaryOperator; +import java.util.stream.Collector; import javax.annotation.CheckForNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -51,6 +53,63 @@ public final class Tables { private Tables() {} + /** + * Returns a {@link Collector} that accumulates elements into a {@code Table} created using the + * specified supplier, whose cells are generated by applying the provided mapping functions to the + * input elements. Cells are inserted into the generated {@code Table} in encounter order. + * + *

If multiple input elements map to the same row and column, an {@code IllegalStateException} + * is thrown when the collection operation is performed. + * + *

To collect to an {@link ImmutableTable}, use {@link ImmutableTable#toImmutableTable}. + */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static < + T extends @Nullable Object, + R extends @Nullable Object, + C extends @Nullable Object, + V extends @Nullable Object, + I extends Table> + Collector toTable( + java.util.function.Function rowFunction, + java.util.function.Function columnFunction, + java.util.function.Function valueFunction, + java.util.function.Supplier tableSupplier) { + return TableCollectors.toTable( + rowFunction, columnFunction, valueFunction, tableSupplier); + } + + /** + * Returns a {@link Collector} that accumulates elements into a {@code Table} created using the + * specified supplier, whose cells are generated by applying the provided mapping functions to the + * input elements. Cells are inserted into the generated {@code Table} in encounter order. + * + *

If multiple input elements map to the same row and column, the specified merging function is + * used to combine the values. Like {@link + * java.util.stream.Collectors#toMap(java.util.function.Function, java.util.function.Function, + * BinaryOperator, java.util.function.Supplier)}, this Collector throws a {@code + * NullPointerException} on null values returned from {@code valueFunction}, and treats nulls + * returned from {@code mergeFunction} as removals of that row/column pair. + */ + @SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"}) + @IgnoreJRERequirement // Users will use this only if they're already using streams. + static < + T extends @Nullable Object, + R extends @Nullable Object, + C extends @Nullable Object, + V extends @Nullable Object, + I extends Table> + Collector toTable( + java.util.function.Function rowFunction, + java.util.function.Function columnFunction, + java.util.function.Function valueFunction, + BinaryOperator mergeFunction, + java.util.function.Supplier tableSupplier) { + return TableCollectors.toTable( + rowFunction, columnFunction, valueFunction, mergeFunction, tableSupplier); + } + /** * Returns an immutable cell with the specified row key, column key, and value. *