From 6ace8bc8ea31d6eb9bc6e2a4698807005c00399c Mon Sep 17 00:00:00 2001 From: cpovirk Date: Thu, 31 Oct 2024 07:17:24 -0700 Subject: [PATCH] Expose more Java 8 APIs to Android users. While there, remove a workaround for https://github.com/google/guava/issues/1330: As discussed in cl/152418428, the problem was fixed for Java 8, and even Android has [a working implementation](https://cs.android.com/android/platform/superproject/+/android-5.0.0_r1.0.1:libcore/luni/src/main/java/java/io/FilterOutputStream.java;l=63,75;drc=eb8027492e81d5d3a0d1cd49494c59f9a03eeaa3) by 5.0.0, [aka Lollipop](https://source.android.com/docs/setup/reference/build-numbers), which is [what we target](https://guava.dev/#important-warnings). And address a few more warnings. RELNOTES=Exposed some additional Java 8 APIs to Android users. Plus: `io`: Changed `ByteSink` and `CharSink` to no longer call `flush()` in some cases before `close()`. This is a no-op for well-behaved streams, which internally flush their data as part of closing. However, we have discovered some stream implementations that have overridden `close()` to do nothing, including not to flush some buffered data. If this change causes problems, the simplest fix is usually to change the `close()` override to at least call `flush()`. PiperOrigin-RevId: 691790805 --- .../collect/testing/IgnoreJRERequirement.java | 30 + .../collect/testing/SpliteratorTester.java | 349 ++++++ .../com/google/common/base/OptionalTest.java | 18 + .../collect/CollectSpliteratorsTest.java | 108 ++ .../com/google/common/io/CharSinkTest.java | 12 + .../com/google/common/io/CharSourceTest.java | 30 + .../src/com/google/common/base/Optional.java | 56 + .../src/com/google/common/base/Stopwatch.java | 35 +- .../common/collect/CollectSpliterators.java | 568 +++++++++ .../com/google/common/collect/Streams.java | 1013 +++++++++++++++++ .../src/com/google/common/io/CharSink.java | 62 +- .../src/com/google/common/io/CharSource.java | 91 ++ android/pom.xml | 14 +- .../collect/testing/IgnoreJRERequirement.java | 30 + .../collect/testing/SpliteratorTester.java | 8 +- .../com/google/common/io/CharSinkTest.java | 3 +- .../src/com/google/common/base/Optional.java | 8 +- .../src/com/google/common/base/Stopwatch.java | 2 +- .../com/google/common/collect/Streams.java | 12 +- guava/src/com/google/common/io/CharSink.java | 19 +- .../src/com/google/common/io/CharSource.java | 34 +- pom.xml | 14 +- 22 files changed, 2448 insertions(+), 68 deletions(-) create mode 100644 android/guava-testlib/src/com/google/common/collect/testing/IgnoreJRERequirement.java create mode 100644 android/guava-testlib/src/com/google/common/collect/testing/SpliteratorTester.java create mode 100644 android/guava-tests/test/com/google/common/collect/CollectSpliteratorsTest.java create mode 100644 android/guava/src/com/google/common/collect/CollectSpliterators.java create mode 100644 android/guava/src/com/google/common/collect/Streams.java create mode 100644 guava-testlib/src/com/google/common/collect/testing/IgnoreJRERequirement.java diff --git a/android/guava-testlib/src/com/google/common/collect/testing/IgnoreJRERequirement.java b/android/guava-testlib/src/com/google/common/collect/testing/IgnoreJRERequirement.java new file mode 100644 index 000000000000..7bc6548bcab2 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/IgnoreJRERequirement.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 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.testing; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Target; + +/** + * Disables Animal Sniffer's checking of compatibility with older versions of Java/Android. + * + *

