mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-17 16:39:29 +00:00
Let composite/filtered collections accept null elements
Closes gh-36923 Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
This commit is contained in:
committed by
Sébastien Deleuze
parent
7e0818f4e9
commit
9726c7ed5f
@@ -28,10 +28,11 @@ import org.jspecify.annotations.Nullable;
|
||||
* exposed through {@link CompositeMap#values()}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Yanming Zhou
|
||||
* @since 6.2
|
||||
* @param <E> the type of elements maintained by this collection
|
||||
*/
|
||||
class CompositeCollection<E> implements Collection<E> {
|
||||
class CompositeCollection<E extends @Nullable Object> implements Collection<E> {
|
||||
|
||||
private final Collection<E> first;
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ import java.util.LinkedHashSet;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Composite iterator that combines multiple other iterators,
|
||||
* as registered via {@link #add(Iterator)}.
|
||||
@@ -30,10 +32,11 @@ import java.util.Set;
|
||||
*
|
||||
* @author Erwin Vervaet
|
||||
* @author Juergen Hoeller
|
||||
* @author Yanming Zhou
|
||||
* @since 3.0
|
||||
* @param <E> the element type
|
||||
*/
|
||||
public class CompositeIterator<E> implements Iterator<E> {
|
||||
public class CompositeIterator<E extends @Nullable Object> implements Iterator<E> {
|
||||
|
||||
private final Set<Iterator<E>> iterators = new LinkedHashSet<>();
|
||||
|
||||
|
||||
@@ -32,17 +32,18 @@ import org.jspecify.annotations.Nullable;
|
||||
* {@link CollectionUtils#compositeMap(Map, Map, BiFunction, Consumer)}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Yanming Zhou
|
||||
* @since 6.2
|
||||
* @param <K> the type of keys maintained by this map
|
||||
* @param <V> the type of mapped values
|
||||
*/
|
||||
final class CompositeMap<K, V> implements Map<K, V> {
|
||||
final class CompositeMap<K, V extends @Nullable Object> implements Map<K, V> {
|
||||
|
||||
private final Map<K,V> first;
|
||||
private final Map<K, V> first;
|
||||
|
||||
private final Map<K,V> second;
|
||||
private final Map<K, V> second;
|
||||
|
||||
private final @Nullable BiFunction<K,V,V> putFunction;
|
||||
private final @Nullable BiFunction<K, V, V> putFunction;
|
||||
|
||||
private final @Nullable Consumer<Map<K, V>> putAllFunction;
|
||||
|
||||
@@ -53,7 +54,7 @@ final class CompositeMap<K, V> implements Map<K, V> {
|
||||
|
||||
CompositeMap(Map<K, V> first, Map<K, V> second,
|
||||
@Nullable BiFunction<K, V, V> putFunction,
|
||||
@Nullable Consumer<Map<K,V>> putAllFunction) {
|
||||
@Nullable Consumer<Map<K, V>> putAllFunction) {
|
||||
|
||||
Assert.notNull(first, "First must not be null");
|
||||
Assert.notNull(second, "Second must not be null");
|
||||
@@ -106,7 +107,7 @@ final class CompositeMap<K, V> implements Map<K, V> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable V put(K key, V value) {
|
||||
public V put(K key, V value) {
|
||||
if (this.putFunction == null) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@@ -116,7 +117,7 @@ final class CompositeMap<K, V> implements Map<K, V> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable V remove(Object key) {
|
||||
public V remove(Object key) {
|
||||
V firstResult = this.first.remove(key);
|
||||
V secondResult = this.second.remove(key);
|
||||
if (firstResult != null) {
|
||||
|
||||
@@ -25,10 +25,11 @@ import org.jspecify.annotations.Nullable;
|
||||
* {@link CompositeMap#keySet()} and {@link CompositeMap#entrySet()}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Yanming Zhou
|
||||
* @since 6.2
|
||||
* @param <E> the type of elements maintained by this set
|
||||
*/
|
||||
final class CompositeSet<E> extends CompositeCollection<E> implements Set<E> {
|
||||
final class CompositeSet<E extends @Nullable Object> extends CompositeCollection<E> implements Set<E> {
|
||||
|
||||
CompositeSet(Set<E> first, Set<E> second) {
|
||||
super(first, second);
|
||||
|
||||
@@ -21,15 +21,18 @@ import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Collection that filters out values that do not match a predicate.
|
||||
* This type is used by {@link CompositeMap}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Yanming Zhou
|
||||
* @since 6.2
|
||||
* @param <E> the type of elements maintained by this collection
|
||||
*/
|
||||
class FilteredCollection<E> extends AbstractCollection<E> {
|
||||
class FilteredCollection<E extends @Nullable Object> extends AbstractCollection<E> {
|
||||
|
||||
private final Collection<E> delegate;
|
||||
|
||||
|
||||
@@ -28,10 +28,11 @@ import org.jspecify.annotations.Nullable;
|
||||
* <p>This type is used by {@link CompositeMap}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Yanming Zhou
|
||||
* @since 6.2
|
||||
* @param <E> the type of elements returned by this iterator
|
||||
*/
|
||||
final class FilteredIterator<E> implements Iterator<E> {
|
||||
final class FilteredIterator<E extends @Nullable Object> implements Iterator<E> {
|
||||
|
||||
private final Iterator<E> delegate;
|
||||
|
||||
@@ -56,12 +57,11 @@ final class FilteredIterator<E> implements Iterator<E> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public E next() {
|
||||
public @Nullable E next() {
|
||||
if (!this.hasNext && !setNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
this.hasNext = false;
|
||||
Assert.state(this.next != null, "Next should not be null");
|
||||
return this.next;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,11 +28,12 @@ import org.jspecify.annotations.Nullable;
|
||||
* This type is used by {@link CompositeMap}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Yanming Zhou
|
||||
* @since 6.2
|
||||
* @param <K> the type of keys maintained by this map
|
||||
* @param <V> the type of mapped values
|
||||
*/
|
||||
final class FilteredMap<K, V> extends AbstractMap<K, V> {
|
||||
final class FilteredMap<K, V extends @Nullable Object> extends AbstractMap<K, V> {
|
||||
|
||||
private final Map<K, V> delegate;
|
||||
|
||||
|
||||
@@ -26,10 +26,11 @@ import org.jspecify.annotations.Nullable;
|
||||
* This type is used by {@link CompositeMap}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Yanming Zhou
|
||||
* @since 6.2
|
||||
* @param <E> the type of elements maintained by this set
|
||||
*/
|
||||
final class FilteredSet<E> extends FilteredCollection<E> implements Set<E> {
|
||||
final class FilteredSet<E extends @Nullable Object> extends FilteredCollection<E> implements Set<E> {
|
||||
|
||||
public FilteredSet(Set<E> delegate, Predicate<E> filter) {
|
||||
super(delegate, filter);
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -28,6 +29,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
class CompositeCollectionTests {
|
||||
|
||||
@@ -192,4 +194,17 @@ class CompositeCollectionTests {
|
||||
assertThat(first).isEmpty();
|
||||
assertThat(second).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullable() {
|
||||
List<@Nullable String> first = new ArrayList<>();
|
||||
first.add("foo");
|
||||
first.add(null);
|
||||
List<@Nullable String> second = new ArrayList<>();
|
||||
second.add("bar");
|
||||
second.add(null);
|
||||
CompositeCollection<@Nullable String> composite = new CompositeCollection<>(first, second);
|
||||
|
||||
assertThat(composite).containsExactly("foo", null, "bar", null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -33,6 +34,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
*
|
||||
* @author Erwin Vervaet
|
||||
* @author Juergen Hoeller
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
class CompositeIteratorTests {
|
||||
|
||||
@@ -99,4 +101,13 @@ class CompositeIteratorTests {
|
||||
it.add(iterator));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullable() {
|
||||
List<@Nullable String> first = Arrays.asList("1", null);
|
||||
List<@Nullable String> second = Arrays.asList("2", null);
|
||||
CompositeIterator<String> it = new CompositeIterator<>();
|
||||
it.add(first.iterator());
|
||||
it.add(second.iterator());
|
||||
assertThat(it).toIterable().containsExactly("1", null, "2", null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -32,6 +33,7 @@ import static org.assertj.core.api.Assertions.entry;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
class CompositeMapTests {
|
||||
|
||||
@@ -220,6 +222,17 @@ class CompositeMapTests {
|
||||
assertThat(entries).containsExactly(entry("foo", "bar"), entry("baz", "qux"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullable() {
|
||||
Map<String, @Nullable String> first = new HashMap<>();
|
||||
first.put("foo", "bar");
|
||||
Map<String, @Nullable String> second = new HashMap<>();
|
||||
second.put("baz", null);
|
||||
CompositeMap<String, @Nullable String> composite = new CompositeMap<>(first, second);
|
||||
|
||||
assertThat(composite).containsExactly(entry("foo", "bar"), entry("baz", null));
|
||||
}
|
||||
|
||||
@Nested
|
||||
class CollisionTests {
|
||||
|
||||
|
||||
@@ -20,12 +20,14 @@ import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
class CompositeSetTests {
|
||||
|
||||
@@ -44,4 +46,16 @@ class CompositeSetTests {
|
||||
assertThat(composite).isNotEqualTo(Collections.emptySet());
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullable() {
|
||||
Set<@Nullable String> first = new HashSet<>();
|
||||
first.add("foo");
|
||||
first.add(null);
|
||||
Set<@Nullable String> second = new HashSet<>();
|
||||
second.add("bar");
|
||||
first.add(null);
|
||||
CompositeSet<@Nullable String> composite = new CompositeSet<>(first, second);
|
||||
|
||||
assertThat(composite).containsExactlyInAnyOrder("foo", null, "bar");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,14 +17,17 @@
|
||||
package org.springframework.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
class FilteredCollectionTests {
|
||||
|
||||
@@ -74,4 +77,11 @@ class FilteredCollectionTests {
|
||||
assertThat(contained).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullable() {
|
||||
List<@Nullable String> list = Arrays.asList("foo", "bar", null);
|
||||
FilteredCollection<@Nullable String> filtered = new FilteredCollection<>(list, s -> !"bar".equals(s));
|
||||
|
||||
assertThat(filtered).containsExactlyInAnyOrder("foo", null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,14 +16,17 @@
|
||||
|
||||
package org.springframework.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
final class FilteredIteratorTests {
|
||||
|
||||
@@ -35,4 +38,12 @@ final class FilteredIteratorTests {
|
||||
assertThat(filtered).toIterable().containsExactly("foo", "baz");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullable() {
|
||||
List<@Nullable String> list = Arrays.asList("foo", "bar", null);
|
||||
FilteredIterator<@Nullable String> filtered = new FilteredIterator<>(list.iterator(), s -> !"bar".equals(s));
|
||||
|
||||
assertThat(filtered).toIterable().containsExactly("foo", null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static java.util.Map.entry;
|
||||
@@ -27,6 +28,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
class FilteredMapTests {
|
||||
|
||||
@@ -100,4 +102,15 @@ class FilteredMapTests {
|
||||
Set<String> keySet = filtered.keySet();
|
||||
assertThat(keySet).containsExactlyInAnyOrder("foo", "quux");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullable() {
|
||||
Map<String, @Nullable String> map = new HashMap<>();
|
||||
map.put("foo", null);
|
||||
map.put("bar", "bar");
|
||||
map.put("baz", "baz");
|
||||
FilteredMap<String, @Nullable String> filtered = new FilteredMap<>(map, s -> !s.equals("baz"));
|
||||
|
||||
assertThat(filtered).containsEntry("foo", null).containsEntry("bar", "bar").doesNotContainKeys("baz");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,14 +17,17 @@
|
||||
package org.springframework.util;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
class FilteredSetTests {
|
||||
|
||||
@@ -39,4 +42,15 @@ class FilteredSetTests {
|
||||
assertThat(filtered).isNotEqualTo(set);
|
||||
assertThat(filtered).isNotEqualTo(Collections.emptySet());
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullable() {
|
||||
Set<@Nullable String> set = new HashSet<>();
|
||||
set.add("foo");
|
||||
set.add("bar");
|
||||
set.add(null);
|
||||
FilteredSet<@Nullable String> filtered = new FilteredSet<>(set, s -> !"bar".equals(s));
|
||||
|
||||
assertThat(filtered).containsExactlyInAnyOrder("foo", null);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user