Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions android/guava/src/com/google/common/cache/LocalCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
Expand Down Expand Up @@ -4402,6 +4404,13 @@ public boolean isEmpty() {
public void clear() {
LocalCache.this.clear();
}

@Override
@IgnoreJRERequirement // used only from APIs with Java 8 types in them
public Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(
iterator(), Spliterator.CONCURRENT | Spliterator.DISTINCT | Spliterator.NONNULL);
}
}

final class KeySet extends AbstractCacheSet<K> {
Expand Down Expand Up @@ -4447,6 +4456,13 @@ public Iterator<V> iterator() {
public boolean contains(Object o) {
return LocalCache.this.containsValue(o);
}

@Override
@IgnoreJRERequirement // used only from APIs with Java 8 types in them
public Spliterator<V> spliterator() {
return Spliterators.spliteratorUnknownSize(
iterator(), Spliterator.CONCURRENT | Spliterator.NONNULL);
}
}

final class EntrySet extends AbstractCacheSet<Entry<K, V>> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.google.common.collect.testing.testers.ConcurrentMapRemoveTester;
import com.google.common.collect.testing.testers.ConcurrentMapReplaceEntryTester;
import com.google.common.collect.testing.testers.ConcurrentMapReplaceTester;
import com.google.common.collect.testing.testers.ConcurrentMapSpliteratorTester;
import java.util.List;

/**
Expand All @@ -44,7 +45,8 @@ public static <K, V> ConcurrentMapTestSuiteBuilder<K, V> using(TestMapGenerator<
ConcurrentMapPutIfAbsentTester.class,
ConcurrentMapRemoveTester.class,
ConcurrentMapReplaceTester.class,
ConcurrentMapReplaceEntryTester.class);
ConcurrentMapReplaceEntryTester.class,
ConcurrentMapSpliteratorTester.class);

@SuppressWarnings("rawtypes") // class literals
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright (C) 2026 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.testers;

import com.google.common.annotations.GwtCompatible;
import com.google.common.collect.testing.AbstractMapTester;
import java.util.Spliterator;
import java.util.concurrent.ConcurrentMap;
import org.jspecify.annotations.NullMarked;
import org.junit.Ignore;

/**
* A generic JUnit test which tests spliterator characteristics on derived views of a concurrent
* map. Can't be invoked directly; please see {@link
* com.google.common.collect.testing.ConcurrentMapTestSuiteBuilder}.
*/
@GwtCompatible
@Ignore("test runners must not instantiate and run this directly, only via suites we build")
// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests.
@SuppressWarnings("JUnit4ClassUsedInJUnit3")
@NullMarked
public class ConcurrentMapSpliteratorTester<K, V> extends AbstractMapTester<K, V> {
@Override
protected ConcurrentMap<K, V> getMap() {
return (ConcurrentMap<K, V>) super.getMap();
}

public void testKeySetSpliteratorHasConcurrentCharacteristic() {
assertTrue(
"keySet spliterator should report CONCURRENT",
getMap().keySet().spliterator().hasCharacteristics(Spliterator.CONCURRENT));
}

public void testKeySetSpliteratorDoesNotHaveSizedCharacteristic() {
assertFalse(
"keySet spliterator should not report SIZED",
getMap().keySet().spliterator().hasCharacteristics(Spliterator.SIZED));
}

public void testEntrySetSpliteratorHasConcurrentCharacteristic() {
assertTrue(
"entrySet spliterator should report CONCURRENT",
getMap().entrySet().spliterator().hasCharacteristics(Spliterator.CONCURRENT));
}

public void testEntrySetSpliteratorDoesNotHaveSizedCharacteristic() {
assertFalse(
"entrySet spliterator should not report SIZED",
getMap().entrySet().spliterator().hasCharacteristics(Spliterator.SIZED));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
* Copyright (C) 2026 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 com.google.common.collect.ForwardingConcurrentMap;
import com.google.common.collect.testing.features.CollectionFeature;
import com.google.common.collect.testing.features.CollectionSize;
import com.google.common.collect.testing.features.MapFeature;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestFailure;
import junit.framework.TestResult;

/** Tests for {@link ConcurrentMapTestSuiteBuilder}. */
@AndroidIncompatible // test-suite builders
public final class ConcurrentMapTestSuiteBuilderTest extends TestCase {
public void testConcurrentHashMapDerivedSetSpliteratorsPass() {
TestResult result = runSuite(new ConcurrentHashMapGenerator(), "ConcurrentHashMap");

assertEquals(0, result.errorCount());
assertEquals(0, result.failureCount());
}

public void testConcurrentMapDerivedSetSpliteratorTestsCatchWrongCharacteristics() {
TestResult result = runSuite(new BadSpliteratorConcurrentMapGenerator(), "BadConcurrentMap");

assertEquals(0, result.errorCount());

List<String> failedTests = failedTests(result);
assertTrue(failedTests.toString(), failedTests.size() >= 4);
assertContainsFailure(
failedTests, "testKeySetSpliteratorHasConcurrentCharacteristic");
assertContainsFailure(
failedTests, "testKeySetSpliteratorDoesNotHaveSizedCharacteristic");
assertContainsFailure(
failedTests, "testEntrySetSpliteratorHasConcurrentCharacteristic");
assertContainsFailure(
failedTests, "testEntrySetSpliteratorDoesNotHaveSizedCharacteristic");
}

private static TestResult runSuite(TestStringMapGenerator generator, String name) {
Test suite =
ConcurrentMapTestSuiteBuilder.using(generator)
.named(name)
.withFeatures(
MapFeature.GENERAL_PURPOSE,
CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
CollectionSize.ONE)
.suppressing(new OpenJdk6MapTests().suppressForConcurrentHashMap())
.createTestSuite();
TestResult result = new TestResult();
suite.run(result);
return result;
}

private static List<String> failedTests(TestResult result) {
List<String> failedTests = new ArrayList<>();
for (java.util.Enumeration<TestFailure> failures = result.failures();
failures.hasMoreElements(); ) {
failedTests.add(failures.nextElement().failedTest().toString());
}
return failedTests;
}

private static void assertContainsFailure(List<String> failedTests, String testName) {
for (String failedTest : failedTests) {
if (failedTest.contains(testName)) {
return;
}
}
fail("Expected failure for " + testName + " in " + failedTests);
}

private static final class ConcurrentHashMapGenerator extends TestStringMapGenerator {
@Override
protected Map<String, String> create(Entry<String, String>[] entries) {
ConcurrentMap<String, String> map = new ConcurrentHashMap<>();
for (Entry<String, String> entry : entries) {
map.put(entry.getKey(), entry.getValue());
}
return map;
}
}

private static final class BadSpliteratorConcurrentMapGenerator extends TestStringMapGenerator {
@Override
protected Map<String, String> create(Entry<String, String>[] entries) {
BadSpliteratorConcurrentMap map = new BadSpliteratorConcurrentMap();
for (Entry<String, String> entry : entries) {
map.put(entry.getKey(), entry.getValue());
}
return map;
}
}

private static final class BadSpliteratorConcurrentMap
extends ForwardingConcurrentMap<String, String> {
private final ConcurrentMap<String, String> delegate = new ConcurrentHashMap<>();

@Override
protected ConcurrentMap<String, String> delegate() {
return delegate;
}

@Override
public Set<String> keySet() {
return new BadSpliteratorSet<>(delegate.keySet());
}

@Override
public Set<Entry<String, String>> entrySet() {
return new BadSpliteratorSet<>(delegate.entrySet());
}
}

private static final class BadSpliteratorSet<E> extends AbstractSet<E> {
private final Set<E> delegate;

BadSpliteratorSet(Set<E> delegate) {
this.delegate = delegate;
}

@Override
public Iterator<E> iterator() {
return delegate.iterator();
}

@Override
public int size() {
return delegate.size();
}

@Override
public boolean contains(Object o) {
return delegate.contains(o);
}

@Override
public boolean remove(Object o) {
return delegate.remove(o);
}

@Override
public void clear() {
delegate.clear();
}

@Override
public Spliterator<E> spliterator() {
return Spliterators.spliterator(iterator(), size(), Spliterator.DISTINCT);
}
}
}
14 changes: 14 additions & 0 deletions guava/src/com/google/common/cache/LocalCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
Expand Down Expand Up @@ -4546,6 +4548,12 @@ public boolean isEmpty() {
public void clear() {
LocalCache.this.clear();
}

@Override
public Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(
iterator(), Spliterator.CONCURRENT | Spliterator.DISTINCT | Spliterator.NONNULL);
}
}

boolean removeIf(BiPredicate<? super K, ? super V> filter) {
Expand Down Expand Up @@ -4614,6 +4622,12 @@ public boolean removeIf(Predicate<? super V> filter) {
public boolean contains(Object o) {
return LocalCache.this.containsValue(o);
}

@Override
public Spliterator<V> spliterator() {
return Spliterators.spliteratorUnknownSize(
iterator(), Spliterator.CONCURRENT | Spliterator.NONNULL);
}
}

final class EntrySet extends AbstractCacheSet<Entry<K, V>> {
Expand Down