diff --git a/guava-tests/test/com/google/common/collect/CollectSpliteratorsTest.java b/guava-tests/test/com/google/common/collect/CollectSpliteratorsTest.java index 7e49123e11bc..40e666d7a7b5 100644 --- a/guava-tests/test/com/google/common/collect/CollectSpliteratorsTest.java +++ b/guava-tests/test/com/google/common/collect/CollectSpliteratorsTest.java @@ -25,6 +25,7 @@ import java.util.Arrays; import java.util.List; import java.util.Spliterator; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.DoubleStream; import java.util.stream.IntStream; import java.util.stream.LongStream; @@ -101,6 +102,32 @@ public void testFlatMapToDouble_nullStream() { .expect(1.0, 1.0, 2.0, 3.0); } + @GwtIncompatible // CopyOnWriteArrayList + public void testFilterPreservesImmutableCharacteristic() { + CopyOnWriteArrayList cow = new CopyOnWriteArrayList<>(Arrays.asList("a", "b", "c")); + Spliterator source = cow.spliterator(); + assertTrue((source.characteristics() & Spliterator.IMMUTABLE) != 0); + + Spliterator filtered = + CollectSpliterators.filter(cow.spliterator(), s -> !s.equals("b")); + assertTrue( + "filter() should preserve IMMUTABLE from source", + (filtered.characteristics() & Spliterator.IMMUTABLE) != 0); + } + + @GwtIncompatible + public void testFilterDropsSized() { + Spliterator source = + Arrays.spliterator(new String[] {"a", "b", "c"}); + assertTrue((source.characteristics() & Spliterator.SIZED) != 0); + + Spliterator filtered = + CollectSpliterators.filter(source, s -> !s.equals("b")); + assertFalse( + "filter() should not preserve SIZED", + (filtered.characteristics() & Spliterator.SIZED) != 0); + } + public void testMultisetsSpliterator() { Multiset multiset = TreeMultiset.create(); multiset.add("a", 3); diff --git a/guava-tests/test/com/google/common/collect/ListsTest.java b/guava-tests/test/com/google/common/collect/ListsTest.java index bd46674a18d2..7686c55cccf9 100644 --- a/guava-tests/test/com/google/common/collect/ListsTest.java +++ b/guava-tests/test/com/google/common/collect/ListsTest.java @@ -57,6 +57,7 @@ import java.util.ListIterator; import java.util.NoSuchElementException; import java.util.RandomAccess; +import java.util.Spliterator; import java.util.concurrent.CopyOnWriteArrayList; import junit.framework.Test; import junit.framework.TestCase; @@ -998,4 +999,68 @@ public void testPartitionSize_1() { public void testPartitionSize_2() { assertEquals(2, partition(nCopies(0x40000001, 1), 0x40000000).size()); } + + @GwtIncompatible // Spliterator + @J2ktIncompatible + public void testAsListSpliteratorIsImmutable() { + List list = Lists.asList("a", new String[] {"b", "c"}); + int characteristics = list.spliterator().characteristics(); + assertTrue("asList should report IMMUTABLE", (characteristics & Spliterator.IMMUTABLE) != 0); + assertTrue("asList should report ORDERED", (characteristics & Spliterator.ORDERED) != 0); + assertTrue("asList should report SIZED", (characteristics & Spliterator.SIZED) != 0); + } + + @GwtIncompatible // Spliterator + @J2ktIncompatible + public void testAsListSpliteratorIteration() { + List list = Lists.asList("a", new String[] {"b", "c"}); + List collected = new ArrayList<>(); + list.spliterator().forEachRemaining(collected::add); + assertEquals(asList("a", "b", "c"), collected); + } + + @GwtIncompatible // Spliterator + @J2ktIncompatible + public void testAsListTwoSpliteratorIsImmutable() { + List list = Lists.asList("a", "b", new String[] {"c"}); + int characteristics = list.spliterator().characteristics(); + assertTrue( + "asList(2+) should report IMMUTABLE", (characteristics & Spliterator.IMMUTABLE) != 0); + } + + @GwtIncompatible // Spliterator + @J2ktIncompatible + public void testAsListTwoSpliteratorIteration() { + List list = Lists.asList("a", "b", new String[] {"c"}); + List collected = new ArrayList<>(); + list.spliterator().forEachRemaining(collected::add); + assertEquals(asList("a", "b", "c"), collected); + } + + @GwtIncompatible // Spliterator + @J2ktIncompatible + public void testCharactersOfSpliteratorCharacteristics() { + List chars = Lists.charactersOf("hello"); + int characteristics = chars.spliterator().characteristics(); + assertTrue("charactersOf should report ORDERED", (characteristics & Spliterator.ORDERED) != 0); + assertTrue("charactersOf should report NONNULL", (characteristics & Spliterator.NONNULL) != 0); + assertTrue("charactersOf should report SIZED", (characteristics & Spliterator.SIZED) != 0); + } + + @GwtIncompatible // Spliterator + @J2ktIncompatible + public void testCharactersOfSpliteratorIteration() { + List chars = Lists.charactersOf("hello"); + List collected = new ArrayList<>(); + chars.spliterator().forEachRemaining(collected::add); + assertEquals(asList('h', 'e', 'l', 'l', 'o'), collected); + } + + @GwtIncompatible // CopyOnWriteArrayList + @J2ktIncompatible + public void testAbstractListWrapperSpliteratorDelegates() { + CopyOnWriteArrayList cow = new CopyOnWriteArrayList<>(asList("a", "b", "c")); + int cowCharacteristics = cow.spliterator().characteristics(); + assertTrue((cowCharacteristics & Spliterator.IMMUTABLE) != 0); + } } diff --git a/guava-tests/test/com/google/common/collect/MapsTest.java b/guava-tests/test/com/google/common/collect/MapsTest.java index 2f857f0dacc6..241581c2e160 100644 --- a/guava-tests/test/com/google/common/collect/MapsTest.java +++ b/guava-tests/test/com/google/common/collect/MapsTest.java @@ -64,6 +64,7 @@ import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; +import java.util.Spliterator; import java.util.TreeMap; import java.util.concurrent.ConcurrentMap; import junit.framework.TestCase; @@ -1624,4 +1625,31 @@ public void testSubMap_unnaturalOrdering() { ImmutableSortedMap.of(2, 0, 4, 0, 6, 0, 8, 0, 10, 0), Maps.subMap(map, Range.all())); } + + @GwtIncompatible // Spliterator + @J2ktIncompatible + public void testKeySetSpliteratorIsDistinct() { + HashMap map = new HashMap<>(); + map.put("a", 1); + map.put("b", 2); + int characteristics = map.keySet().spliterator().characteristics(); + assertTrue( + "keySet spliterator should report DISTINCT", + (characteristics & Spliterator.DISTINCT) != 0); + } + + @GwtIncompatible // Spliterator + @J2ktIncompatible + public void testValuesSpliteratorCharacteristics() { + HashMap map = new HashMap<>(); + map.put("a", 1); + map.put("b", 2); + Spliterator valuesSpliterator = map.values().spliterator(); + assertTrue( + "values spliterator should report SIZED", + (valuesSpliterator.characteristics() & Spliterator.SIZED) != 0); + assertFalse( + "values spliterator should NOT report DISTINCT", + (valuesSpliterator.characteristics() & Spliterator.DISTINCT) != 0); + } } diff --git a/guava/src/com/google/common/collect/CollectSpliterators.java b/guava/src/com/google/common/collect/CollectSpliterators.java index 612f19bcffeb..707623f37d6e 100644 --- a/guava/src/com/google/common/collect/CollectSpliterators.java +++ b/guava/src/com/google/common/collect/CollectSpliterators.java @@ -199,11 +199,15 @@ public long estimateSize() { @Override public int characteristics() { + // IMMUTABLE and CONCURRENT describe the source, not the elements, so filtering + // does not invalidate them. This matches JDK Stream.filter(), which only clears SIZED. return fromSpliterator.characteristics() & (Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.ORDERED - | Spliterator.SORTED); + | Spliterator.SORTED + | Spliterator.IMMUTABLE + | Spliterator.CONCURRENT); } } return new Splitr(); diff --git a/guava/src/com/google/common/collect/Lists.java b/guava/src/com/google/common/collect/Lists.java index 32e032edd1bf..dc167bea1099 100644 --- a/guava/src/com/google/common/collect/Lists.java +++ b/guava/src/com/google/common/collect/Lists.java @@ -50,6 +50,7 @@ import java.util.NoSuchElementException; import java.util.Objects; import java.util.RandomAccess; +import java.util.Spliterator; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Predicate; import org.jspecify.annotations.Nullable; @@ -356,6 +357,14 @@ public E get(int index) { return (index == 0) ? first : rest[index - 1]; } + @Override + @GwtIncompatible + @J2ktIncompatible + public Spliterator spliterator() { + return CollectSpliterators.indexed( + size(), Spliterator.ORDERED | Spliterator.IMMUTABLE | Spliterator.SIZED, this::get); + } + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } @@ -394,6 +403,14 @@ public E get(int index) { } } + @Override + @GwtIncompatible + @J2ktIncompatible + public Spliterator spliterator() { + return CollectSpliterators.indexed( + size(), Spliterator.ORDERED | Spliterator.IMMUTABLE | Spliterator.SIZED, this::get); + } + @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 0; } @@ -829,6 +846,14 @@ public Character get(int index) { public int size() { return sequence.length(); } + + @Override + @GwtIncompatible + @J2ktIncompatible + public Spliterator spliterator() { + return CollectSpliterators.indexed( + size(), Spliterator.ORDERED | Spliterator.NONNULL | Spliterator.SIZED, this::get); + } } /** @@ -1201,6 +1226,13 @@ public boolean contains(@Nullable Object o) { public int size() { return backingList.size(); } + + @Override + @GwtIncompatible + @J2ktIncompatible + public Spliterator spliterator() { + return backingList.spliterator(); + } } private static class RandomAccessListWrapper diff --git a/guava/src/com/google/common/collect/Maps.java b/guava/src/com/google/common/collect/Maps.java index d06ca15f2f53..f5f6ceb66e7d 100644 --- a/guava/src/com/google/common/collect/Maps.java +++ b/guava/src/com/google/common/collect/Maps.java @@ -3942,6 +3942,14 @@ public boolean remove(@Nullable Object o) { public void clear() { map().clear(); } + + @Override + @GwtIncompatible + @J2ktIncompatible + public Spliterator spliterator() { + return CollectSpliterators.map( + map().entrySet().spliterator(), Spliterator.DISTINCT, Entry::getKey); + } } static @Nullable K keyOrNull(@Nullable Entry entry) { @@ -4171,6 +4179,13 @@ public boolean contains(@Nullable Object o) { public void clear() { map().clear(); } + + @Override + @GwtIncompatible + @J2ktIncompatible + public Spliterator spliterator() { + return CollectSpliterators.map(map().entrySet().spliterator(), 0, Entry::getValue); + } } abstract static class EntrySet