Skip to content

Commit 73dbf7e

Browse files
cpovirkGoogle Java Core Libraries
authored andcommitted
Include our Collector APIs as package-private in our open-source Android codebase.
This is a test before exposing them as `public`. We have successfully used them inside Google, but we want to test as much as we can before adding them to our open-source project, since we don't want to have to remove them later. Package-private APIs are of course of no use to users. However, there mere existence may be enough to cause problems for build tools or for Android apps that perform runtime reflection on the Guava classes (which incidentally we do not recommend, for this and other reasons). Our hope is that such problems are rare to nonexistent or, failing that, that they can be solved by enabling [library desugaring](https://developer.android.com/studio/write/java8-support#library-desugaring) for any affected apps. Please do report any problems that this change causes. The next step before exposing the APIs as `public` will likely be to expose an override of `spliterator()`. Since that API will be an override, it is more likely to be preserved by optimizers, which might remove the unused `Collector` APIs. (Sadly, we can't prevent that by inserting a usage of the `Collector` APIs in "real code" because that would require all our users to enable library desugaring.) (Originally, I'd planned to expose `spliterator()` immediately, as discussed in cl/576629272. In fact, that CL _did_ expose the method. However, we never released it. (And even if we had, I think we could remove it, since either it's an override (in which case calls to it will continue to work after it's removed) or it's not (in which case Java 8 APIs aren't available, so calls to it would never have worked.) But I think the approach of this current CL is more conservative.) If all goes well, we'll then expose the APIs as `public`. We might considering using `@Beta` for a time, but we'd be unlikely to remove them, so again, please report any problems that this change or any future Java-8-API change causes you. (This CL requires lots of `@IgnoreJRERequirement` annotations. In an ideal world, we'd run Animal Sniffer twice: one run that allows APIs that require library desugaring and one that doesn't, with our classes' using a separate `@IgnoreJRERequirement`-style annotation for APIs like these.) This change is further progress toward #6567. RELNOTES=This version of `guava-android` contains some package-private methods whose signature includes the Java 8 `Collector` API. This is a test to identify any problems before we expose those methods publicly to users. Please report any problems that you encounter. PiperOrigin-RevId: 589183735
1 parent 64a8435 commit 73dbf7e

21 files changed

+1072
-20
lines changed

android/guava/src/com/google/common/collect/Comparators.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@
1717
package com.google.common.collect;
1818

1919
import static com.google.common.base.Preconditions.checkNotNull;
20+
import static com.google.common.collect.CollectPreconditions.checkNonnegative;
2021

2122
import com.google.common.annotations.GwtCompatible;
2223
import java.util.Comparator;
2324
import java.util.Iterator;
25+
import java.util.List;
26+
import java.util.stream.Collector;
2427
import org.checkerframework.checker.nullness.qual.Nullable;
2528

2629
/**
@@ -106,6 +109,65 @@ private Comparators() {}
106109
return true;
107110
}
108111

112+
/**
113+
* Returns a {@code Collector} that returns the {@code k} smallest (relative to the specified
114+
* {@code Comparator}) input elements, in ascending order, as an unmodifiable {@code List}. Ties
115+
* are broken arbitrarily.
116+
*
117+
* <p>For example:
118+
*
119+
* <pre>{@code
120+
* Stream.of("foo", "quux", "banana", "elephant")
121+
* .collect(least(2, comparingInt(String::length)))
122+
* // returns {"foo", "quux"}
123+
* }</pre>
124+
*
125+
* <p>This {@code Collector} uses O(k) memory and takes expected time O(n) (worst-case O(n log
126+
* k)), as opposed to e.g. {@code Stream.sorted(comparator).limit(k)}, which currently takes O(n
127+
* log n) time and O(n) space.
128+
*
129+
* @throws IllegalArgumentException if {@code k < 0}
130+
*/
131+
@SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"})
132+
@IgnoreJRERequirement // Users will use this only if they're already using streams.
133+
static <T extends @Nullable Object> Collector<T, ?, List<T>> least(
134+
int k, Comparator<? super T> comparator) {
135+
checkNonnegative(k, "k");
136+
checkNotNull(comparator);
137+
return Collector.of(
138+
() -> TopKSelector.<T>least(k, comparator),
139+
TopKSelector::offer,
140+
TopKSelector::combine,
141+
TopKSelector::topK,
142+
Collector.Characteristics.UNORDERED);
143+
}
144+
145+
/**
146+
* Returns a {@code Collector} that returns the {@code k} greatest (relative to the specified
147+
* {@code Comparator}) input elements, in descending order, as an unmodifiable {@code List}. Ties
148+
* are broken arbitrarily.
149+
*
150+
* <p>For example:
151+
*
152+
* <pre>{@code
153+
* Stream.of("foo", "quux", "banana", "elephant")
154+
* .collect(greatest(2, comparingInt(String::length)))
155+
* // returns {"elephant", "banana"}
156+
* }</pre>
157+
*
158+
* <p>This {@code Collector} uses O(k) memory and takes expected time O(n) (worst-case O(n log
159+
* k)), as opposed to e.g. {@code Stream.sorted(comparator.reversed()).limit(k)}, which currently
160+
* takes O(n log n) time and O(n) space.
161+
*
162+
* @throws IllegalArgumentException if {@code k < 0}
163+
*/
164+
@SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"})
165+
@IgnoreJRERequirement // Users will use this only if they're already using streams.
166+
static <T extends @Nullable Object> Collector<T, ?, List<T>> greatest(
167+
int k, Comparator<? super T> comparator) {
168+
return least(k, comparator.reversed());
169+
}
170+
109171
/**
110172
* Returns the minimum of the two values. If the values compare as 0, the first is returned.
111173
*

android/guava/src/com/google/common/collect/ImmutableBiMap.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,12 @@
2929
import java.util.Collection;
3030
import java.util.Comparator;
3131
import java.util.Map;
32+
import java.util.function.BinaryOperator;
33+
import java.util.function.Function;
34+
import java.util.stream.Collector;
35+
import java.util.stream.Collectors;
3236
import javax.annotation.CheckForNull;
37+
import org.checkerframework.checker.nullness.qual.Nullable;
3338

3439
/**
3540
* A {@link BiMap} whose contents will never change, with many other important properties detailed
@@ -42,6 +47,24 @@
4247
@ElementTypesAreNonnullByDefault
4348
public abstract class ImmutableBiMap<K, V> extends ImmutableMap<K, V> implements BiMap<K, V> {
4449

50+
/**
51+
* Returns a {@link Collector} that accumulates elements into an {@code ImmutableBiMap} whose keys
52+
* and values are the result of applying the provided mapping functions to the input elements.
53+
* Entries appear in the result {@code ImmutableBiMap} in encounter order.
54+
*
55+
* <p>If the mapped keys or values contain duplicates (according to {@link
56+
* Object#equals(Object)}), an {@code IllegalArgumentException} is thrown when the collection
57+
* operation is performed. (This differs from the {@code Collector} returned by {@link
58+
* Collectors#toMap(Function, Function)}, which throws an {@code IllegalStateException}.)
59+
*/
60+
@SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"})
61+
@IgnoreJRERequirement // Users will use this only if they're already using streams.
62+
static <T extends @Nullable Object, K, V> Collector<T, ?, ImmutableBiMap<K, V>> toImmutableBiMap(
63+
Function<? super T, ? extends K> keyFunction,
64+
Function<? super T, ? extends V> valueFunction) {
65+
return CollectCollectors.toImmutableBiMap(keyFunction, valueFunction);
66+
}
67+
4568
/**
4669
* Returns the empty bimap.
4770
*
@@ -592,5 +615,41 @@ private void readObject(ObjectInputStream stream) throws InvalidObjectException
592615
throw new InvalidObjectException("Use SerializedForm");
593616
}
594617

618+
/**
619+
* Not supported. Use {@link #toImmutableBiMap} instead. This method exists only to hide {@link
620+
* ImmutableMap#toImmutableMap(Function, Function)} from consumers of {@code ImmutableBiMap}.
621+
*
622+
* @throws UnsupportedOperationException always
623+
* @deprecated Use {@link ImmutableBiMap#toImmutableBiMap}.
624+
*/
625+
@Deprecated
626+
@DoNotCall("Use toImmutableBiMap")
627+
@SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"})
628+
@IgnoreJRERequirement // Users will use this only if they're already using streams.
629+
static <T extends @Nullable Object, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap(
630+
Function<? super T, ? extends K> keyFunction,
631+
Function<? super T, ? extends V> valueFunction) {
632+
throw new UnsupportedOperationException();
633+
}
634+
635+
/**
636+
* Not supported. This method does not make sense for {@code BiMap}. This method exists only to
637+
* hide {@link ImmutableMap#toImmutableMap(Function, Function, BinaryOperator)} from consumers of
638+
* {@code ImmutableBiMap}.
639+
*
640+
* @throws UnsupportedOperationException always
641+
* @deprecated
642+
*/
643+
@Deprecated
644+
@DoNotCall("Use toImmutableBiMap")
645+
@SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"})
646+
@IgnoreJRERequirement // Users will use this only if they're already using streams.
647+
static <T extends @Nullable Object, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap(
648+
Function<? super T, ? extends K> keyFunction,
649+
Function<? super T, ? extends V> valueFunction,
650+
BinaryOperator<V> mergeFunction) {
651+
throw new UnsupportedOperationException();
652+
}
653+
595654
private static final long serialVersionUID = 0xdecaf;
596655
}

android/guava/src/com/google/common/collect/ImmutableCollection.java

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@
3636
import java.util.HashSet;
3737
import java.util.Iterator;
3838
import java.util.List;
39-
import java.util.Spliterator;
40-
import java.util.Spliterators;
4139
import javax.annotation.CheckForNull;
4240
import org.checkerframework.checker.nullness.qual.Nullable;
4341

@@ -178,26 +176,13 @@ public abstract class ImmutableCollection<E> extends AbstractCollection<E> imple
178176
* These are properties of the collection as a whole; SIZED and SUBSIZED are more properties of
179177
* the spliterator implementation.
180178
*/
181-
@SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"})
182-
// @IgnoreJRERequirement is not necessary because this compiles down to a constant.
183-
// (which is fortunate because Animal Sniffer doesn't look for @IgnoreJRERequirement on fields)
184-
static final int SPLITERATOR_CHARACTERISTICS =
185-
Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.ORDERED;
186179

187180
ImmutableCollection() {}
188181

189182
/** Returns an unmodifiable iterator across the elements in this collection. */
190183
@Override
191184
public abstract UnmodifiableIterator<E> iterator();
192185

193-
@Override
194-
@SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"})
195-
@IgnoreJRERequirement // used only from APIs with Java 8 types in them
196-
// (not used within guava-android as of this writing, but we include it in the jar as a test)
197-
public Spliterator<E> spliterator() {
198-
return Spliterators.spliterator(this, SPLITERATOR_CHARACTERISTICS);
199-
}
200-
201186
private static final Object[] EMPTY_ARRAY = {};
202187

203188
@Override

android/guava/src/com/google/common/collect/ImmutableList.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import java.util.Iterator;
4242
import java.util.List;
4343
import java.util.RandomAccess;
44+
import java.util.stream.Collector;
4445
import javax.annotation.CheckForNull;
4546
import org.checkerframework.checker.nullness.qual.Nullable;
4647

@@ -62,6 +63,16 @@
6263
public abstract class ImmutableList<E> extends ImmutableCollection<E>
6364
implements List<E>, RandomAccess {
6465

66+
/**
67+
* Returns a {@code Collector} that accumulates the input elements into a new {@code
68+
* ImmutableList}, in encounter order.
69+
*/
70+
@SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"})
71+
@IgnoreJRERequirement // Users will use this only if they're already using streams.
72+
static <E> Collector<E, ?, ImmutableList<E>> toImmutableList() {
73+
return CollectCollectors.toImmutableList();
74+
}
75+
6576
/**
6677
* Returns the empty immutable list. This list behaves and performs comparably to {@link
6778
* Collections#emptyList}, and is preferable mainly for consistency and maintainability of your

android/guava/src/com/google/common/collect/ImmutableListMultimap.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@
3333
import java.util.Comparator;
3434
import java.util.Map;
3535
import java.util.Map.Entry;
36+
import java.util.function.Function;
37+
import java.util.stream.Collector;
38+
import java.util.stream.Stream;
3639
import javax.annotation.CheckForNull;
40+
import org.checkerframework.checker.nullness.qual.Nullable;
3741

3842
/**
3943
* A {@link ListMultimap} whose contents will never change, with many other important properties
@@ -49,6 +53,78 @@
4953
@ElementTypesAreNonnullByDefault
5054
public class ImmutableListMultimap<K, V> extends ImmutableMultimap<K, V>
5155
implements ListMultimap<K, V> {
56+
/**
57+
* Returns a {@link Collector} that accumulates elements into an {@code ImmutableListMultimap}
58+
* whose keys and values are the result of applying the provided mapping functions to the input
59+
* elements.
60+
*
61+
* <p>For streams with defined encounter order (as defined in the Ordering section of the {@link
62+
* java.util.stream} Javadoc), that order is preserved, but entries are <a
63+
* href="ImmutableMultimap.html#iteration">grouped by key</a>.
64+
*
65+
* <p>Example:
66+
*
67+
* <pre>{@code
68+
* static final Multimap<Character, String> FIRST_LETTER_MULTIMAP =
69+
* Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
70+
* .collect(toImmutableListMultimap(str -> str.charAt(0), str -> str.substring(1)));
71+
*
72+
* // is equivalent to
73+
*
74+
* static final Multimap<Character, String> FIRST_LETTER_MULTIMAP =
75+
* new ImmutableListMultimap.Builder<Character, String>()
76+
* .put('b', "anana")
77+
* .putAll('a', "pple", "sparagus")
78+
* .putAll('c', "arrot", "herry")
79+
* .build();
80+
* }</pre>
81+
*/
82+
@SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"})
83+
@IgnoreJRERequirement // Users will use this only if they're already using streams.
84+
static <T extends @Nullable Object, K, V>
85+
Collector<T, ?, ImmutableListMultimap<K, V>> toImmutableListMultimap(
86+
Function<? super T, ? extends K> keyFunction,
87+
Function<? super T, ? extends V> valueFunction) {
88+
return CollectCollectors.toImmutableListMultimap(keyFunction, valueFunction);
89+
}
90+
91+
/**
92+
* Returns a {@code Collector} accumulating entries into an {@code ImmutableListMultimap}. Each
93+
* input element is mapped to a key and a stream of values, each of which are put into the
94+
* resulting {@code Multimap}, in the encounter order of the stream and the encounter order of the
95+
* streams of values.
96+
*
97+
* <p>Example:
98+
*
99+
* <pre>{@code
100+
* static final ImmutableListMultimap<Character, Character> FIRST_LETTER_MULTIMAP =
101+
* Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
102+
* .collect(
103+
* flatteningToImmutableListMultimap(
104+
* str -> str.charAt(0),
105+
* str -> str.substring(1).chars().mapToObj(c -> (char) c));
106+
*
107+
* // is equivalent to
108+
*
109+
* static final ImmutableListMultimap<Character, Character> FIRST_LETTER_MULTIMAP =
110+
* ImmutableListMultimap.<Character, Character>builder()
111+
* .putAll('b', Arrays.asList('a', 'n', 'a', 'n', 'a'))
112+
* .putAll('a', Arrays.asList('p', 'p', 'l', 'e'))
113+
* .putAll('c', Arrays.asList('a', 'r', 'r', 'o', 't'))
114+
* .putAll('a', Arrays.asList('s', 'p', 'a', 'r', 'a', 'g', 'u', 's'))
115+
* .putAll('c', Arrays.asList('h', 'e', 'r', 'r', 'y'))
116+
* .build();
117+
* }
118+
* }</pre>
119+
*/
120+
@SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"})
121+
@IgnoreJRERequirement // Users will use this only if they're already using streams.
122+
static <T extends @Nullable Object, K, V>
123+
Collector<T, ?, ImmutableListMultimap<K, V>> flatteningToImmutableListMultimap(
124+
Function<? super T, ? extends K> keyFunction,
125+
Function<? super T, ? extends Stream<? extends V>> valuesFunction) {
126+
return CollectCollectors.flatteningToImmutableListMultimap(keyFunction, valuesFunction);
127+
}
52128

53129
/**
54130
* Returns the empty multimap.

android/guava/src/com/google/common/collect/ImmutableMap.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@
4646
import java.util.Map.Entry;
4747
import java.util.Set;
4848
import java.util.SortedMap;
49+
import java.util.function.BinaryOperator;
50+
import java.util.function.Function;
51+
import java.util.stream.Collector;
52+
import java.util.stream.Collectors;
4953
import javax.annotation.CheckForNull;
5054
import org.checkerframework.checker.nullness.qual.Nullable;
5155

@@ -66,6 +70,44 @@
6670
@ElementTypesAreNonnullByDefault
6771
public abstract class ImmutableMap<K, V> implements Map<K, V>, Serializable {
6872

73+
/**
74+
* Returns a {@link Collector} that accumulates elements into an {@code ImmutableMap} whose keys
75+
* and values are the result of applying the provided mapping functions to the input elements.
76+
* Entries appear in the result {@code ImmutableMap} in encounter order.
77+
*
78+
* <p>If the mapped keys contain duplicates (according to {@link Object#equals(Object)}, an {@code
79+
* IllegalArgumentException} is thrown when the collection operation is performed. (This differs
80+
* from the {@code Collector} returned by {@link Collectors#toMap(Function, Function)}, which
81+
* throws an {@code IllegalStateException}.)
82+
*/
83+
@SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"})
84+
@IgnoreJRERequirement // Users will use this only if they're already using streams.
85+
static <T extends @Nullable Object, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap(
86+
Function<? super T, ? extends K> keyFunction,
87+
Function<? super T, ? extends V> valueFunction) {
88+
return CollectCollectors.toImmutableMap(keyFunction, valueFunction);
89+
}
90+
91+
/**
92+
* Returns a {@link Collector} that accumulates elements into an {@code ImmutableMap} whose keys
93+
* and values are the result of applying the provided mapping functions to the input elements.
94+
*
95+
* <p>If the mapped keys contain duplicates (according to {@link Object#equals(Object)}), the
96+
* values are merged using the specified merging function. If the merging function returns {@code
97+
* null}, then the collector removes the value that has been computed for the key thus far (though
98+
* future occurrences of the key would reinsert it).
99+
*
100+
* <p>Entries will appear in the encounter order of the first occurrence of the key.
101+
*/
102+
@SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"})
103+
@IgnoreJRERequirement // Users will use this only if they're already using streams.
104+
static <T extends @Nullable Object, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap(
105+
Function<? super T, ? extends K> keyFunction,
106+
Function<? super T, ? extends V> valueFunction,
107+
BinaryOperator<V> mergeFunction) {
108+
return CollectCollectors.toImmutableMap(keyFunction, valueFunction, mergeFunction);
109+
}
110+
69111
/**
70112
* Returns the empty map. This map behaves and performs comparably to {@link
71113
* Collections#emptyMap}, and is preferable mainly for consistency and maintainability of your

0 commit comments

Comments
 (0)