diff --git a/android/guava-tests/test/com/google/common/util/concurrent/FuturesTest.java b/android/guava-tests/test/com/google/common/util/concurrent/FuturesTest.java index 1f10abe73ede..f78c66901110 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/FuturesTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/FuturesTest.java @@ -2929,6 +2929,39 @@ public void testSuccessfulAsList_partialFailure() throws Exception { assertThat(results).containsExactly(null, DATA2).inOrder(); } + public void testSuccessfulAsList_withNullAndFailure() throws Exception { + SettableFuture future1 = SettableFuture.create(); + SettableFuture future2 = SettableFuture.create(); + ListenableFuture> compound = successfulAsList(future1, future2); + + future1.set(null); + future2.setException(new Exception()); + + assertThat(getDone(compound)).containsExactly(null, null).inOrder(); + } + + public void testSuccessfulAsList_withCancellation() throws Exception { + SettableFuture future1 = SettableFuture.create(); + SettableFuture future2 = SettableFuture.create(); + ListenableFuture> compound = successfulAsList(future1, future2); + + future1.cancel(false); + future2.set(DATA2); + + assertThat(getDone(compound)).containsExactly(null, DATA2).inOrder(); + } + + public void testAllAsList_withNull() throws Exception { + SettableFuture future1 = SettableFuture.create(); + SettableFuture future2 = SettableFuture.create(); + ListenableFuture> compound = allAsList(future1, future2); + + future1.set(null); + future2.set(DATA2); + + assertThat(getDone(compound)).containsExactly(null, DATA2).inOrder(); + } + public void testSuccessfulAsList_totalFailure() throws Exception { SingleCallListener listener = new SingleCallListener(); SettableFuture future1 = SettableFuture.create(); diff --git a/android/guava/src/com/google/common/util/concurrent/CollectionFuture.java b/android/guava/src/com/google/common/util/concurrent/CollectionFuture.java index 49e013a02dc3..64edd6347e17 100644 --- a/android/guava/src/com/google/common/util/concurrent/CollectionFuture.java +++ b/android/guava/src/com/google/common/util/concurrent/CollectionFuture.java @@ -15,14 +15,13 @@ package com.google.common.util.concurrent; import static com.google.common.collect.Lists.newArrayListWithCapacity; +import static java.util.Collections.emptyList; import static java.util.Collections.unmodifiableList; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ImmutableCollection; -import com.google.common.collect.Lists; import com.google.errorprone.annotations.concurrent.LazyInit; import com.google.j2objc.annotations.RetainedLocalRef; -import java.util.Collections; import java.util.List; import org.jspecify.annotations.Nullable; @@ -36,7 +35,7 @@ abstract class CollectionFuture> values; + @LazyInit private @Nullable List<@Nullable Object> values; @SuppressWarnings("EmptyList") // ImmutableList doesn't support nullable element types CollectionFuture( @@ -44,10 +43,8 @@ abstract class CollectionFuture> values = - futures.isEmpty() - ? Collections.<@Nullable Present>emptyList() - : Lists.<@Nullable Present>newArrayListWithCapacity(futures.size()); + List<@Nullable Object> values = + futures.isEmpty() ? emptyList() : newArrayListWithCapacity(futures.size()); // Populate the results list with null initially. for (int i = 0; i < futures.size(); ++i) { @@ -59,15 +56,15 @@ abstract class CollectionFuture> localValues = values; + @RetainedLocalRef List<@Nullable Object> localValues = values; if (localValues != null) { - localValues.set(index, new Present<>(returnValue)); + localValues.set(index, returnValue == null ? NULL : returnValue); } } @Override final void handleAllCompleted() { - @RetainedLocalRef List<@Nullable Present> localValues = values; + @RetainedLocalRef List<@Nullable Object> localValues = values; if (localValues != null) { set(combine(localValues)); } @@ -79,7 +76,7 @@ void releaseResources(ReleaseResourcesReason reason) { this.values = null; } - abstract C combine(List<@Nullable Present> values); + abstract C combine(List<@Nullable Object> values); /** Used for {@link Futures#allAsList} and {@link Futures#successfulAsList}. */ static final class ListFuture @@ -92,21 +89,16 @@ static final class ListFuture } @Override - public List<@Nullable V> combine(List<@Nullable Present> values) { + public List<@Nullable V> combine(List<@Nullable Object> values) { List<@Nullable V> result = newArrayListWithCapacity(values.size()); - for (Present element : values) { - result.add(element != null ? element.value : null); + for (Object element : values) { + @SuppressWarnings("unchecked") // we stored either V or NULL + V value = (element == null || element == NULL) ? null : (V) element; + result.add(value); } return unmodifiableList(result); } } - /** The result of a successful {@code Future}. */ - private static final class Present { - @ParametricNullness final V value; - - Present(@ParametricNullness V value) { - this.value = value; - } - } + private static final Object NULL = new Object(); } diff --git a/guava-tests/test/com/google/common/util/concurrent/FuturesTest.java b/guava-tests/test/com/google/common/util/concurrent/FuturesTest.java index 1f10abe73ede..f78c66901110 100644 --- a/guava-tests/test/com/google/common/util/concurrent/FuturesTest.java +++ b/guava-tests/test/com/google/common/util/concurrent/FuturesTest.java @@ -2929,6 +2929,39 @@ public void testSuccessfulAsList_partialFailure() throws Exception { assertThat(results).containsExactly(null, DATA2).inOrder(); } + public void testSuccessfulAsList_withNullAndFailure() throws Exception { + SettableFuture future1 = SettableFuture.create(); + SettableFuture future2 = SettableFuture.create(); + ListenableFuture> compound = successfulAsList(future1, future2); + + future1.set(null); + future2.setException(new Exception()); + + assertThat(getDone(compound)).containsExactly(null, null).inOrder(); + } + + public void testSuccessfulAsList_withCancellation() throws Exception { + SettableFuture future1 = SettableFuture.create(); + SettableFuture future2 = SettableFuture.create(); + ListenableFuture> compound = successfulAsList(future1, future2); + + future1.cancel(false); + future2.set(DATA2); + + assertThat(getDone(compound)).containsExactly(null, DATA2).inOrder(); + } + + public void testAllAsList_withNull() throws Exception { + SettableFuture future1 = SettableFuture.create(); + SettableFuture future2 = SettableFuture.create(); + ListenableFuture> compound = allAsList(future1, future2); + + future1.set(null); + future2.set(DATA2); + + assertThat(getDone(compound)).containsExactly(null, DATA2).inOrder(); + } + public void testSuccessfulAsList_totalFailure() throws Exception { SingleCallListener listener = new SingleCallListener(); SettableFuture future1 = SettableFuture.create(); diff --git a/guava/src/com/google/common/util/concurrent/CollectionFuture.java b/guava/src/com/google/common/util/concurrent/CollectionFuture.java index 49e013a02dc3..64edd6347e17 100644 --- a/guava/src/com/google/common/util/concurrent/CollectionFuture.java +++ b/guava/src/com/google/common/util/concurrent/CollectionFuture.java @@ -15,14 +15,13 @@ package com.google.common.util.concurrent; import static com.google.common.collect.Lists.newArrayListWithCapacity; +import static java.util.Collections.emptyList; import static java.util.Collections.unmodifiableList; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ImmutableCollection; -import com.google.common.collect.Lists; import com.google.errorprone.annotations.concurrent.LazyInit; import com.google.j2objc.annotations.RetainedLocalRef; -import java.util.Collections; import java.util.List; import org.jspecify.annotations.Nullable; @@ -36,7 +35,7 @@ abstract class CollectionFuture> values; + @LazyInit private @Nullable List<@Nullable Object> values; @SuppressWarnings("EmptyList") // ImmutableList doesn't support nullable element types CollectionFuture( @@ -44,10 +43,8 @@ abstract class CollectionFuture> values = - futures.isEmpty() - ? Collections.<@Nullable Present>emptyList() - : Lists.<@Nullable Present>newArrayListWithCapacity(futures.size()); + List<@Nullable Object> values = + futures.isEmpty() ? emptyList() : newArrayListWithCapacity(futures.size()); // Populate the results list with null initially. for (int i = 0; i < futures.size(); ++i) { @@ -59,15 +56,15 @@ abstract class CollectionFuture> localValues = values; + @RetainedLocalRef List<@Nullable Object> localValues = values; if (localValues != null) { - localValues.set(index, new Present<>(returnValue)); + localValues.set(index, returnValue == null ? NULL : returnValue); } } @Override final void handleAllCompleted() { - @RetainedLocalRef List<@Nullable Present> localValues = values; + @RetainedLocalRef List<@Nullable Object> localValues = values; if (localValues != null) { set(combine(localValues)); } @@ -79,7 +76,7 @@ void releaseResources(ReleaseResourcesReason reason) { this.values = null; } - abstract C combine(List<@Nullable Present> values); + abstract C combine(List<@Nullable Object> values); /** Used for {@link Futures#allAsList} and {@link Futures#successfulAsList}. */ static final class ListFuture @@ -92,21 +89,16 @@ static final class ListFuture } @Override - public List<@Nullable V> combine(List<@Nullable Present> values) { + public List<@Nullable V> combine(List<@Nullable Object> values) { List<@Nullable V> result = newArrayListWithCapacity(values.size()); - for (Present element : values) { - result.add(element != null ? element.value : null); + for (Object element : values) { + @SuppressWarnings("unchecked") // we stored either V or NULL + V value = (element == null || element == NULL) ? null : (V) element; + result.add(value); } return unmodifiableList(result); } } - /** The result of a successful {@code Future}. */ - private static final class Present { - @ParametricNullness final V value; - - Present(@ParametricNullness V value) { - this.value = value; - } - } + private static final Object NULL = new Object(); }