Skip to content

Commit

Permalink
Include our Collector APIs as package-private in our open-source An…
Browse files Browse the repository at this point in the history
…droid 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
  • Loading branch information
cpovirk authored and Google Java Core Libraries committed Dec 8, 2023
1 parent 64a8435 commit 73dbf7e
Show file tree
Hide file tree
Showing 21 changed files with 1,072 additions and 20 deletions.
62 changes: 62 additions & 0 deletions android/guava/src/com/google/common/collect/Comparators.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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.
*
* <p>For example:
*
* <pre>{@code
* Stream.of("foo", "quux", "banana", "elephant")
* .collect(least(2, comparingInt(String::length)))
* // returns {"foo", "quux"}
* }</pre>
*
* <p>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 <T extends @Nullable Object> Collector<T, ?, List<T>> least(
int k, Comparator<? super T> comparator) {
checkNonnegative(k, "k");
checkNotNull(comparator);
return Collector.of(
() -> TopKSelector.<T>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.
*
* <p>For example:
*
* <pre>{@code
* Stream.of("foo", "quux", "banana", "elephant")
* .collect(greatest(2, comparingInt(String::length)))
* // returns {"elephant", "banana"}
* }</pre>
*
* <p>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 <T extends @Nullable Object> Collector<T, ?, List<T>> greatest(
int k, Comparator<? super T> comparator) {
return least(k, comparator.reversed());
}

/**
* Returns the minimum of the two values. If the values compare as 0, the first is returned.
*
Expand Down
59 changes: 59 additions & 0 deletions android/guava/src/com/google/common/collect/ImmutableBiMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -42,6 +47,24 @@
@ElementTypesAreNonnullByDefault
public abstract class ImmutableBiMap<K, V> extends ImmutableMap<K, V> implements BiMap<K, V> {

/**
* 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.
*
* <p>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 <T extends @Nullable Object, K, V> Collector<T, ?, ImmutableBiMap<K, V>> toImmutableBiMap(
Function<? super T, ? extends K> keyFunction,
Function<? super T, ? extends V> valueFunction) {
return CollectCollectors.toImmutableBiMap(keyFunction, valueFunction);
}

/**
* Returns the empty bimap.
*
Expand Down Expand Up @@ -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 <T extends @Nullable Object, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap(
Function<? super T, ? extends K> keyFunction,
Function<? super T, ? extends V> 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 <T extends @Nullable Object, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap(
Function<? super T, ? extends K> keyFunction,
Function<? super T, ? extends V> valueFunction,
BinaryOperator<V> mergeFunction) {
throw new UnsupportedOperationException();
}

private static final long serialVersionUID = 0xdecaf;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -178,26 +176,13 @@ public abstract class ImmutableCollection<E> extends AbstractCollection<E> 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() {}

/** Returns an unmodifiable iterator across the elements in this collection. */
@Override
public abstract UnmodifiableIterator<E> 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<E> spliterator() {
return Spliterators.spliterator(this, SPLITERATOR_CHARACTERISTICS);
}

private static final Object[] EMPTY_ARRAY = {};

@Override
Expand Down
11 changes: 11 additions & 0 deletions android/guava/src/com/google/common/collect/ImmutableList.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -62,6 +63,16 @@
public abstract class ImmutableList<E> extends ImmutableCollection<E>
implements List<E>, 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 <E> Collector<E, ?, ImmutableList<E>> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -49,6 +53,78 @@
@ElementTypesAreNonnullByDefault
public class ImmutableListMultimap<K, V> extends ImmutableMultimap<K, V>
implements ListMultimap<K, V> {
/**
* 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.
*
* <p>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 <a
* href="ImmutableMultimap.html#iteration">grouped by key</a>.
*
* <p>Example:
*
* <pre>{@code
* static final Multimap<Character, String> 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<Character, String> FIRST_LETTER_MULTIMAP =
* new ImmutableListMultimap.Builder<Character, String>()
* .put('b', "anana")
* .putAll('a', "pple", "sparagus")
* .putAll('c', "arrot", "herry")
* .build();
* }</pre>
*/
@SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"})
@IgnoreJRERequirement // Users will use this only if they're already using streams.
static <T extends @Nullable Object, K, V>
Collector<T, ?, ImmutableListMultimap<K, V>> toImmutableListMultimap(
Function<? super T, ? extends K> keyFunction,
Function<? super T, ? extends V> 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.
*
* <p>Example:
*
* <pre>{@code
* static final ImmutableListMultimap<Character, Character> 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<Character, Character> FIRST_LETTER_MULTIMAP =
* ImmutableListMultimap.<Character, Character>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();
* }
* }</pre>
*/
@SuppressWarnings({"AndroidJdkLibsChecker", "Java7ApiChecker"})
@IgnoreJRERequirement // Users will use this only if they're already using streams.
static <T extends @Nullable Object, K, V>
Collector<T, ?, ImmutableListMultimap<K, V>> flatteningToImmutableListMultimap(
Function<? super T, ? extends K> keyFunction,
Function<? super T, ? extends Stream<? extends V>> valuesFunction) {
return CollectCollectors.flatteningToImmutableListMultimap(keyFunction, valuesFunction);
}

/**
* Returns the empty multimap.
Expand Down
42 changes: 42 additions & 0 deletions android/guava/src/com/google/common/collect/ImmutableMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -66,6 +70,44 @@
@ElementTypesAreNonnullByDefault
public abstract class ImmutableMap<K, V> implements Map<K, V>, 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.
*
* <p>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 <T extends @Nullable Object, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap(
Function<? super T, ? extends K> keyFunction,
Function<? super T, ? extends V> 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.
*
* <p>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).
*
* <p>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 <T extends @Nullable Object, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap(
Function<? super T, ? extends K> keyFunction,
Function<? super T, ? extends V> valueFunction,
BinaryOperator<V> 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
Expand Down
Loading

0 comments on commit 73dbf7e

Please sign in to comment.