Each package's copy of this annotation needs to be listed in our {@code pom.xml}. + */ +@Target({METHOD, CONSTRUCTOR, TYPE}) +@ElementTypesAreNonnullByDefault +@interface IgnoreJRERequirement {} diff --git a/android/guava-testlib/src/com/google/common/collect/testing/SpliteratorTester.java b/android/guava-testlib/src/com/google/common/collect/testing/SpliteratorTester.java new file mode 100644 index 000000000000..09e171386535 --- /dev/null +++ b/android/guava-testlib/src/com/google/common/collect/testing/SpliteratorTester.java @@ -0,0 +1,349 @@ +/* + * Copyright (C) 2015 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.testing; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.testing.Helpers.assertEqualIgnoringOrder; +import static com.google.common.collect.testing.Helpers.assertEqualInOrder; +import static com.google.common.collect.testing.Platform.format; +import static java.util.Arrays.asList; +import static java.util.Collections.unmodifiableSet; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Ordering; +import com.google.common.primitives.Ints; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterator.OfPrimitive; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Tester for {@code Spliterator} implementations. + * + * @since NEXT (but since 21.0 in the JRE flavor) + */ +@GwtCompatible +@ElementTypesAreNonnullByDefault +@SuppressWarnings("Java7ApiChecker") +@IgnoreJRERequirement // Users will use this only if they're already using Spliterator. +public final class SpliteratorTester { + /** Return type from "contains the following elements" assertions. */ + public interface Ordered { + /** + * Attests that the expected values must not just be present but must be present in the order + * they were given. + */ + void inOrder(); + } + + @IgnoreJRERequirement // *should* be redundant with the annotation on SpliteratorTester + private abstract static class GeneralSpliterator { + final Spliterator spliterator; + + GeneralSpliterator(Spliterator spliterator) { + this.spliterator = checkNotNull(spliterator); + } + + abstract void forEachRemaining(Consumer action); + + abstract boolean tryAdvance(Consumer action); + + abstract @Nullable GeneralSpliterator trySplit(); + + final int characteristics() { + return spliterator.characteristics(); + } + + final long estimateSize() { + return spliterator.estimateSize(); + } + + final Comparator getComparator() { + return spliterator.getComparator(); + } + + final long getExactSizeIfKnown() { + return spliterator.getExactSizeIfKnown(); + } + + final boolean hasCharacteristics(int characteristics) { + return spliterator.hasCharacteristics(characteristics); + } + } + + @IgnoreJRERequirement // *should* be redundant with the annotation on SpliteratorTester + private static final class GeneralSpliteratorOfObject + extends GeneralSpliterator { + GeneralSpliteratorOfObject(Spliterator spliterator) { + super(spliterator); + } + + @Override + void forEachRemaining(Consumer action) { + spliterator.forEachRemaining(action); + } + + @Override + boolean tryAdvance(Consumer action) { + return spliterator.tryAdvance(action); + } + + @Override + @Nullable GeneralSpliterator trySplit() { + Spliterator split = spliterator.trySplit(); + return split == null ? null : new GeneralSpliteratorOfObject<>(split); + } + } + + @IgnoreJRERequirement // *should* be redundant with the annotation on SpliteratorTester + private static final class GeneralSpliteratorOfPrimitive< + E extends @Nullable Object, C, S extends Spliterator.OfPrimitive> + extends GeneralSpliterator { + final OfPrimitive spliteratorOfPrimitive; + final Function, C> consumerizer; + + GeneralSpliteratorOfPrimitive( + Spliterator.OfPrimitive spliterator, + Function, C> consumerizer) { + super(spliterator); + this.spliteratorOfPrimitive = spliterator; + this.consumerizer = consumerizer; + } + + @Override + void forEachRemaining(Consumer action) { + spliteratorOfPrimitive.forEachRemaining(consumerizer.apply(action)); + } + + @Override + boolean tryAdvance(Consumer action) { + return spliteratorOfPrimitive.tryAdvance(consumerizer.apply(action)); + } + + @Override + @Nullable GeneralSpliterator trySplit() { + Spliterator.OfPrimitive split = spliteratorOfPrimitive.trySplit(); + return split == null ? null : new GeneralSpliteratorOfPrimitive<>(split, consumerizer); + } + } + + /** + * Different ways of decomposing a Spliterator, all of which must produce the same elements (up to + * ordering, if Spliterator.ORDERED is not present). + */ + @IgnoreJRERequirement // *should* be redundant with the annotation on SpliteratorTester + enum SpliteratorDecompositionStrategy { + NO_SPLIT_FOR_EACH_REMAINING { + @Override + void forEach( + GeneralSpliterator spliterator, Consumer consumer) { + spliterator.forEachRemaining(consumer); + } + }, + NO_SPLIT_TRY_ADVANCE { + @Override + void forEach( + GeneralSpliterator spliterator, Consumer consumer) { + while (spliterator.tryAdvance(consumer)) { + // do nothing + } + } + }, + MAXIMUM_SPLIT { + @Override + void forEach( + GeneralSpliterator spliterator, Consumer consumer) { + for (GeneralSpliterator prefix = trySplitTestingSize(spliterator); + prefix != null; + prefix = trySplitTestingSize(spliterator)) { + forEach(prefix, consumer); + } + long size = spliterator.getExactSizeIfKnown(); + long[] counter = {0}; + spliterator.forEachRemaining( + e -> { + consumer.accept(e); + counter[0]++; + }); + if (size >= 0) { + assertEquals(size, counter[0]); + } + } + }, + ALTERNATE_ADVANCE_AND_SPLIT { + @Override + void forEach( + GeneralSpliterator spliterator, Consumer consumer) { + while (spliterator.tryAdvance(consumer)) { + GeneralSpliterator prefix = trySplitTestingSize(spliterator); + if (prefix != null) { + forEach(prefix, consumer); + } + } + } + }; + + abstract void forEach( + GeneralSpliterator spliterator, Consumer consumer); + + static final Set ALL_STRATEGIES = + unmodifiableSet(new LinkedHashSet<>(asList(values()))); + } + + private static @Nullable GeneralSpliterator trySplitTestingSize( + GeneralSpliterator spliterator) { + boolean subsized = spliterator.hasCharacteristics(Spliterator.SUBSIZED); + long originalSize = spliterator.estimateSize(); + GeneralSpliterator trySplit = spliterator.trySplit(); + if (spliterator.estimateSize() > originalSize) { + fail( + format( + "estimated size of spliterator after trySplit (%s) is larger than original size (%s)", + spliterator.estimateSize(), originalSize)); + } + if (trySplit != null) { + if (trySplit.estimateSize() > originalSize) { + fail( + format( + "estimated size of trySplit result (%s) is larger than original size (%s)", + trySplit.estimateSize(), originalSize)); + } + } + if (subsized) { + if (trySplit != null) { + assertEquals( + "sum of estimated sizes of trySplit and original spliterator after trySplit", + originalSize, + trySplit.estimateSize() + spliterator.estimateSize()); + } else { + assertEquals( + "estimated size of spliterator after failed trySplit", + originalSize, + spliterator.estimateSize()); + } + } + return trySplit; + } + + public static SpliteratorTester of( + Supplier> spliteratorSupplier) { + return new SpliteratorTester<>( + ImmutableSet.of(() -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get()))); + } + + /** + * @since NEXT (but since 28.1 in the JRE flavor) + */ + public static SpliteratorTester ofInt(Supplier spliteratorSupplier) { + return new SpliteratorTester<>( + ImmutableSet.of( + () -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get()), + () -> new GeneralSpliteratorOfPrimitive<>(spliteratorSupplier.get(), c -> c::accept))); + } + + /** + * @since NEXT (but since 28.1 in the JRE flavor) + */ + public static SpliteratorTester ofLong(Supplier spliteratorSupplier) { + return new SpliteratorTester<>( + ImmutableSet.of( + () -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get()), + () -> new GeneralSpliteratorOfPrimitive<>(spliteratorSupplier.get(), c -> c::accept))); + } + + /** + * @since NEXT (but since 28.1 in the JRE flavor) + */ + public static SpliteratorTester ofDouble( + Supplier spliteratorSupplier) { + return new SpliteratorTester<>( + ImmutableSet.of( + () -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get()), + () -> new GeneralSpliteratorOfPrimitive<>(spliteratorSupplier.get(), c -> c::accept))); + } + + private final ImmutableSet>> spliteratorSuppliers; + + private SpliteratorTester(ImmutableSet>> spliteratorSuppliers) { + this.spliteratorSuppliers = checkNotNull(spliteratorSuppliers); + } + + @SafeVarargs + @CanIgnoreReturnValue + public final Ordered expect(Object... elements) { + return expect(asList(elements)); + } + + @CanIgnoreReturnValue + public final Ordered expect(Iterable elements) { + List> resultsForAllStrategies = new ArrayList<>(); + for (Supplier> spliteratorSupplier : spliteratorSuppliers) { + GeneralSpliterator spliterator = spliteratorSupplier.get(); + int characteristics = spliterator.characteristics(); + long estimatedSize = spliterator.estimateSize(); + for (SpliteratorDecompositionStrategy strategy : + SpliteratorDecompositionStrategy.ALL_STRATEGIES) { + List resultsForStrategy = new ArrayList<>(); + strategy.forEach(spliteratorSupplier.get(), resultsForStrategy::add); + + // TODO(cpovirk): better failure messages + if ((characteristics & Spliterator.NONNULL) != 0) { + assertFalse(resultsForStrategy.contains(null)); + } + if ((characteristics & Spliterator.SORTED) != 0) { + Comparator comparator = spliterator.getComparator(); + if (comparator == null) { + // A sorted spliterator with no comparator is already using natural order. + // (We could probably find a way to avoid rawtypes here if we wanted.) + @SuppressWarnings({"unchecked", "rawtypes"}) + Comparator naturalOrder = + (Comparator) Comparator.naturalOrder(); + comparator = naturalOrder; + } + assertTrue(Ordering.from(comparator).isOrdered(resultsForStrategy)); + } + if ((characteristics & Spliterator.SIZED) != 0) { + assertEquals(Ints.checkedCast(estimatedSize), resultsForStrategy.size()); + } + + assertEqualIgnoringOrder(elements, resultsForStrategy); + resultsForAllStrategies.add(resultsForStrategy); + } + } + return new Ordered() { + @Override + public void inOrder() { + for (List resultsForStrategy : resultsForAllStrategies) { + assertEqualInOrder(elements, resultsForStrategy); + } + } + }; + } +} diff --git a/android/guava-tests/test/com/google/common/base/OptionalTest.java b/android/guava-tests/test/com/google/common/base/OptionalTest.java index 2df1a8e62225..ec197f5c2b82 100644 --- a/android/guava-tests/test/com/google/common/base/OptionalTest.java +++ b/android/guava-tests/test/com/google/common/base/OptionalTest.java @@ -41,6 +41,24 @@ @ElementTypesAreNonnullByDefault @GwtCompatible(emulated = true) public final class OptionalTest extends TestCase { + @SuppressWarnings("NullOptional") + public void testToJavaUtil_static() { + assertNull(Optional.toJavaUtil(null)); + assertEquals(java.util.Optional.empty(), Optional.toJavaUtil(Optional.absent())); + assertEquals(java.util.Optional.of("abc"), Optional.toJavaUtil(Optional.of("abc"))); + } + + public void testToJavaUtil_instance() { + assertEquals(java.util.Optional.empty(), Optional.absent().toJavaUtil()); + assertEquals(java.util.Optional.of("abc"), Optional.of("abc").toJavaUtil()); + } + + @SuppressWarnings("NullOptional") + public void testFromJavaUtil() { + assertNull(Optional.fromJavaUtil(null)); + assertEquals(Optional.absent(), Optional.fromJavaUtil(java.util.Optional.empty())); + assertEquals(Optional.of("abc"), Optional.fromJavaUtil(java.util.Optional.of("abc"))); + } public void testAbsent() { Optional optionalName = Optional.absent(); diff --git a/android/guava-tests/test/com/google/common/collect/CollectSpliteratorsTest.java b/android/guava-tests/test/com/google/common/collect/CollectSpliteratorsTest.java new file mode 100644 index 000000000000..44815e122dfa --- /dev/null +++ b/android/guava-tests/test/com/google/common/collect/CollectSpliteratorsTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2015 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.collect.Lists.charactersOf; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Ascii; +import com.google.common.collect.testing.SpliteratorTester; +import java.util.Arrays; +import java.util.List; +import java.util.Spliterator; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import junit.framework.TestCase; + +/** Tests for {@code CollectSpliterators}. */ +@GwtCompatible +@ElementTypesAreNonnullByDefault +public class CollectSpliteratorsTest extends TestCase { + public void testMap() { + SpliteratorTester.of( + () -> + CollectSpliterators.map( + Arrays.spliterator(new String[] {"a", "b", "c", "d", "e"}), Ascii::toUpperCase)) + .expect("A", "B", "C", "D", "E"); + } + + public void testFlatMap() { + SpliteratorTester.of( + () -> + CollectSpliterators.flatMap( + Arrays.spliterator(new String[] {"abc", "", "de", "f", "g", ""}), + (String str) -> charactersOf(str).spliterator(), + Spliterator.SIZED | Spliterator.DISTINCT | Spliterator.NONNULL, + 7)) + .expect('a', 'b', 'c', 'd', 'e', 'f', 'g'); + } + + public void testFlatMap_nullStream() { + SpliteratorTester.of( + () -> + CollectSpliterators.flatMap( + Arrays.spliterator(new String[] {"abc", "", "de", "f", "g", ""}), + (String str) -> str.isEmpty() ? null : charactersOf(str).spliterator(), + Spliterator.SIZED | Spliterator.DISTINCT | Spliterator.NONNULL, + 7)) + .expect('a', 'b', 'c', 'd', 'e', 'f', 'g'); + } + + public void testFlatMapToInt_nullStream() { + SpliteratorTester.ofInt( + () -> + CollectSpliterators.flatMapToInt( + Arrays.spliterator(new Integer[] {1, 0, 1, 2, 3}), + (Integer i) -> i == 0 ? null : IntStream.of(i).spliterator(), + Spliterator.SIZED | Spliterator.DISTINCT | Spliterator.NONNULL, + 4)) + .expect(1, 1, 2, 3); + } + + public void testFlatMapToLong_nullStream() { + SpliteratorTester.ofLong( + () -> + CollectSpliterators.flatMapToLong( + Arrays.spliterator(new Long[] {1L, 0L, 1L, 2L, 3L}), + (Long i) -> i == 0L ? null : LongStream.of(i).spliterator(), + Spliterator.SIZED | Spliterator.DISTINCT | Spliterator.NONNULL, + 4)) + .expect(1L, 1L, 2L, 3L); + } + + public void testFlatMapToDouble_nullStream() { + SpliteratorTester.ofDouble( + () -> + CollectSpliterators.flatMapToDouble( + Arrays.spliterator(new Double[] {1.0, 0.0, 1.0, 2.0, 3.0}), + (Double i) -> i == 0.0 ? null : DoubleStream.of(i).spliterator(), + Spliterator.SIZED | Spliterator.DISTINCT | Spliterator.NONNULL, + 4)) + .expect(1.0, 1.0, 2.0, 3.0); + } + + public void testMultisetsSpliterator() { + Multiset multiset = TreeMultiset.create(); + multiset.add("a", 3); + multiset.add("b", 1); + multiset.add("c", 2); + + List actualValues = Lists.newArrayList(); + multiset.spliterator().forEachRemaining(actualValues::add); + assertThat(multiset).containsExactly("a", "a", "a", "b", "c", "c").inOrder(); + } +} diff --git a/android/guava-tests/test/com/google/common/io/CharSinkTest.java b/android/guava-tests/test/com/google/common/io/CharSinkTest.java index 42c84eea007f..3718c278d58d 100644 --- a/android/guava-tests/test/com/google/common/io/CharSinkTest.java +++ b/android/guava-tests/test/com/google/common/io/CharSinkTest.java @@ -16,6 +16,7 @@ package com.google.common.io; +import static com.google.common.base.StandardSystemProperty.LINE_SEPARATOR; import static com.google.common.io.TestOption.CLOSE_THROWS; import static com.google.common.io.TestOption.OPEN_THROWS; import static com.google.common.io.TestOption.READ_THROWS; @@ -90,6 +91,17 @@ public void testWriteLines_withDefaultSeparator() throws IOException { assertEquals("foo" + separator + "bar" + separator + "baz" + separator, sink.getString()); } + public void testWriteLines_stream() throws IOException { + sink.writeLines(ImmutableList.of("foo", "bar", "baz").stream()); + String separator = LINE_SEPARATOR.value(); + assertEquals("foo" + separator + "bar" + separator + "baz" + separator, sink.getString()); + } + + public void testWriteLines_stream_separator() throws IOException { + sink.writeLines(ImmutableList.of("foo", "bar", "baz").stream(), "!"); + assertEquals("foo!bar!baz!", sink.getString()); + } + public void testClosesOnErrors_copyingFromCharSourceThatThrows() { for (TestOption option : EnumSet.of(OPEN_THROWS, READ_THROWS, CLOSE_THROWS)) { TestCharSource failSource = new TestCharSource(STRING, option); diff --git a/android/guava-tests/test/com/google/common/io/CharSourceTest.java b/android/guava-tests/test/com/google/common/io/CharSourceTest.java index 2eb9706da670..e59bfcb6448c 100644 --- a/android/guava-tests/test/com/google/common/io/CharSourceTest.java +++ b/android/guava-tests/test/com/google/common/io/CharSourceTest.java @@ -16,6 +16,7 @@ package com.google.common.io; +import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.io.TestOption.CLOSE_THROWS; import static com.google.common.io.TestOption.OPEN_THROWS; import static com.google.common.io.TestOption.READ_THROWS; @@ -34,6 +35,7 @@ import java.io.Writer; import java.util.EnumSet; import java.util.List; +import java.util.stream.Stream; import junit.framework.TestSuite; /** @@ -62,6 +64,8 @@ public static TestSuite suite() { private static final String STRING = ASCII + I18N; private static final String LINES = "foo\nbar\r\nbaz\rsomething"; + private static final ImmutableList SPLIT_LINES = + ImmutableList.of("foo", "bar", "baz", "something"); private TestCharSource source; @@ -88,6 +92,21 @@ public void testOpenBufferedStream() throws IOException { assertEquals(STRING, writer.toString()); } + public void testLines() throws IOException { + source = new TestCharSource(LINES); + + ImmutableList lines; + try (Stream linesStream = source.lines()) { + assertTrue(source.wasStreamOpened()); + assertFalse(source.wasStreamClosed()); + + lines = linesStream.collect(toImmutableList()); + } + + assertTrue(source.wasStreamClosed()); + assertEquals(SPLIT_LINES, lines); + } + public void testCopyTo_appendable() throws IOException { StringBuilder builder = new StringBuilder(); @@ -170,6 +189,17 @@ public List getResult() { assertTrue(lines.wasStreamOpened() && lines.wasStreamClosed()); } + public void testForEachLine() throws IOException { + source = new TestCharSource(LINES); + + ImmutableList.Builder builder = ImmutableList.builder(); + source.forEachLine(builder::add); + + assertEquals(SPLIT_LINES, builder.build()); + assertTrue(source.wasStreamOpened()); + assertTrue(source.wasStreamClosed()); + } + public void testCopyToAppendable_doesNotCloseIfWriter() throws IOException { TestWriter writer = new TestWriter(); assertFalse(writer.closed()); diff --git a/android/guava/src/com/google/common/base/Optional.java b/android/guava/src/com/google/common/base/Optional.java index c8141322f40f..dc744ea281e0 100644 --- a/android/guava/src/com/google/common/base/Optional.java +++ b/android/guava/src/com/google/common/base/Optional.java @@ -119,6 +119,62 @@ public static Optional fromNullable(@CheckForNull T nullableReference) { return (nullableReference == null) ? Optional.absent() : new Present(nullableReference); } + /** + * Returns the equivalent {@code com.google.common.base.Optional} value to the given {@code + * java.util.Optional}, or {@code null} if the argument is null. + * + * @since NEXT (but since 21.0 in the JRE flavor) + */ + @SuppressWarnings("Java7ApiChecker") + @IgnoreJRERequirement // Users will use this only if they're already using Optional. + @CheckForNull + public static Optional fromJavaUtil(@CheckForNull java.util.Optional javaUtilOptional) { + return (javaUtilOptional == null) ? null : fromNullable(javaUtilOptional.orElse(null)); + } + + /** + * Returns the equivalent {@code java.util.Optional} value to the given {@code + * com.google.common.base.Optional}, or {@code null} if the argument is null. + * + *

If {@code googleOptional} is known to be non-null, use {@code googleOptional.toJavaUtil()} + * instead. + * + *

Unfortunately, the method reference {@code Optional::toJavaUtil} will not work, because it + * could refer to either the static or instance version of this method. Write out the lambda + * expression {@code o -> Optional.toJavaUtil(o)} instead. + * + * @since NEXT (but since 21.0 in the JRE flavor) + */ + @SuppressWarnings({ + "AmbiguousMethodReference", // We chose the name despite knowing this risk. + "Java7ApiChecker", + }) + // If users use this when they shouldn't, we hope that NewApi will catch subsequent Optional calls + @IgnoreJRERequirement + @CheckForNull + public static java.util.Optional toJavaUtil(@CheckForNull Optional googleOptional) { + return googleOptional == null ? null : googleOptional.toJavaUtil(); + } + + /** + * Returns the equivalent {@code java.util.Optional} value to this optional. + * + *

Unfortunately, the method reference {@code Optional::toJavaUtil} will not work, because it + * could refer to either the static or instance version of this method. Write out the lambda + * expression {@code o -> o.toJavaUtil()} instead. + * + * @since NEXT (but since 21.0 in the JRE flavor) + */ + @SuppressWarnings({ + "AmbiguousMethodReference", // We chose the name despite knowing this risk. + "Java7ApiChecker", + }) + // If users use this when they shouldn't, we hope that NewApi will catch subsequent Optional calls + @IgnoreJRERequirement + public java.util.Optional toJavaUtil() { + return java.util.Optional.ofNullable(orNull()); + } + Optional() {} /** diff --git a/android/guava/src/com/google/common/base/Stopwatch.java b/android/guava/src/com/google/common/base/Stopwatch.java index ba1a0f29a4f2..486df88fb04c 100644 --- a/android/guava/src/com/google/common/base/Stopwatch.java +++ b/android/guava/src/com/google/common/base/Stopwatch.java @@ -25,7 +25,11 @@ import static java.util.concurrent.TimeUnit.SECONDS; import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.j2objc.annotations.J2ObjCIncompatible; +import java.time.Duration; import java.util.concurrent.TimeUnit; /** @@ -61,7 +65,7 @@ * doSomething(); * stopwatch.stop(); // optional * - * long millis = stopwatch.elapsed(MILLISECONDS); + * Duration duration = stopwatch.elapsed(); * * log.info("time: " + stopwatch); // formatted string like "12.3 ms" * } @@ -201,8 +205,12 @@ private long elapsedNanos() { * Returns the current elapsed time shown on this stopwatch, expressed in the desired time unit, * with any fraction rounded down. * - *

Note that the overhead of measurement can be more than a microsecond, so it is generally not - * useful to specify {@link TimeUnit#NANOSECONDS} precision here. + *

Note: the overhead of measurement can be more than a microsecond, so it is generally + * not useful to specify {@link TimeUnit#NANOSECONDS} precision here. + * + *

It is generally not a good idea to use an ambiguous, unitless {@code long} to represent + * elapsed time. Therefore, we recommend using {@link #elapsed()} instead, which returns a + * strongly-typed {@code Duration} instance. * * @since 14.0 (since 10.0 as {@code elapsedTime()}) */ @@ -210,6 +218,27 @@ public long elapsed(TimeUnit desiredUnit) { return desiredUnit.convert(elapsedNanos(), NANOSECONDS); } + /** + * Returns the current elapsed time shown on this stopwatch as a {@link Duration}. Unlike {@link + * #elapsed(TimeUnit)}, this method does not lose any precision due to rounding. + * + *

Warning: do not call this method from Android code unless you are on Android API + * level 26+ or you opt in to + * library desugaring. + * + * @since NEXT (but since 22.0 in the JRE flavor) + */ + @SuppressWarnings("Java7ApiChecker") + // If users use this when they shouldn't, we hope that NewApi will catch subsequent Duration calls + @IgnoreJRERequirement + @J2ktIncompatible + @GwtIncompatible + @J2ObjCIncompatible + public Duration elapsed() { + return Duration.ofNanos(elapsedNanos()); + } + /** Returns a string representation of the current elapsed time. */ @Override public String toString() { diff --git a/android/guava/src/com/google/common/collect/CollectSpliterators.java b/android/guava/src/com/google/common/collect/CollectSpliterators.java new file mode 100644 index 000000000000..31d59012d12a --- /dev/null +++ b/android/guava/src/com/google/common/collect/CollectSpliterators.java @@ -0,0 +1,568 @@ +/* + * Copyright (C) 2015 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.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.NullnessCasts.uncheckedCastNullableTToT; +import static java.lang.Math.max; + +import com.google.common.annotations.GwtCompatible; +import com.google.j2objc.annotations.Weak; +import java.util.Comparator; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.function.DoubleConsumer; +import java.util.function.Function; +import java.util.function.IntConsumer; +import java.util.function.IntFunction; +import java.util.function.LongConsumer; +import java.util.function.Predicate; +import java.util.stream.IntStream; +import javax.annotation.CheckForNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** Spliterator utilities for {@code common.collect} internals. */ +@GwtCompatible +@ElementTypesAreNonnullByDefault +@SuppressWarnings("Java7ApiChecker") +@IgnoreJRERequirement // used only from APIs that work with Stream +final class CollectSpliterators { + private CollectSpliterators() {} + + static Spliterator indexed( + int size, int extraCharacteristics, IntFunction function) { + return indexed(size, extraCharacteristics, function, null); + } + + static Spliterator indexed( + int size, + int extraCharacteristics, + IntFunction function, + @CheckForNull Comparator comparator) { + if (comparator != null) { + checkArgument((extraCharacteristics & Spliterator.SORTED) != 0); + } + /* + * @IgnoreJRERequirement should be redundant with the one on Streams itself, but it's necessary + * as of Animal Sniffer 1.24. Maybe Animal Sniffer processes this nested class before it + * processes Streams and thus hasn't had a chance to see Streams's annotation? + */ + @IgnoreJRERequirement + class WithCharacteristics implements Spliterator { + private final Spliterator.OfInt delegate; + + WithCharacteristics(Spliterator.OfInt delegate) { + this.delegate = delegate; + } + + @Override + public boolean tryAdvance(Consumer action) { + return delegate.tryAdvance((IntConsumer) i -> action.accept(function.apply(i))); + } + + @Override + public void forEachRemaining(Consumer action) { + delegate.forEachRemaining((IntConsumer) i -> action.accept(function.apply(i))); + } + + @Override + @CheckForNull + public Spliterator trySplit() { + Spliterator.OfInt split = delegate.trySplit(); + return (split == null) ? null : new WithCharacteristics(split); + } + + @Override + public long estimateSize() { + return delegate.estimateSize(); + } + + @Override + public int characteristics() { + return Spliterator.ORDERED + | Spliterator.SIZED + | Spliterator.SUBSIZED + | extraCharacteristics; + } + + @Override + @CheckForNull + public Comparator getComparator() { + if (hasCharacteristics(Spliterator.SORTED)) { + return comparator; + } else { + throw new IllegalStateException(); + } + } + } + return new WithCharacteristics(IntStream.range(0, size).spliterator()); + } + + /** + * Returns a {@code Spliterator} over the elements of {@code fromSpliterator} mapped by {@code + * function}. + */ + static + Spliterator map( + Spliterator fromSpliterator, + Function function) { + checkNotNull(fromSpliterator); + checkNotNull(function); + return new Spliterator() { + + @Override + public boolean tryAdvance(Consumer action) { + return fromSpliterator.tryAdvance( + fromElement -> action.accept(function.apply(fromElement))); + } + + @Override + public void forEachRemaining(Consumer action) { + fromSpliterator.forEachRemaining(fromElement -> action.accept(function.apply(fromElement))); + } + + @Override + @CheckForNull + public Spliterator trySplit() { + Spliterator fromSplit = fromSpliterator.trySplit(); + return (fromSplit != null) ? map(fromSplit, function) : null; + } + + @Override + public long estimateSize() { + return fromSpliterator.estimateSize(); + } + + @Override + public int characteristics() { + return fromSpliterator.characteristics() + & ~(Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.SORTED); + } + }; + } + + /** Returns a {@code Spliterator} filtered by the specified predicate. */ + static Spliterator filter( + Spliterator fromSpliterator, Predicate predicate) { + checkNotNull(fromSpliterator); + checkNotNull(predicate); + @IgnoreJRERequirement // see earlier comment about redundancy + class Splitr implements Spliterator, Consumer { + @CheckForNull T holder = null; + + @Override + public void accept(@ParametricNullness T t) { + this.holder = t; + } + + @Override + public boolean tryAdvance(Consumer action) { + while (fromSpliterator.tryAdvance(this)) { + try { + // The cast is safe because tryAdvance puts a T into `holder`. + T next = uncheckedCastNullableTToT(holder); + if (predicate.test(next)) { + action.accept(next); + return true; + } + } finally { + holder = null; + } + } + return false; + } + + @Override + @CheckForNull + public Spliterator trySplit() { + Spliterator fromSplit = fromSpliterator.trySplit(); + return (fromSplit == null) ? null : filter(fromSplit, predicate); + } + + @Override + public long estimateSize() { + return fromSpliterator.estimateSize() / 2; + } + + @Override + @CheckForNull + public Comparator getComparator() { + return fromSpliterator.getComparator(); + } + + @Override + public int characteristics() { + return fromSpliterator.characteristics() + & (Spliterator.DISTINCT + | Spliterator.NONNULL + | Spliterator.ORDERED + | Spliterator.SORTED); + } + } + return new Splitr(); + } + + /** + * Returns a {@code Spliterator} that iterates over the elements of the spliterators generated by + * applying {@code function} to the elements of {@code fromSpliterator}. + */ + static + Spliterator flatMap( + Spliterator fromSpliterator, + Function> function, + int topCharacteristics, + long topSize) { + checkArgument( + (topCharacteristics & Spliterator.SUBSIZED) == 0, + "flatMap does not support SUBSIZED characteristic"); + checkArgument( + (topCharacteristics & Spliterator.SORTED) == 0, + "flatMap does not support SORTED characteristic"); + checkNotNull(fromSpliterator); + checkNotNull(function); + return new FlatMapSpliteratorOfObject<>( + null, fromSpliterator, function, topCharacteristics, topSize); + } + + /** + * Returns a {@code Spliterator.OfInt} that iterates over the elements of the spliterators + * generated by applying {@code function} to the elements of {@code fromSpliterator}. (If {@code + * function} returns {@code null} for an input, it is replaced with an empty stream.) + */ + static Spliterator.OfInt flatMapToInt( + Spliterator fromSpliterator, + Function function, + int topCharacteristics, + long topSize) { + checkArgument( + (topCharacteristics & Spliterator.SUBSIZED) == 0, + "flatMap does not support SUBSIZED characteristic"); + checkArgument( + (topCharacteristics & Spliterator.SORTED) == 0, + "flatMap does not support SORTED characteristic"); + checkNotNull(fromSpliterator); + checkNotNull(function); + return new FlatMapSpliteratorOfInt<>( + null, fromSpliterator, function, topCharacteristics, topSize); + } + + /** + * Returns a {@code Spliterator.OfLong} that iterates over the elements of the spliterators + * generated by applying {@code function} to the elements of {@code fromSpliterator}. (If {@code + * function} returns {@code null} for an input, it is replaced with an empty stream.) + */ + static Spliterator.OfLong flatMapToLong( + Spliterator fromSpliterator, + Function function, + int topCharacteristics, + long topSize) { + checkArgument( + (topCharacteristics & Spliterator.SUBSIZED) == 0, + "flatMap does not support SUBSIZED characteristic"); + checkArgument( + (topCharacteristics & Spliterator.SORTED) == 0, + "flatMap does not support SORTED characteristic"); + checkNotNull(fromSpliterator); + checkNotNull(function); + return new FlatMapSpliteratorOfLong<>( + null, fromSpliterator, function, topCharacteristics, topSize); + } + + /** + * Returns a {@code Spliterator.OfDouble} that iterates over the elements of the spliterators + * generated by applying {@code function} to the elements of {@code fromSpliterator}. (If {@code + * function} returns {@code null} for an input, it is replaced with an empty stream.) + */ + static Spliterator.OfDouble flatMapToDouble( + Spliterator fromSpliterator, + Function function, + int topCharacteristics, + long topSize) { + checkArgument( + (topCharacteristics & Spliterator.SUBSIZED) == 0, + "flatMap does not support SUBSIZED characteristic"); + checkArgument( + (topCharacteristics & Spliterator.SORTED) == 0, + "flatMap does not support SORTED characteristic"); + checkNotNull(fromSpliterator); + checkNotNull(function); + return new FlatMapSpliteratorOfDouble<>( + null, fromSpliterator, function, topCharacteristics, topSize); + } + + /** + * Implements the {@link Stream#flatMap} operation on spliterators. + * + * @param the element type of the input spliterator + * @param the element type of the output spliterators + * @param the type of the output spliterators + */ + @IgnoreJRERequirement // see earlier comment about redundancy + abstract static class FlatMapSpliterator< + InElementT extends @Nullable Object, + OutElementT extends @Nullable Object, + OutSpliteratorT extends Spliterator> + implements Spliterator { + /** Factory for constructing {@link FlatMapSpliterator} instances. */ + @IgnoreJRERequirement // should be redundant with the annotations on *both* enclosing classes + interface Factory> { + OutSpliteratorT newFlatMapSpliterator( + @CheckForNull OutSpliteratorT prefix, + Spliterator fromSplit, + Function function, + int splitCharacteristics, + long estSplitSize); + } + + @Weak @CheckForNull OutSpliteratorT prefix; + final Spliterator from; + final Function function; + final Factory factory; + int characteristics; + long estimatedSize; + + FlatMapSpliterator( + @CheckForNull OutSpliteratorT prefix, + Spliterator from, + Function function, + Factory factory, + int characteristics, + long estimatedSize) { + this.prefix = prefix; + this.from = from; + this.function = function; + this.factory = factory; + this.characteristics = characteristics; + this.estimatedSize = estimatedSize; + } + + /* + * The tryAdvance and forEachRemaining in FlatMapSpliteratorOfPrimitive are overloads of these + * methods, not overrides. They are annotated @Override because they implement methods from + * Spliterator.OfPrimitive (and override default implementations from Spliterator.OfPrimitive or + * a subtype like Spliterator.OfInt). + */ + + @Override + public /*non-final for J2KT*/ boolean tryAdvance(Consumer action) { + while (true) { + if (prefix != null && prefix.tryAdvance(action)) { + if (estimatedSize != Long.MAX_VALUE) { + estimatedSize--; + } + return true; + } else { + prefix = null; + } + if (!from.tryAdvance(fromElement -> prefix = function.apply(fromElement))) { + return false; + } + } + } + + @Override + public /*non-final for J2KT*/ void forEachRemaining(Consumer action) { + if (prefix != null) { + prefix.forEachRemaining(action); + prefix = null; + } + from.forEachRemaining( + fromElement -> { + Spliterator elements = function.apply(fromElement); + if (elements != null) { + elements.forEachRemaining(action); + } + }); + estimatedSize = 0; + } + + @Override + @CheckForNull + public final OutSpliteratorT trySplit() { + Spliterator fromSplit = from.trySplit(); + if (fromSplit != null) { + int splitCharacteristics = characteristics & ~Spliterator.SIZED; + long estSplitSize = estimateSize(); + if (estSplitSize < Long.MAX_VALUE) { + estSplitSize /= 2; + this.estimatedSize -= estSplitSize; + this.characteristics = splitCharacteristics; + } + OutSpliteratorT result = + factory.newFlatMapSpliterator( + this.prefix, fromSplit, function, splitCharacteristics, estSplitSize); + this.prefix = null; + return result; + } else if (prefix != null) { + OutSpliteratorT result = prefix; + this.prefix = null; + return result; + } else { + return null; + } + } + + @Override + public final long estimateSize() { + if (prefix != null) { + estimatedSize = max(estimatedSize, prefix.estimateSize()); + } + return max(estimatedSize, 0); + } + + @Override + public final int characteristics() { + return characteristics; + } + } + + /** + * Implementation of {@link Stream#flatMap} with an object spliterator output type. + * + *

To avoid having this type, we could use {@code FlatMapSpliterator} directly. The main + * advantages to having the type are the ability to use its constructor reference below and the + * parallelism with the primitive version. In short, it makes its caller ({@code flatMap}) + * simpler. + * + * @param the element type of the input spliterator + * @param the element type of the output spliterators + */ + @IgnoreJRERequirement // see earlier comment about redundancy + static final class FlatMapSpliteratorOfObject< + InElementT extends @Nullable Object, OutElementT extends @Nullable Object> + extends FlatMapSpliterator> { + FlatMapSpliteratorOfObject( + @CheckForNull Spliterator prefix, + Spliterator from, + Function> function, + int characteristics, + long estimatedSize) { + super( + prefix, from, function, FlatMapSpliteratorOfObject::new, characteristics, estimatedSize); + } + } + + /** + * Implementation of {@link Stream#flatMap} with a primitive spliterator output type. + * + * @param the element type of the input spliterator + * @param the (boxed) element type of the output spliterators + * @param the specialized consumer type for the primitive output type + * @param the primitive spliterator type associated with {@code OutElementT} + */ + @IgnoreJRERequirement // see earlier comment about redundancy + abstract static class FlatMapSpliteratorOfPrimitive< + InElementT extends @Nullable Object, + OutElementT extends @Nullable Object, + OutConsumerT, + OutSpliteratorT extends + Spliterator.OfPrimitive> + extends FlatMapSpliterator + implements Spliterator.OfPrimitive { + + FlatMapSpliteratorOfPrimitive( + @CheckForNull OutSpliteratorT prefix, + Spliterator from, + Function function, + Factory factory, + int characteristics, + long estimatedSize) { + super(prefix, from, function, factory, characteristics, estimatedSize); + } + + @Override + public final boolean tryAdvance(OutConsumerT action) { + while (true) { + if (prefix != null && prefix.tryAdvance(action)) { + if (estimatedSize != Long.MAX_VALUE) { + estimatedSize--; + } + return true; + } else { + prefix = null; + } + if (!from.tryAdvance(fromElement -> prefix = function.apply(fromElement))) { + return false; + } + } + } + + @Override + public final void forEachRemaining(OutConsumerT action) { + if (prefix != null) { + prefix.forEachRemaining(action); + prefix = null; + } + from.forEachRemaining( + fromElement -> { + OutSpliteratorT elements = function.apply(fromElement); + if (elements != null) { + elements.forEachRemaining(action); + } + }); + estimatedSize = 0; + } + } + + /** Implementation of {@link #flatMapToInt}. */ + @IgnoreJRERequirement // see earlier comment about redundancy + static final class FlatMapSpliteratorOfInt + extends FlatMapSpliteratorOfPrimitive + implements Spliterator.OfInt { + FlatMapSpliteratorOfInt( + @CheckForNull Spliterator.OfInt prefix, + Spliterator from, + Function function, + int characteristics, + long estimatedSize) { + super(prefix, from, function, FlatMapSpliteratorOfInt::new, characteristics, estimatedSize); + } + } + + /** Implementation of {@link #flatMapToLong}. */ + @IgnoreJRERequirement // see earlier comment about redundancy + static final class FlatMapSpliteratorOfLong + extends FlatMapSpliteratorOfPrimitive + implements Spliterator.OfLong { + FlatMapSpliteratorOfLong( + @CheckForNull Spliterator.OfLong prefix, + Spliterator from, + Function function, + int characteristics, + long estimatedSize) { + super(prefix, from, function, FlatMapSpliteratorOfLong::new, characteristics, estimatedSize); + } + } + + /** Implementation of {@link #flatMapToDouble}. */ + @IgnoreJRERequirement // see earlier comment about redundancy + static final class FlatMapSpliteratorOfDouble + extends FlatMapSpliteratorOfPrimitive< + InElementT, Double, DoubleConsumer, Spliterator.OfDouble> + implements Spliterator.OfDouble { + FlatMapSpliteratorOfDouble( + @CheckForNull Spliterator.OfDouble prefix, + Spliterator from, + Function function, + int characteristics, + long estimatedSize) { + super( + prefix, from, function, FlatMapSpliteratorOfDouble::new, characteristics, estimatedSize); + } + } +} diff --git a/android/guava/src/com/google/common/collect/Streams.java b/android/guava/src/com/google/common/collect/Streams.java new file mode 100644 index 000000000000..eb41b96f66b2 --- /dev/null +++ b/android/guava/src/com/google/common/collect/Streams.java @@ -0,0 +1,1013 @@ +/* + * Copyright (C) 2015 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 com.google.common.collect.NullnessCasts.uncheckedCastNullableTToT; +import static java.lang.Math.min; +import static java.util.Objects.requireNonNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.math.LongMath; +import com.google.errorprone.annotations.InlineMe; +import com.google.errorprone.annotations.InlineMeValidationDisabled; +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Deque; +import java.util.Iterator; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; +import java.util.PrimitiveIterator; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.Spliterators.AbstractSpliterator; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.DoubleConsumer; +import java.util.function.IntConsumer; +import java.util.function.LongConsumer; +import java.util.stream.BaseStream; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import javax.annotation.CheckForNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Static utility methods related to {@code Stream} instances. + * + * @since NEXT (but since 21.0 in the JRE flavor) + */ +@GwtCompatible +@ElementTypesAreNonnullByDefault +@SuppressWarnings("Java7ApiChecker") +/* + * Users will use most of these methods only if they're already using Stream. For a few other + * methods, like stream(Iterable), we have to rely on users not to call them without library + * desugaring. + */ +@IgnoreJRERequirement +public final class Streams { + /** + * Returns a sequential {@link Stream} of the contents of {@code iterable}, delegating to {@link + * Collection#stream} if possible. + */ + public static Stream stream(Iterable iterable) { + return (iterable instanceof Collection) + ? ((Collection) iterable).stream() + : StreamSupport.stream(iterable.spliterator(), false); + } + + /** + * Returns {@link Collection#stream}. + * + * @deprecated There is no reason to use this; just invoke {@code collection.stream()} directly. + */ + @Deprecated + @InlineMe(replacement = "collection.stream()") + public static Stream stream(Collection collection) { + return collection.stream(); + } + + /** + * Returns a sequential {@link Stream} of the remaining contents of {@code iterator}. Do not use + * {@code iterator} directly after passing it to this method. + */ + public static Stream stream(Iterator iterator) { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false); + } + + /** + * If a value is present in {@code optional}, returns a stream containing only that element, + * otherwise returns an empty stream. + */ + public static Stream stream(com.google.common.base.Optional optional) { + return optional.isPresent() ? Stream.of(optional.get()) : Stream.empty(); + } + + /** + * If a value is present in {@code optional}, returns a stream containing only that element, + * otherwise returns an empty stream. + * + *

Java 9 users: use {@code optional.stream()} instead. + */ + @Beta + @InlineMe(replacement = "optional.stream()") + @InlineMeValidationDisabled("Java 9+ API only") + public static Stream stream(java.util.Optional optional) { + return optional.isPresent() ? Stream.of(optional.get()) : Stream.empty(); + } + + /** + * If a value is present in {@code optional}, returns a stream containing only that element, + * otherwise returns an empty stream. + * + *

Java 9 users: use {@code optional.stream()} instead. + */ + @Beta + @InlineMe(replacement = "optional.stream()") + @InlineMeValidationDisabled("Java 9+ API only") + public static IntStream stream(OptionalInt optional) { + return optional.isPresent() ? IntStream.of(optional.getAsInt()) : IntStream.empty(); + } + + /** + * If a value is present in {@code optional}, returns a stream containing only that element, + * otherwise returns an empty stream. + * + *

Java 9 users: use {@code optional.stream()} instead. + */ + @Beta + @InlineMe(replacement = "optional.stream()") + @InlineMeValidationDisabled("Java 9+ API only") + public static LongStream stream(OptionalLong optional) { + return optional.isPresent() ? LongStream.of(optional.getAsLong()) : LongStream.empty(); + } + + /** + * If a value is present in {@code optional}, returns a stream containing only that element, + * otherwise returns an empty stream. + * + *

Java 9 users: use {@code optional.stream()} instead. + */ + @Beta + @InlineMe(replacement = "optional.stream()") + @InlineMeValidationDisabled("Java 9+ API only") + public static DoubleStream stream(OptionalDouble optional) { + return optional.isPresent() ? DoubleStream.of(optional.getAsDouble()) : DoubleStream.empty(); + } + + @SuppressWarnings("CatchingUnchecked") // sneaky checked exception + private static void closeAll(BaseStream[] toClose) { + // If one of the streams throws an exception, continue closing the others, then throw the + // exception later. If more than one stream throws an exception, the later ones are added to the + // first as suppressed exceptions. We don't catch Error on the grounds that it should be allowed + // to propagate immediately. + Exception exception = null; + for (BaseStream stream : toClose) { + try { + stream.close(); + } catch (Exception e) { // sneaky checked exception + if (exception == null) { + exception = e; + } else { + exception.addSuppressed(e); + } + } + } + if (exception != null) { + // Normally this is a RuntimeException that doesn't need sneakyThrow. + // But theoretically we could see sneaky checked exception + sneakyThrow(exception); + } + } + + /** Throws an undeclared checked exception. */ + private static void sneakyThrow(Throwable t) { + class SneakyThrower { + @SuppressWarnings("unchecked") // not really safe, but that's the point + void throwIt(Throwable t) throws T { + throw (T) t; + } + } + new SneakyThrower().throwIt(t); + } + + /** + * Returns a {@link Stream} containing the elements of the first stream, followed by the elements + * of the second stream, and so on. + * + *

This is equivalent to {@code Stream.of(streams).flatMap(stream -> stream)}, but the returned + * stream may perform better. + * + * @see Stream#concat(Stream, Stream) + */ + @SuppressWarnings("unchecked") // could probably be avoided with a forwarding Spliterator + @SafeVarargs + public static Stream concat(Stream... streams) { + // TODO(lowasser): consider an implementation that can support SUBSIZED + boolean isParallel = false; + int characteristics = Spliterator.ORDERED | Spliterator.SIZED | Spliterator.NONNULL; + long estimatedSize = 0L; + ImmutableList.Builder> splitrsBuilder = + new ImmutableList.Builder<>(streams.length); + for (Stream stream : streams) { + isParallel |= stream.isParallel(); + Spliterator splitr = stream.spliterator(); + splitrsBuilder.add(splitr); + characteristics &= splitr.characteristics(); + estimatedSize = LongMath.saturatedAdd(estimatedSize, splitr.estimateSize()); + } + return StreamSupport.stream( + CollectSpliterators.flatMap( + splitrsBuilder.build().spliterator(), + splitr -> (Spliterator) splitr, + characteristics, + estimatedSize), + isParallel) + .onClose(() -> closeAll(streams)); + } + + /** + * Returns an {@link IntStream} containing the elements of the first stream, followed by the + * elements of the second stream, and so on. + * + *

This is equivalent to {@code Stream.of(streams).flatMapToInt(stream -> stream)}, but the + * returned stream may perform better. + * + * @see IntStream#concat(IntStream, IntStream) + */ + public static IntStream concat(IntStream... streams) { + boolean isParallel = false; + int characteristics = Spliterator.ORDERED | Spliterator.SIZED | Spliterator.NONNULL; + long estimatedSize = 0L; + ImmutableList.Builder splitrsBuilder = + new ImmutableList.Builder<>(streams.length); + for (IntStream stream : streams) { + isParallel |= stream.isParallel(); + Spliterator.OfInt splitr = stream.spliterator(); + splitrsBuilder.add(splitr); + characteristics &= splitr.characteristics(); + estimatedSize = LongMath.saturatedAdd(estimatedSize, splitr.estimateSize()); + } + return StreamSupport.intStream( + CollectSpliterators.flatMapToInt( + splitrsBuilder.build().spliterator(), + splitr -> splitr, + characteristics, + estimatedSize), + isParallel) + .onClose(() -> closeAll(streams)); + } + + /** + * Returns a {@link LongStream} containing the elements of the first stream, followed by the + * elements of the second stream, and so on. + * + *

This is equivalent to {@code Stream.of(streams).flatMapToLong(stream -> stream)}, but the + * returned stream may perform better. + * + * @see LongStream#concat(LongStream, LongStream) + */ + public static LongStream concat(LongStream... streams) { + boolean isParallel = false; + int characteristics = Spliterator.ORDERED | Spliterator.SIZED | Spliterator.NONNULL; + long estimatedSize = 0L; + ImmutableList.Builder splitrsBuilder = + new ImmutableList.Builder<>(streams.length); + for (LongStream stream : streams) { + isParallel |= stream.isParallel(); + Spliterator.OfLong splitr = stream.spliterator(); + splitrsBuilder.add(splitr); + characteristics &= splitr.characteristics(); + estimatedSize = LongMath.saturatedAdd(estimatedSize, splitr.estimateSize()); + } + return StreamSupport.longStream( + CollectSpliterators.flatMapToLong( + splitrsBuilder.build().spliterator(), + splitr -> splitr, + characteristics, + estimatedSize), + isParallel) + .onClose(() -> closeAll(streams)); + } + + /** + * Returns a {@link DoubleStream} containing the elements of the first stream, followed by the + * elements of the second stream, and so on. + * + *

This is equivalent to {@code Stream.of(streams).flatMapToDouble(stream -> stream)}, but the + * returned stream may perform better. + * + * @see DoubleStream#concat(DoubleStream, DoubleStream) + */ + public static DoubleStream concat(DoubleStream... streams) { + boolean isParallel = false; + int characteristics = Spliterator.ORDERED | Spliterator.SIZED | Spliterator.NONNULL; + long estimatedSize = 0L; + ImmutableList.Builder splitrsBuilder = + new ImmutableList.Builder<>(streams.length); + for (DoubleStream stream : streams) { + isParallel |= stream.isParallel(); + Spliterator.OfDouble splitr = stream.spliterator(); + splitrsBuilder.add(splitr); + characteristics &= splitr.characteristics(); + estimatedSize = LongMath.saturatedAdd(estimatedSize, splitr.estimateSize()); + } + return StreamSupport.doubleStream( + CollectSpliterators.flatMapToDouble( + splitrsBuilder.build().spliterator(), + splitr -> splitr, + characteristics, + estimatedSize), + isParallel) + .onClose(() -> closeAll(streams)); + } + + /** + * Returns a stream in which each element is the result of passing the corresponding element of + * each of {@code streamA} and {@code streamB} to {@code function}. + * + *

For example: + * + *

{@code
+   * Streams.zip(
+   *   Stream.of("foo1", "foo2", "foo3"),
+   *   Stream.of("bar1", "bar2"),
+   *   (arg1, arg2) -> arg1 + ":" + arg2)
+   * }
+ * + *

will return {@code Stream.of("foo1:bar1", "foo2:bar2")}. + * + *

The resulting stream will only be as long as the shorter of the two input streams; if one + * stream is longer, its extra elements will be ignored. + * + *

Note that if you are calling {@link Stream#forEach} on the resulting stream, you might want + * to consider using {@link #forEachPair} instead of this method. + * + *

Performance note: The resulting stream is not efficiently splittable. + * This may harm parallel performance. + */ + @Beta + public static + Stream zip( + Stream streamA, Stream streamB, BiFunction function) { + checkNotNull(streamA); + checkNotNull(streamB); + checkNotNull(function); + boolean isParallel = streamA.isParallel() || streamB.isParallel(); // same as Stream.concat + Spliterator splitrA = streamA.spliterator(); + Spliterator splitrB = streamB.spliterator(); + int characteristics = + splitrA.characteristics() + & splitrB.characteristics() + & (Spliterator.SIZED | Spliterator.ORDERED); + Iterator itrA = Spliterators.iterator(splitrA); + Iterator itrB = Spliterators.iterator(splitrB); + return StreamSupport.stream( + new AbstractSpliterator( + min(splitrA.estimateSize(), splitrB.estimateSize()), characteristics) { + @Override + public boolean tryAdvance(Consumer action) { + if (itrA.hasNext() && itrB.hasNext()) { + action.accept(function.apply(itrA.next(), itrB.next())); + return true; + } + return false; + } + }, + isParallel) + .onClose(streamA::close) + .onClose(streamB::close); + } + + /** + * Invokes {@code consumer} once for each pair of corresponding elements in {@code streamA} + * and {@code streamB}. If one stream is longer than the other, the extra elements are silently + * ignored. Elements passed to the consumer are guaranteed to come from the same position in their + * respective source streams. For example: + * + *

{@code
+   * Streams.forEachPair(
+   *   Stream.of("foo1", "foo2", "foo3"),
+   *   Stream.of("bar1", "bar2"),
+   *   (arg1, arg2) -> System.out.println(arg1 + ":" + arg2)
+   * }
+ * + *

will print: + * + *

{@code
+   * foo1:bar1
+   * foo2:bar2
+   * }
+ * + *

Warning: If either supplied stream is a parallel stream, the same correspondence + * between elements will be made, but the order in which those pairs of elements are passed to the + * consumer is not defined. + * + *

Note that many usages of this method can be replaced with simpler calls to {@link #zip}. + * This method behaves equivalently to {@linkplain #zip zipping} the stream elements into + * temporary pair objects and then using {@link Stream#forEach} on that stream. + * + * @since NEXT (but since 22.0 in the JRE flavor) + */ + @Beta + public static void forEachPair( + Stream streamA, Stream streamB, BiConsumer consumer) { + checkNotNull(consumer); + + if (streamA.isParallel() || streamB.isParallel()) { + zip(streamA, streamB, TemporaryPair::new).forEach(pair -> consumer.accept(pair.a, pair.b)); + } else { + Iterator iterA = streamA.iterator(); + Iterator iterB = streamB.iterator(); + while (iterA.hasNext() && iterB.hasNext()) { + consumer.accept(iterA.next(), iterB.next()); + } + } + } + + // Use this carefully - it doesn't implement value semantics + private static class TemporaryPair { + @ParametricNullness final A a; + @ParametricNullness final B b; + + TemporaryPair(@ParametricNullness A a, @ParametricNullness B b) { + this.a = a; + this.b = b; + } + } + + /** + * Returns a stream consisting of the results of applying the given function to the elements of + * {@code stream} and their indices in the stream. For example, + * + *

{@code
+   * mapWithIndex(
+   *     Stream.of("a", "b", "c"),
+   *     (e, index) -> index + ":" + e)
+   * }
+ * + *

would return {@code Stream.of("0:a", "1:b", "2:c")}. + * + *

The resulting stream is efficiently splittable + * if and only if {@code stream} was efficiently splittable and its underlying spliterator + * reported {@link Spliterator#SUBSIZED}. This is generally the case if the underlying stream + * comes from a data structure supporting efficient indexed random access, typically an array or + * list. + * + *

The order of the resulting stream is defined if and only if the order of the original stream + * was defined. + */ + public static Stream mapWithIndex( + Stream stream, FunctionWithIndex function) { + checkNotNull(stream); + checkNotNull(function); + boolean isParallel = stream.isParallel(); + Spliterator fromSpliterator = stream.spliterator(); + + if (!fromSpliterator.hasCharacteristics(Spliterator.SUBSIZED)) { + Iterator fromIterator = Spliterators.iterator(fromSpliterator); + return StreamSupport.stream( + new AbstractSpliterator( + fromSpliterator.estimateSize(), + fromSpliterator.characteristics() & (Spliterator.ORDERED | Spliterator.SIZED)) { + long index = 0; + + @Override + public boolean tryAdvance(Consumer action) { + if (fromIterator.hasNext()) { + action.accept(function.apply(fromIterator.next(), index++)); + return true; + } + return false; + } + }, + isParallel) + .onClose(stream::close); + } + class Splitr extends MapWithIndexSpliterator, R, Splitr> implements Consumer { + @CheckForNull T holder; + + Splitr(Spliterator splitr, long index) { + super(splitr, index); + } + + @Override + public void accept(@ParametricNullness T t) { + this.holder = t; + } + + @Override + public boolean tryAdvance(Consumer action) { + if (fromSpliterator.tryAdvance(this)) { + try { + // The cast is safe because tryAdvance puts a T into `holder`. + action.accept(function.apply(uncheckedCastNullableTToT(holder), index++)); + return true; + } finally { + holder = null; + } + } + return false; + } + + @Override + Splitr createSplit(Spliterator from, long i) { + return new Splitr(from, i); + } + } + return StreamSupport.stream(new Splitr(fromSpliterator, 0), isParallel).onClose(stream::close); + } + + /** + * Returns a stream consisting of the results of applying the given function to the elements of + * {@code stream} and their indexes in the stream. For example, + * + *

{@code
+   * mapWithIndex(
+   *     IntStream.of(10, 11, 12),
+   *     (e, index) -> index + ":" + e)
+   * }
+ * + *

...would return {@code Stream.of("0:10", "1:11", "2:12")}. + * + *

The resulting stream is efficiently splittable + * if and only if {@code stream} was efficiently splittable and its underlying spliterator + * reported {@link Spliterator#SUBSIZED}. This is generally the case if the underlying stream + * comes from a data structure supporting efficient indexed random access, typically an array or + * list. + * + *

The order of the resulting stream is defined if and only if the order of the original stream + * was defined. + */ + @SuppressWarnings("AndroidJdkLibsChecker") // b/229998664 + public static Stream mapWithIndex( + IntStream stream, IntFunctionWithIndex function) { + checkNotNull(stream); + checkNotNull(function); + boolean isParallel = stream.isParallel(); + Spliterator.OfInt fromSpliterator = stream.spliterator(); + + if (!fromSpliterator.hasCharacteristics(Spliterator.SUBSIZED)) { + PrimitiveIterator.OfInt fromIterator = Spliterators.iterator(fromSpliterator); + return StreamSupport.stream( + new AbstractSpliterator( + fromSpliterator.estimateSize(), + fromSpliterator.characteristics() & (Spliterator.ORDERED | Spliterator.SIZED)) { + long index = 0; + + @Override + public boolean tryAdvance(Consumer action) { + if (fromIterator.hasNext()) { + action.accept(function.apply(fromIterator.nextInt(), index++)); + return true; + } + return false; + } + }, + isParallel) + .onClose(stream::close); + } + class Splitr extends MapWithIndexSpliterator + implements IntConsumer, Spliterator { + int holder; + + Splitr(Spliterator.OfInt splitr, long index) { + super(splitr, index); + } + + @Override + public void accept(int t) { + this.holder = t; + } + + @Override + public boolean tryAdvance(Consumer action) { + if (fromSpliterator.tryAdvance(this)) { + action.accept(function.apply(holder, index++)); + return true; + } + return false; + } + + @Override + Splitr createSplit(Spliterator.OfInt from, long i) { + return new Splitr(from, i); + } + } + return StreamSupport.stream(new Splitr(fromSpliterator, 0), isParallel).onClose(stream::close); + } + + /** + * Returns a stream consisting of the results of applying the given function to the elements of + * {@code stream} and their indexes in the stream. For example, + * + *

{@code
+   * mapWithIndex(
+   *     LongStream.of(10, 11, 12),
+   *     (e, index) -> index + ":" + e)
+   * }
+ * + *

...would return {@code Stream.of("0:10", "1:11", "2:12")}. + * + *

The resulting stream is efficiently splittable + * if and only if {@code stream} was efficiently splittable and its underlying spliterator + * reported {@link Spliterator#SUBSIZED}. This is generally the case if the underlying stream + * comes from a data structure supporting efficient indexed random access, typically an array or + * list. + * + *

The order of the resulting stream is defined if and only if the order of the original stream + * was defined. + */ + @SuppressWarnings("AndroidJdkLibsChecker") // b/229998664 + public static Stream mapWithIndex( + LongStream stream, LongFunctionWithIndex function) { + checkNotNull(stream); + checkNotNull(function); + boolean isParallel = stream.isParallel(); + Spliterator.OfLong fromSpliterator = stream.spliterator(); + + if (!fromSpliterator.hasCharacteristics(Spliterator.SUBSIZED)) { + PrimitiveIterator.OfLong fromIterator = Spliterators.iterator(fromSpliterator); + return StreamSupport.stream( + new AbstractSpliterator( + fromSpliterator.estimateSize(), + fromSpliterator.characteristics() & (Spliterator.ORDERED | Spliterator.SIZED)) { + long index = 0; + + @Override + public boolean tryAdvance(Consumer action) { + if (fromIterator.hasNext()) { + action.accept(function.apply(fromIterator.nextLong(), index++)); + return true; + } + return false; + } + }, + isParallel) + .onClose(stream::close); + } + class Splitr extends MapWithIndexSpliterator + implements LongConsumer, Spliterator { + long holder; + + Splitr(Spliterator.OfLong splitr, long index) { + super(splitr, index); + } + + @Override + public void accept(long t) { + this.holder = t; + } + + @Override + public boolean tryAdvance(Consumer action) { + if (fromSpliterator.tryAdvance(this)) { + action.accept(function.apply(holder, index++)); + return true; + } + return false; + } + + @Override + Splitr createSplit(Spliterator.OfLong from, long i) { + return new Splitr(from, i); + } + } + return StreamSupport.stream(new Splitr(fromSpliterator, 0), isParallel).onClose(stream::close); + } + + /** + * Returns a stream consisting of the results of applying the given function to the elements of + * {@code stream} and their indexes in the stream. For example, + * + *

{@code
+   * mapWithIndex(
+   *     DoubleStream.of(0.0, 1.0, 2.0)
+   *     (e, index) -> index + ":" + e)
+   * }
+ * + *

...would return {@code Stream.of("0:0.0", "1:1.0", "2:2.0")}. + * + *

The resulting stream is efficiently splittable + * if and only if {@code stream} was efficiently splittable and its underlying spliterator + * reported {@link Spliterator#SUBSIZED}. This is generally the case if the underlying stream + * comes from a data structure supporting efficient indexed random access, typically an array or + * list. + * + *

The order of the resulting stream is defined if and only if the order of the original stream + * was defined. + */ + @SuppressWarnings("AndroidJdkLibsChecker") // b/229998664 + public static Stream mapWithIndex( + DoubleStream stream, DoubleFunctionWithIndex function) { + checkNotNull(stream); + checkNotNull(function); + boolean isParallel = stream.isParallel(); + Spliterator.OfDouble fromSpliterator = stream.spliterator(); + + if (!fromSpliterator.hasCharacteristics(Spliterator.SUBSIZED)) { + PrimitiveIterator.OfDouble fromIterator = Spliterators.iterator(fromSpliterator); + return StreamSupport.stream( + new AbstractSpliterator( + fromSpliterator.estimateSize(), + fromSpliterator.characteristics() & (Spliterator.ORDERED | Spliterator.SIZED)) { + long index = 0; + + @Override + public boolean tryAdvance(Consumer action) { + if (fromIterator.hasNext()) { + action.accept(function.apply(fromIterator.nextDouble(), index++)); + return true; + } + return false; + } + }, + isParallel) + .onClose(stream::close); + } + class Splitr extends MapWithIndexSpliterator + implements DoubleConsumer, Spliterator { + double holder; + + Splitr(Spliterator.OfDouble splitr, long index) { + super(splitr, index); + } + + @Override + public void accept(double t) { + this.holder = t; + } + + @Override + public boolean tryAdvance(Consumer action) { + if (fromSpliterator.tryAdvance(this)) { + action.accept(function.apply(holder, index++)); + return true; + } + return false; + } + + @Override + Splitr createSplit(Spliterator.OfDouble from, long i) { + return new Splitr(from, i); + } + } + return StreamSupport.stream(new Splitr(fromSpliterator, 0), isParallel).onClose(stream::close); + } + + /** + * An analogue of {@link java.util.function.Function} also accepting an index. + * + *

This interface is only intended for use by callers of {@link #mapWithIndex(Stream, + * FunctionWithIndex)}. + * + * @since NEXT (but since 21.0 in the JRE flavor) + */ + public interface FunctionWithIndex { + /** Applies this function to the given argument and its index within a stream. */ + @ParametricNullness + R apply(@ParametricNullness T from, long index); + } + + /* + * @IgnoreJRERequirement should be redundant with the one on Streams itself, but it's necessary as + * of Animal Sniffer 1.24. Maybe Animal Sniffer processes this nested class before it processes + * Streams and thus hasn't had a chance to see Streams's annotation? + */ + @IgnoreJRERequirement + private abstract static class MapWithIndexSpliterator< + F extends Spliterator, + R extends @Nullable Object, + S extends MapWithIndexSpliterator> + implements Spliterator { + final F fromSpliterator; + long index; + + MapWithIndexSpliterator(F fromSpliterator, long index) { + this.fromSpliterator = fromSpliterator; + this.index = index; + } + + abstract S createSplit(F from, long i); + + @Override + @CheckForNull + public S trySplit() { + Spliterator splitOrNull = fromSpliterator.trySplit(); + if (splitOrNull == null) { + return null; + } + @SuppressWarnings("unchecked") + F split = (F) splitOrNull; + S result = createSplit(split, index); + this.index += split.getExactSizeIfKnown(); + return result; + } + + @Override + public long estimateSize() { + return fromSpliterator.estimateSize(); + } + + @Override + public int characteristics() { + return fromSpliterator.characteristics() + & (Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED); + } + } + + /** + * An analogue of {@link java.util.function.IntFunction} also accepting an index. + * + *

This interface is only intended for use by callers of {@link #mapWithIndex(IntStream, + * IntFunctionWithIndex)}. + * + * @since NEXT (but since 21.0 in the JRE flavor) + */ + public interface IntFunctionWithIndex { + /** Applies this function to the given argument and its index within a stream. */ + @ParametricNullness + R apply(int from, long index); + } + + /** + * An analogue of {@link java.util.function.LongFunction} also accepting an index. + * + *

This interface is only intended for use by callers of {@link #mapWithIndex(LongStream, + * LongFunctionWithIndex)}. + * + * @since NEXT (but since 21.0 in the JRE flavor) + */ + public interface LongFunctionWithIndex { + /** Applies this function to the given argument and its index within a stream. */ + @ParametricNullness + R apply(long from, long index); + } + + /** + * An analogue of {@link java.util.function.DoubleFunction} also accepting an index. + * + *

This interface is only intended for use by callers of {@link #mapWithIndex(DoubleStream, + * DoubleFunctionWithIndex)}. + * + * @since NEXT (but since 21.0 in the JRE flavor) + */ + public interface DoubleFunctionWithIndex { + /** Applies this function to the given argument and its index within a stream. */ + @ParametricNullness + R apply(double from, long index); + } + + /** + * Returns the last element of the specified stream, or {@link java.util.Optional#empty} if the + * stream is empty. + * + *

Equivalent to {@code stream.reduce((a, b) -> b)}, but may perform significantly better. This + * method's runtime will be between O(log n) and O(n), performing better on efficiently splittable + * streams. + * + *

If the stream has nondeterministic order, this has equivalent semantics to {@link + * Stream#findAny} (which you might as well use). + * + * @see Stream#findFirst() + * @throws NullPointerException if the last element of the stream is null + */ + /* + * By declaring instead of , we declare this method as requiring a + * stream whose elements are non-null. However, the method goes out of its way to still handle + * nulls in the stream. This means that the method can safely be used with a stream that contains + * nulls as long as the *last* element is *not* null. + * + * (To "go out of its way," the method tracks a `set` bit so that it can distinguish "the final + * split has a last element of null, so throw NPE" from "the final split was empty, so look for an + * element in the prior one.") + */ + public static java.util.Optional findLast(Stream stream) { + class OptionalState { + boolean set = false; + @CheckForNull T value = null; + + void set(T value) { + this.set = true; + this.value = value; + } + + T get() { + /* + * requireNonNull is safe because we call get() only if we've previously called set(). + * + * (For further discussion of nullness, see the comment above the method.) + */ + return requireNonNull(value); + } + } + OptionalState state = new OptionalState(); + + Deque> splits = new ArrayDeque<>(); + splits.addLast(stream.spliterator()); + + while (!splits.isEmpty()) { + Spliterator spliterator = splits.removeLast(); + + if (spliterator.getExactSizeIfKnown() == 0) { + continue; // drop this split + } + + // Many spliterators will have trySplits that are SUBSIZED even if they are not themselves + // SUBSIZED. + if (spliterator.hasCharacteristics(Spliterator.SUBSIZED)) { + // we can drill down to exactly the smallest nonempty spliterator + while (true) { + Spliterator prefix = spliterator.trySplit(); + if (prefix == null || prefix.getExactSizeIfKnown() == 0) { + break; + } else if (spliterator.getExactSizeIfKnown() == 0) { + spliterator = prefix; + break; + } + } + + // spliterator is known to be nonempty now + spliterator.forEachRemaining(state::set); + return java.util.Optional.of(state.get()); + } + + Spliterator prefix = spliterator.trySplit(); + if (prefix == null || prefix.getExactSizeIfKnown() == 0) { + // we can't split this any further + spliterator.forEachRemaining(state::set); + if (state.set) { + return java.util.Optional.of(state.get()); + } + // fall back to the last split + continue; + } + splits.addLast(prefix); + splits.addLast(spliterator); + } + return java.util.Optional.empty(); + } + + /** + * Returns the last element of the specified stream, or {@link OptionalInt#empty} if the stream is + * empty. + * + *

Equivalent to {@code stream.reduce((a, b) -> b)}, but may perform significantly better. This + * method's runtime will be between O(log n) and O(n), performing better on efficiently splittable + * streams. + * + * @see IntStream#findFirst() + * @throws NullPointerException if the last element of the stream is null + */ + public static OptionalInt findLast(IntStream stream) { + // findLast(Stream) does some allocation, so we might as well box some more + java.util.Optional boxedLast = findLast(stream.boxed()); + return boxedLast.map(OptionalInt::of).orElse(OptionalInt.empty()); + } + + /** + * Returns the last element of the specified stream, or {@link OptionalLong#empty} if the stream + * is empty. + * + *

Equivalent to {@code stream.reduce((a, b) -> b)}, but may perform significantly better. This + * method's runtime will be between O(log n) and O(n), performing better on efficiently splittable + * streams. + * + * @see LongStream#findFirst() + * @throws NullPointerException if the last element of the stream is null + */ + public static OptionalLong findLast(LongStream stream) { + // findLast(Stream) does some allocation, so we might as well box some more + java.util.Optional boxedLast = findLast(stream.boxed()); + return boxedLast.map(OptionalLong::of).orElse(OptionalLong.empty()); + } + + /** + * Returns the last element of the specified stream, or {@link OptionalDouble#empty} if the stream + * is empty. + * + *

Equivalent to {@code stream.reduce((a, b) -> b)}, but may perform significantly better. This + * method's runtime will be between O(log n) and O(n), performing better on efficiently splittable + * streams. + * + * @see DoubleStream#findFirst() + * @throws NullPointerException if the last element of the stream is null + */ + public static OptionalDouble findLast(DoubleStream stream) { + // findLast(Stream) does some allocation, so we might as well box some more + java.util.Optional boxedLast = findLast(stream.boxed()); + return boxedLast.map(OptionalDouble::of).orElse(OptionalDouble.empty()); + } + + private Streams() {} +} diff --git a/android/guava/src/com/google/common/io/CharSink.java b/android/guava/src/com/google/common/io/CharSink.java index 14f350e833c8..045fa16bcdce 100644 --- a/android/guava/src/com/google/common/io/CharSink.java +++ b/android/guava/src/com/google/common/io/CharSink.java @@ -15,6 +15,7 @@ package com.google.common.io; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.StandardSystemProperty.LINE_SEPARATOR; import com.google.common.annotations.GwtIncompatible; import com.google.common.annotations.J2ktIncompatible; @@ -24,6 +25,8 @@ import java.io.Reader; import java.io.Writer; import java.nio.charset.Charset; +import java.util.Iterator; +import java.util.stream.Stream; /** * A destination to which characters can be written, such as a text file. Unlike a {@link Writer}, a @@ -116,20 +119,45 @@ public void writeLines(Iterable lines) throws IOExceptio */ public void writeLines(Iterable lines, String lineSeparator) throws IOException { - checkNotNull(lines); + writeLines(lines.iterator(), lineSeparator); + } + + /** + * Writes the given lines of text to this sink with each line (including the last) terminated with + * the operating system's default line separator. This method is equivalent to {@code + * writeLines(lines, System.getProperty("line.separator"))}. + * + * @throws IOException if an I/O error occurs while writing to this sink + * @since NEXT (but since 22.0 in the JRE flavor) + */ + @SuppressWarnings("Java7ApiChecker") + @IgnoreJRERequirement // Users will use this only if they're already using Stream. + public void writeLines(Stream lines) throws IOException { + writeLines(lines, LINE_SEPARATOR.value()); + } + + /** + * Writes the given lines of text to this sink with each line (including the last) terminated with + * the given line separator. + * + * @throws IOException if an I/O error occurs while writing to this sink + * @since NEXT (but since 22.0 in the JRE flavor) + */ + @SuppressWarnings("Java7ApiChecker") + @IgnoreJRERequirement // Users will use this only if they're already using Stream. + public void writeLines(Stream lines, String lineSeparator) + throws IOException { + writeLines(lines.iterator(), lineSeparator); + } + + private void writeLines(Iterator lines, String lineSeparator) + throws IOException { checkNotNull(lineSeparator); - Closer closer = Closer.create(); - try { - Writer out = closer.register(openBufferedStream()); - for (CharSequence line : lines) { - out.append(line).append(lineSeparator); + try (Writer out = openBufferedStream()) { + while (lines.hasNext()) { + out.append(lines.next()).append(lineSeparator); } - out.flush(); // https://github.com/google/guava/issues/1330 - } catch (Throwable e) { - throw closer.rethrow(e); - } finally { - closer.close(); } } @@ -145,16 +173,8 @@ public void writeLines(Iterable lines, String lineSepara public long writeFrom(Readable readable) throws IOException { checkNotNull(readable); - Closer closer = Closer.create(); - try { - Writer out = closer.register(openStream()); - long written = CharStreams.copy(readable, out); - out.flush(); // https://github.com/google/guava/issues/1330 - return written; - } catch (Throwable e) { - throw closer.rethrow(e); - } finally { - closer.close(); + try (Writer out = openStream()) { + return CharStreams.copy(readable, out); } } } diff --git a/android/guava/src/com/google/common/io/CharSource.java b/android/guava/src/com/google/common/io/CharSource.java index f7a4abe878bd..0256312c16d4 100644 --- a/android/guava/src/com/google/common/io/CharSource.java +++ b/android/guava/src/com/google/common/io/CharSource.java @@ -15,6 +15,7 @@ package com.google.common.io; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Streams.stream; import com.google.common.annotations.GwtIncompatible; import com.google.common.annotations.J2ktIncompatible; @@ -25,15 +26,20 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.MustBeClosed; import java.io.BufferedReader; +import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.StringReader; +import java.io.UncheckedIOException; import java.io.Writer; import java.nio.charset.Charset; import java.util.Iterator; import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Stream; import javax.annotation.CheckForNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -124,6 +130,55 @@ public BufferedReader openBufferedStream() throws IOException { : new BufferedReader(reader); } + /** + * Opens a new {@link Stream} for reading text one line at a time from this source. This method + * returns a new, independent stream each time it is called. + * + *

The returned stream is lazy and only reads from the source in the terminal operation. If an + * I/O error occurs while the stream is reading from the source or when the stream is closed, an + * {@link UncheckedIOException} is thrown. + * + *

Like {@link BufferedReader#readLine()}, this method considers a line to be a sequence of + * text that is terminated by (but does not include) one of {@code \r\n}, {@code \r} or {@code + * \n}. If the source's content does not end in a line termination sequence, it is treated as if + * it does. + * + *

The caller is responsible for ensuring that the returned stream is closed. For example: + * + *

{@code
+   * try (Stream lines = source.lines()) {
+   *   lines.map(...)
+   *      .filter(...)
+   *      .forEach(...);
+   * }
+   * }
+ * + * @throws IOException if an I/O error occurs while opening the stream + * @since NEXT (but since 22.0 in the JRE flavor) + */ + @MustBeClosed + @SuppressWarnings("Java7ApiChecker") + // If users use this when they shouldn't, we hope that NewApi will catch subsequent Stream calls. + @IgnoreJRERequirement + public Stream lines() throws IOException { + BufferedReader reader = openBufferedStream(); + return reader.lines().onClose(() -> closeUnchecked(reader)); + } + + @SuppressWarnings("Java7ApiChecker") + @IgnoreJRERequirement // helper for lines() + /* + * If we make these calls inline inside the lambda inside lines(), we get an Animal Sniffer error, + * despite the @IgnoreJRERequirement annotation there. For details, see ImmutableSortedMultiset. + */ + private static void closeUnchecked(Closeable closeable) { + try { + closeable.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + /** * Returns the size of this source in chars, if the size can be easily determined without actually * opening the data stream. @@ -331,6 +386,34 @@ public ImmutableList readLines() throws IOException { } } + /** + * Reads all lines of text from this source, running the given {@code action} for each line as it + * is read. + * + *

Like {@link BufferedReader#readLine()}, this method considers a line to be a sequence of + * text that is terminated by (but does not include) one of {@code \r\n}, {@code \r} or {@code + * \n}. If the source's content does not end in a line termination sequence, it is treated as if + * it does. + * + * @throws IOException if an I/O error occurs while reading from this source or if {@code action} + * throws an {@code UncheckedIOException} + * @since NEXT (but since 22.0 in the JRE flavor) + */ + @SuppressWarnings("Java7ApiChecker") + /* + * We have to rely on users not to call this without library desugaring, as NewApi won't flag + * Consumer creation. + */ + @IgnoreJRERequirement + public void forEachLine(Consumer action) throws IOException { + try (Stream lines = lines()) { + // The lines should be ordered regardless in most cases, but use forEachOrdered to be sure + lines.forEachOrdered(action); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + } + /** * Returns whether the source has zero chars. The default implementation first checks {@link * #lengthIfKnown}, returning true if it's known to be zero and false if it's known to be @@ -520,6 +603,14 @@ protected String computeNext() { }; } + @Override + @SuppressWarnings("Java7ApiChecker") + // If users use this when they shouldn't, we hope that NewApi will catch subsequent Stream calls + @IgnoreJRERequirement + public Stream lines() { + return stream(linesIterator()); + } + @Override @CheckForNull public String readFirstLine() { diff --git a/android/pom.xml b/android/pom.xml index f38a21f729dc..19b94ef261e2 100644 --- a/android/pom.xml +++ b/android/pom.xml @@ -280,7 +280,19 @@ - com.google.common.base.IgnoreJRERequirement,com.google.common.cache.IgnoreJRERequirement,com.google.common.collect.IgnoreJRERequirement,com.google.common.hash.IgnoreJRERequirement,com.google.common.io.IgnoreJRERequirement,com.google.common.math.IgnoreJRERequirement,com.google.common.primitives.IgnoreJRERequirement,com.google.common.reflect.IgnoreJRERequirement,com.google.common.testing.IgnoreJRERequirement,com.google.common.util.concurrent.IgnoreJRERequirement + + com.google.common.base.IgnoreJRERequirement + com.google.common.cache.IgnoreJRERequirement + com.google.common.collect.IgnoreJRERequirement + com.google.common.collect.testing.IgnoreJRERequirement + com.google.common.hash.IgnoreJRERequirement + com.google.common.io.IgnoreJRERequirement + com.google.common.math.IgnoreJRERequirement + com.google.common.primitives.IgnoreJRERequirement + com.google.common.reflect.IgnoreJRERequirement + com.google.common.testing.IgnoreJRERequirement + com.google.common.util.concurrent.IgnoreJRERequirement + true com.toasttab.android diff --git a/guava-testlib/src/com/google/common/collect/testing/IgnoreJRERequirement.java b/guava-testlib/src/com/google/common/collect/testing/IgnoreJRERequirement.java new file mode 100644 index 000000000000..7bc6548bcab2 --- /dev/null +++ b/guava-testlib/src/com/google/common/collect/testing/IgnoreJRERequirement.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 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.testing; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Target; + +/** + * Disables Animal Sniffer's checking of compatibility with older versions of Java/Android. + * + *

Each package's copy of this annotation needs to be listed in our {@code pom.xml}. + */ +@Target({METHOD, CONSTRUCTOR, TYPE}) +@ElementTypesAreNonnullByDefault +@interface IgnoreJRERequirement {} diff --git a/guava-testlib/src/com/google/common/collect/testing/SpliteratorTester.java b/guava-testlib/src/com/google/common/collect/testing/SpliteratorTester.java index b7eff0e8576b..131cbf814db5 100644 --- a/guava-testlib/src/com/google/common/collect/testing/SpliteratorTester.java +++ b/guava-testlib/src/com/google/common/collect/testing/SpliteratorTester.java @@ -47,7 +47,7 @@ /** * Tester for {@code Spliterator} implementations. * - * @since 21.0 + * @since 21.0 (but only since 33.4.0 in the Android flavor) */ @GwtCompatible @ElementTypesAreNonnullByDefault @@ -253,7 +253,7 @@ enum SpliteratorDecompositionStrategy { } /** - * @since 28.1 + * @since 28.1 (but only since 33.4.0 in the Android flavor) */ public static SpliteratorTester ofInt(Supplier spliteratorSupplier) { return new SpliteratorTester<>( @@ -263,7 +263,7 @@ public static SpliteratorTester ofInt(Supplier split } /** - * @since 28.1 + * @since 28.1 (but only since 33.4.0 in the Android flavor) */ public static SpliteratorTester ofLong(Supplier spliteratorSupplier) { return new SpliteratorTester<>( @@ -273,7 +273,7 @@ public static SpliteratorTester ofLong(Supplier splite } /** - * @since 28.1 + * @since 28.1 (but only since 33.4.0 in the Android flavor) */ public static SpliteratorTester ofDouble( Supplier spliteratorSupplier) { diff --git a/guava-tests/test/com/google/common/io/CharSinkTest.java b/guava-tests/test/com/google/common/io/CharSinkTest.java index ac07dc2ee5b4..3718c278d58d 100644 --- a/guava-tests/test/com/google/common/io/CharSinkTest.java +++ b/guava-tests/test/com/google/common/io/CharSinkTest.java @@ -16,6 +16,7 @@ package com.google.common.io; +import static com.google.common.base.StandardSystemProperty.LINE_SEPARATOR; import static com.google.common.io.TestOption.CLOSE_THROWS; import static com.google.common.io.TestOption.OPEN_THROWS; import static com.google.common.io.TestOption.READ_THROWS; @@ -92,7 +93,7 @@ public void testWriteLines_withDefaultSeparator() throws IOException { public void testWriteLines_stream() throws IOException { sink.writeLines(ImmutableList.of("foo", "bar", "baz").stream()); - String separator = System.getProperty("line.separator"); + String separator = LINE_SEPARATOR.value(); assertEquals("foo" + separator + "bar" + separator + "baz" + separator, sink.getString()); } diff --git a/guava/src/com/google/common/base/Optional.java b/guava/src/com/google/common/base/Optional.java index c421710787d2..af41e734914a 100644 --- a/guava/src/com/google/common/base/Optional.java +++ b/guava/src/com/google/common/base/Optional.java @@ -123,7 +123,7 @@ public static Optional fromNullable(@CheckForNull T nullableReference) { * Returns the equivalent {@code com.google.common.base.Optional} value to the given {@code * java.util.Optional}, or {@code null} if the argument is null. * - * @since 21.0 + * @since 21.0 (but only since 33.4.0 in the Android flavor) */ @CheckForNull public static Optional fromJavaUtil(@CheckForNull java.util.Optional javaUtilOptional) { @@ -141,8 +141,9 @@ public static Optional fromJavaUtil(@CheckForNull java.util.Optional j * could refer to either the static or instance version of this method. Write out the lambda * expression {@code o -> Optional.toJavaUtil(o)} instead. * - * @since 21.0 + * @since 21.0 (but only since 33.4.0 in the Android flavor) */ + @SuppressWarnings("AmbiguousMethodReference") // We chose the name despite knowing this risk. @CheckForNull public static java.util.Optional toJavaUtil(@CheckForNull Optional googleOptional) { return googleOptional == null ? null : googleOptional.toJavaUtil(); @@ -155,8 +156,9 @@ public static java.util.Optional toJavaUtil(@CheckForNull Optional goo * could refer to either the static or instance version of this method. Write out the lambda * expression {@code o -> o.toJavaUtil()} instead. * - * @since 21.0 + * @since 21.0 (but only since 33.4.0 in the Android flavor) */ + @SuppressWarnings("AmbiguousMethodReference") // We chose the name despite knowing this risk. public java.util.Optional toJavaUtil() { return java.util.Optional.ofNullable(orNull()); } diff --git a/guava/src/com/google/common/base/Stopwatch.java b/guava/src/com/google/common/base/Stopwatch.java index 3b2421bf9889..480f973b6d56 100644 --- a/guava/src/com/google/common/base/Stopwatch.java +++ b/guava/src/com/google/common/base/Stopwatch.java @@ -222,7 +222,7 @@ public long elapsed(TimeUnit desiredUnit) { * Returns the current elapsed time shown on this stopwatch as a {@link Duration}. Unlike {@link * #elapsed(TimeUnit)}, this method does not lose any precision due to rounding. * - * @since 22.0 + * @since 22.0 (but only since 33.4.0 in the Android flavor) */ @J2ktIncompatible @GwtIncompatible diff --git a/guava/src/com/google/common/collect/Streams.java b/guava/src/com/google/common/collect/Streams.java index 5161f0a588d6..4038937ce675 100644 --- a/guava/src/com/google/common/collect/Streams.java +++ b/guava/src/com/google/common/collect/Streams.java @@ -55,7 +55,7 @@ /** * Static utility methods related to {@code Stream} instances. * - * @since 21.0 + * @since 21.0 (but only since 33.4.0 in the Android flavor) */ @GwtCompatible @ElementTypesAreNonnullByDefault @@ -402,7 +402,7 @@ public boolean tryAdvance(Consumer action) { * This method behaves equivalently to {@linkplain #zip zipping} the stream elements into * temporary pair objects and then using {@link Stream#forEach} on that stream. * - * @since 22.0 + * @since 22.0 (but only since 33.4.0 in the Android flavor) */ @Beta public static void forEachPair( @@ -757,7 +757,7 @@ Splitr createSplit(Spliterator.OfDouble from, long i) { *

This interface is only intended for use by callers of {@link #mapWithIndex(Stream, * FunctionWithIndex)}. * - * @since 21.0 + * @since 21.0 (but only since 33.4.0 in the Android flavor) */ public interface FunctionWithIndex { /** Applies this function to the given argument and its index within a stream. */ @@ -812,7 +812,7 @@ public int characteristics() { *

This interface is only intended for use by callers of {@link #mapWithIndex(IntStream, * IntFunctionWithIndex)}. * - * @since 21.0 + * @since 21.0 (but only since 33.4.0 in the Android flavor) */ public interface IntFunctionWithIndex { /** Applies this function to the given argument and its index within a stream. */ @@ -826,7 +826,7 @@ public interface IntFunctionWithIndex { *

This interface is only intended for use by callers of {@link #mapWithIndex(LongStream, * LongFunctionWithIndex)}. * - * @since 21.0 + * @since 21.0 (but only since 33.4.0 in the Android flavor) */ public interface LongFunctionWithIndex { /** Applies this function to the given argument and its index within a stream. */ @@ -840,7 +840,7 @@ public interface LongFunctionWithIndex { *

This interface is only intended for use by callers of {@link #mapWithIndex(DoubleStream, * DoubleFunctionWithIndex)}. * - * @since 21.0 + * @since 21.0 (but only since 33.4.0 in the Android flavor) */ public interface DoubleFunctionWithIndex { /** Applies this function to the given argument and its index within a stream. */ diff --git a/guava/src/com/google/common/io/CharSink.java b/guava/src/com/google/common/io/CharSink.java index 3e0d22a59b50..be1777536789 100644 --- a/guava/src/com/google/common/io/CharSink.java +++ b/guava/src/com/google/common/io/CharSink.java @@ -15,6 +15,7 @@ package com.google.common.io; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.StandardSystemProperty.LINE_SEPARATOR; import com.google.common.annotations.GwtIncompatible; import com.google.common.annotations.J2ktIncompatible; @@ -127,10 +128,10 @@ public void writeLines(Iterable lines, String lineSepara * writeLines(lines, System.getProperty("line.separator"))}. * * @throws IOException if an I/O error occurs while writing to this sink - * @since 22.0 + * @since 22.0 (but only since 33.4.0 in the Android flavor) */ public void writeLines(Stream lines) throws IOException { - writeLines(lines, System.getProperty("line.separator")); + writeLines(lines, LINE_SEPARATOR.value()); } /** @@ -138,7 +139,7 @@ public void writeLines(Stream lines) throws IOException * the given line separator. * * @throws IOException if an I/O error occurs while writing to this sink - * @since 22.0 + * @since 22.0 (but only since 33.4.0 in the Android flavor) */ public void writeLines(Stream lines, String lineSeparator) throws IOException { @@ -168,16 +169,8 @@ private void writeLines(Iterator lines, String lineSepar public long writeFrom(Readable readable) throws IOException { checkNotNull(readable); - Closer closer = Closer.create(); - try { - Writer out = closer.register(openStream()); - long written = CharStreams.copy(readable, out); - out.flush(); // https://github.com/google/guava/issues/1330 - return written; - } catch (Throwable e) { - throw closer.rethrow(e); - } finally { - closer.close(); + try (Writer out = openStream()) { + return CharStreams.copy(readable, out); } } } diff --git a/guava/src/com/google/common/io/CharSource.java b/guava/src/com/google/common/io/CharSource.java index 3c82bac13409..e2a016080ea9 100644 --- a/guava/src/com/google/common/io/CharSource.java +++ b/guava/src/com/google/common/io/CharSource.java @@ -15,6 +15,7 @@ package com.google.common.io; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Streams.stream; import com.google.common.annotations.GwtIncompatible; import com.google.common.annotations.J2ktIncompatible; @@ -24,10 +25,10 @@ import com.google.common.collect.AbstractIterator; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; -import com.google.common.collect.Streams; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.MustBeClosed; import java.io.BufferedReader; +import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.Reader; @@ -153,21 +154,26 @@ public BufferedReader openBufferedStream() throws IOException { * } * * @throws IOException if an I/O error occurs while opening the stream - * @since 22.0 + * @since 22.0 (but only since 33.4.0 in the Android flavor) */ @MustBeClosed public Stream lines() throws IOException { BufferedReader reader = openBufferedStream(); - return reader - .lines() - .onClose( - () -> { - try { - reader.close(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }); + return reader.lines().onClose(() -> closeUnchecked(reader)); + } + + @SuppressWarnings("Java7ApiChecker") + @IgnoreJRERequirement // helper for lines() + /* + * If we make these calls inline inside the lambda inside lines(), we get an Animal Sniffer error, + * despite the @IgnoreJRERequirement annotation there. For details, see ImmutableSortedMultiset. + */ + private static void closeUnchecked(Closeable closeable) { + try { + closeable.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } /** @@ -388,7 +394,7 @@ public ImmutableList readLines() throws IOException { * * @throws IOException if an I/O error occurs while reading from this source or if {@code action} * throws an {@code UncheckedIOException} - * @since 22.0 + * @since 22.0 (but only since 33.4.0 in the Android flavor) */ public void forEachLine(Consumer action) throws IOException { try (Stream lines = lines()) { @@ -590,7 +596,7 @@ protected String computeNext() { @Override public Stream lines() { - return Streams.stream(linesIterator()); + return stream(linesIterator()); } @Override diff --git a/pom.xml b/pom.xml index a6b76710c4a1..bff5de79da7c 100644 --- a/pom.xml +++ b/pom.xml @@ -281,7 +281,19 @@ - com.google.common.base.IgnoreJRERequirement,com.google.common.cache.IgnoreJRERequirement,com.google.common.collect.IgnoreJRERequirement,com.google.common.hash.IgnoreJRERequirement,com.google.common.io.IgnoreJRERequirement,com.google.common.math.IgnoreJRERequirement,com.google.common.primitives.IgnoreJRERequirement,com.google.common.reflect.IgnoreJRERequirement,com.google.common.testing.IgnoreJRERequirement,com.google.common.util.concurrent.IgnoreJRERequirement + + com.google.common.base.IgnoreJRERequirement + com.google.common.cache.IgnoreJRERequirement + com.google.common.collect.IgnoreJRERequirement + com.google.common.collect.testing.IgnoreJRERequirement + com.google.common.hash.IgnoreJRERequirement + com.google.common.io.IgnoreJRERequirement + com.google.common.math.IgnoreJRERequirement + com.google.common.primitives.IgnoreJRERequirement + com.google.common.reflect.IgnoreJRERequirement + com.google.common.testing.IgnoreJRERequirement + com.google.common.util.concurrent.IgnoreJRERequirement + true org.codehaus.mojo.signature