Browse Source

Use composite collections in attribute merging

This commit introduces composite collections (i.e. Collection, Set, Map)
and uses these composites in request predicates, where before new
collections were instantiated.

Closes gh-32245
pull/32313/head
Arjen Poutsma 2 years ago
parent
commit
aee03c5201
  1. 40
      spring-core/src/main/java/org/springframework/util/CollectionUtils.java
  2. 163
      spring-core/src/main/java/org/springframework/util/CompositeCollection.java
  3. 189
      spring-core/src/main/java/org/springframework/util/CompositeMap.java
  4. 67
      spring-core/src/main/java/org/springframework/util/CompositeSet.java
  5. 195
      spring-core/src/test/java/org/springframework/util/CompositeCollectionTests.java
  6. 221
      spring-core/src/test/java/org/springframework/util/CompositeMapTests.java
  7. 47
      spring-core/src/test/java/org/springframework/util/CompositeSetTests.java
  8. 43
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java
  9. 41
      spring-webmvc/src/main/java/org/springframework/web/servlet/function/RequestPredicates.java

40
spring-core/src/main/java/org/springframework/util/CollectionUtils.java

@ -31,6 +31,8 @@ import java.util.Map; @@ -31,6 +31,8 @@ import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.SortedSet;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import org.springframework.lang.Nullable;
@ -506,4 +508,42 @@ public abstract class CollectionUtils { @@ -506,4 +508,42 @@ public abstract class CollectionUtils {
return new UnmodifiableMultiValueMap<>(targetMap);
}
/**
* Return a (partially unmodifiable) map that combines the provided two
* maps. Invoking {@link Map#put(Object, Object)} or {@link Map#putAll(Map)}
* on the returned map results in an {@link UnsupportedOperationException}.
* @param first the first map to compose
* @param second the second map to compose
* @return a new map that composes the given two maps
* @since 6.2
*/
public static <K, V> Map<K, V> compositeMap(Map<K,V> first, Map<K,V> second) {
return new CompositeMap<>(first, second);
}
/**
* Return a map that combines the provided maps. Invoking
* {@link Map#put(Object, Object)} on the returned map will apply
* {@code putFunction}, or will throw an
* {@link UnsupportedOperationException} {@code putFunction} is
* {@code null}. The same applies to {@link Map#putAll(Map)} and
* {@code putAllFunction}.
* @param first the first map to compose
* @param second the second map to compose
* @param putFunction applied when {@code Map::put} is invoked. If
* {@code null}, {@code Map::put} throws an
* {@code UnsupportedOperationException}.
* @param putAllFunction applied when {@code Map::putAll} is invoked. If
* {@code null}, {@code Map::putAll} throws an
* {@code UnsupportedOperationException}.
* @return a new map that composes the give maps
* @since 6.2
*/
public static <K, V> Map<K, V> compositeMap(Map<K,V> first, Map<K,V> second,
@Nullable BiFunction<K, V, V> putFunction,
@Nullable Consumer<Map<K, V>> putAllFunction) {
return new CompositeMap<>(first, second, putFunction, putAllFunction);
}
}

163
spring-core/src/main/java/org/springframework/util/CompositeCollection.java

@ -0,0 +1,163 @@ @@ -0,0 +1,163 @@
/*
* Copyright 2002-2024 the original author or 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
*
* https://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 org.springframework.util;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;
/**
* Composite collection that combines two other collections. This type is only
* exposed through {@link CompositeMap#values()}.
*
* @author Arjen Poutsma
* @since 6.2
* @param <E> the type of elements maintained by this collection
*/
class CompositeCollection<E> implements Collection<E> {
private final Collection<E> first;
private final Collection<E> second;
CompositeCollection(Collection<E> first, Collection<E> second) {
Assert.notNull(first, "First must not be null");
Assert.notNull(second, "Second must not be null");
this.first = first;
this.second = second;
}
@Override
public int size() {
return this.first.size() + this.second.size();
}
@Override
public boolean isEmpty() {
return this.first.isEmpty() && this.second.isEmpty();
}
@Override
public boolean contains(Object o) {
if (this.first.contains(o)) {
return true;
}
else {
return this.second.contains(o);
}
}
@Override
public Iterator<E> iterator() {
CompositeIterator<E> iterator = new CompositeIterator<>();
iterator.add(this.first.iterator());
iterator.add(this.second.iterator());
return iterator;
}
@Override
public Object[] toArray() {
Object[] result = new Object[size()];
Object[] firstArray = this.first.toArray();
Object[] secondArray = this.second.toArray();
System.arraycopy(firstArray, 0, result, 0, firstArray.length);
System.arraycopy(secondArray, 0, result, firstArray.length, secondArray.length);
return result;
}
@Override
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
int size = this.size();
T[] result;
if (a.length >= size) {
result = a;
}
else {
result = (T[]) Array.newInstance(a.getClass().getComponentType(), size);
}
int idx = 0;
for (E e : this) {
result[idx++] = (T) e;
}
if (result.length > size) {
result[size] = null;
}
return result;
}
@Override
public boolean add(E e) {
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object o) {
boolean firstResult = this.first.remove(o);
boolean secondResult = this.second.remove(o);
return firstResult || secondResult;
}
@Override
public boolean containsAll(Collection<?> c) {
for (Object o : c) {
if (!contains(o)) {
return false;
}
}
return true;
}
@Override
public boolean addAll(Collection<? extends E> c) {
boolean changed = false;
for (E e : c) {
if (add(e)) {
changed = true;
}
}
return changed;
}
@Override
public boolean removeAll(Collection<?> c) {
if (c.isEmpty()) {
return false;
}
boolean firstResult = this.first.removeAll(c);
boolean secondResult = this.second.removeAll(c);
return firstResult || secondResult;
}
@Override
public boolean retainAll(Collection<?> c) {
boolean firstResult = this.first.retainAll(c);
boolean secondResult = this.second.retainAll(c);
return firstResult || secondResult;
}
@Override
public void clear() {
this.first.clear();
this.second.clear();
}
}

189
spring-core/src/main/java/org/springframework/util/CompositeMap.java

@ -0,0 +1,189 @@ @@ -0,0 +1,189 @@
/*
* Copyright 2002-2024 the original author or 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
*
* https://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 org.springframework.util;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import org.springframework.lang.Nullable;
/**
* Composite map that combines two other maps. This type is created via
* {@link CollectionUtils#compositeMap(Map, Map, BiFunction, Consumer)}.
*
* @author Arjen Poutsma
* @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> {
private final Map<K,V> first;
private final Map<K,V> second;
@Nullable
private final BiFunction<K,V,V> putFunction;
@Nullable
private final Consumer<Map<K, V>> putAllFunction;
CompositeMap(Map<K, V> first, Map<K, V> second) {
this(first, second, null, null);
}
CompositeMap(Map<K, V> first, Map<K, V> second,
@Nullable BiFunction<K, V, V> putFunction,
@Nullable Consumer<Map<K,V>> putAllFunction) {
Assert.notNull(first, "First must not be null");
Assert.notNull(second, "Second must not be null");
this.first = first;
this.second = second;
this.putFunction = putFunction;
this.putAllFunction = putAllFunction;
}
@Override
public int size() {
return this.first.size() + this.second.size();
}
@Override
public boolean isEmpty() {
return this.first.isEmpty() && this.second.isEmpty();
}
@Override
public boolean containsKey(Object key) {
if (this.first.containsKey(key)) {
return true;
}
else {
return this.second.containsKey(key);
}
}
@Override
public boolean containsValue(Object value) {
if (this.first.containsValue(value)) {
return true;
}
else {
return this.second.containsValue(value);
}
}
@Override
@Nullable
public V get(Object key) {
V firstResult = this.first.get(key);
if (firstResult != null) {
return firstResult;
}
else {
return this.second.get(key);
}
}
@Override
@Nullable
public V put(K key, V value) {
if (this.putFunction == null) {
throw new UnsupportedOperationException();
}
else {
return this.putFunction.apply(key, value);
}
}
@Override
@Nullable
public V remove(Object key) {
V firstResult = this.first.remove(key);
V secondResult = this.second.remove(key);
if (firstResult != null) {
return firstResult;
}
else {
return secondResult;
}
}
@Override
@SuppressWarnings("unchecked")
public void putAll(Map<? extends K, ? extends V> m) {
if (this.putAllFunction != null) {
this.putAllFunction.accept((Map<K, V>) m);
}
else {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
put(e.getKey(), e.getValue());
}
}
}
@Override
public void clear() {
this.first.clear();
this.second.clear();
}
@Override
public Set<K> keySet() {
return new CompositeSet<>(this.first.keySet(), this.second.keySet());
}
@Override
public Collection<V> values() {
return new CompositeCollection<>(this.first.values(), this.second.values());
}
@Override
public Set<Entry<K, V>> entrySet() {
return new CompositeSet<>(this.first.entrySet(), this.second.entrySet());
}
@Override
public String toString() {
Iterator<Entry<K, V>> i = entrySet().iterator();
if (!i.hasNext()) {
return "{}";
}
StringBuilder sb = new StringBuilder();
sb.append('{');
while (true) {
Entry<K, V> e = i.next();
K key = e.getKey();
V value = e.getValue();
sb.append(key == this ? "(this Map)" : key);
sb.append('=');
sb.append(value == this ? "(this Map)" : value);
if (!i.hasNext()) {
return sb.append('}').toString();
}
sb.append(',').append(' ');
}
}
}

67
spring-core/src/main/java/org/springframework/util/CompositeSet.java

@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
/*
* Copyright 2002-2024 the original author or 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
*
* https://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 org.springframework.util;
import java.util.Set;
/**
* Composite set that combines two other sets. This type is only exposed through
* {@link CompositeMap#keySet()} and {@link CompositeMap#entrySet()}.
*
* @author Arjen Poutsma
* @since 6.2
* @param <E> the type of elements maintained by this set
*/
final class CompositeSet<E> extends CompositeCollection<E> implements Set<E> {
CompositeSet(Set<E> first, Set<E> second) {
super(first, second);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
else if (obj instanceof Set<?> set) {
if (set.size() != size()) {
return false;
}
try {
return containsAll(set);
}
catch (ClassCastException | NullPointerException ignored) {
return false;
}
}
else {
return false;
}
}
@Override
public int hashCode() {
int hashCode = 0;
for (E obj : this) {
if (obj != null) {
hashCode += obj.hashCode();
}
}
return hashCode;
}
}

195
spring-core/src/test/java/org/springframework/util/CompositeCollectionTests.java

@ -0,0 +1,195 @@ @@ -0,0 +1,195 @@
/*
* Copyright 2002-2024 the original author or 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
*
* https://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 org.springframework.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Arjen Poutsma
*/
class CompositeCollectionTests {
@Test
void size() {
List<String> first = List.of("foo", "bar", "baz");
List<String> second = List.of("qux", "quux");
CompositeCollection<String> composite = new CompositeCollection<>(first, second);
assertThat(composite).hasSize(5);
}
@Test
void isEmpty() {
List<String> first = List.of("foo", "bar", "baz");
List<String> second = List.of("qux", "quux");
CompositeCollection<String> composite = new CompositeCollection<>(first, second);
assertThat(composite).isNotEmpty();
composite = new CompositeCollection<>(Collections.emptyList(), Collections.emptyList());
assertThat(composite).isEmpty();
}
@Test
void contains() {
List<String> first = List.of("foo", "bar");
List<String> second = List.of("baz", "qux");
CompositeCollection<String> composite = new CompositeCollection<>(first, second);
assertThat(composite.contains("foo")).isTrue();
assertThat(composite.contains("bar")).isTrue();
assertThat(composite.contains("baz")).isTrue();
assertThat(composite.contains("qux")).isTrue();
assertThat(composite.contains("quux")).isFalse();
}
@Test
void iterator() {
List<String> first = List.of("foo", "bar");
List<String> second = List.of("baz", "qux");
CompositeCollection<String> composite = new CompositeCollection<>(first, second);
Iterator<String> iterator = composite.iterator();
assertThat(iterator).hasNext();
assertThat(iterator.next()).isEqualTo("foo");
assertThat(iterator).hasNext();
assertThat(iterator.next()).isEqualTo("bar");
assertThat(iterator).hasNext();
assertThat(iterator.next()).isEqualTo("baz");
assertThat(iterator).hasNext();
assertThat(iterator.next()).isEqualTo("qux");
assertThat(iterator).isExhausted();
}
@Test
void toArray() {
List<String> first = List.of("foo", "bar");
List<String> second = List.of("baz", "qux");
CompositeCollection<String> composite = new CompositeCollection<>(first, second);
Object[] array = composite.toArray();
assertThat(array).containsExactly("foo", "bar", "baz", "qux");
}
@Test
void toArrayArgs() {
List<String> first = List.of("foo", "bar");
List<String> second = List.of("baz", "qux");
CompositeCollection<String> composite = new CompositeCollection<>(first, second);
String[] array = new String[composite.size()];
array = composite.toArray(array);
assertThat(array).containsExactly("foo", "bar", "baz", "qux");
}
@Test
void add() {
List<String> first = List.of("foo", "bar");
List<String> second = List.of("baz", "qux");
CompositeCollection<String> composite = new CompositeCollection<>(first, second);
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> composite.add("quux"));
}
@Test
void remove() {
List<String> first = new ArrayList<>(List.of("foo", "bar"));
List<String> second = new ArrayList<>(List.of("baz", "qux"));
CompositeCollection<String> composite = new CompositeCollection<>(first, second);
assertThat(composite.remove("foo")).isTrue();
assertThat(composite.contains("foo")).isFalse();
assertThat(first).containsExactly("bar");
assertThat(composite.remove("quux")).isFalse();
}
@Test
void containsAll() {
List<String> first = List.of("foo", "bar");
List<String> second = List.of("baz", "qux");
CompositeCollection<String> composite = new CompositeCollection<>(first, second);
List<String> all = new ArrayList<>(first);
all.addAll(second);
assertThat(composite.containsAll(all)).isTrue();
all.add("quux");
assertThat(composite.containsAll(all)).isFalse();
}
@Test
void addAll() {
List<String> first = List.of("foo", "bar");
List<String> second = List.of("baz", "qux");
CompositeCollection<String> composite = new CompositeCollection<>(first, second);
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> composite.addAll(List.of("quux", "corge")));
}
@Test
void removeAll() {
List<String> first = new ArrayList<>(List.of("foo", "bar"));
List<String> second = new ArrayList<>(List.of("baz", "qux"));
CompositeCollection<String> composite = new CompositeCollection<>(first, second);
List<String> all = new ArrayList<>(first);
all.addAll(second);
assertThat(composite.removeAll(all)).isTrue();
assertThat(composite).isEmpty();
assertThat(first).isEmpty();
assertThat(second).isEmpty();
}
@Test
void retainAll() {
List<String> first = new ArrayList<>(List.of("foo", "bar"));
List<String> second = new ArrayList<>(List.of("baz", "qux"));
CompositeCollection<String> composite = new CompositeCollection<>(first, second);
assertThat(composite.retainAll(List.of("bar", "baz"))).isTrue();
assertThat(composite).containsExactly("bar", "baz");
assertThat(first).containsExactly("bar");
assertThat(second).containsExactly("baz");
}
@Test
void clear() {
List<String> first = new ArrayList<>(List.of("foo", "bar"));
List<String> second = new ArrayList<>(List.of("baz", "qux"));
CompositeCollection<String> composite = new CompositeCollection<>(first, second);
composite.clear();
assertThat(composite).isEmpty();
assertThat(first).isEmpty();
assertThat(second).isEmpty();
}
}

221
spring-core/src/test/java/org/springframework/util/CompositeMapTests.java

@ -0,0 +1,221 @@ @@ -0,0 +1,221 @@
/*
* Copyright 2002-2024 the original author or 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
*
* https://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 org.springframework.util;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.entry;
/**
* @author Arjen Poutsma
*/
class CompositeMapTests {
@Test
void size() {
Map<String, String> first = Map.of("foo", "bar", "baz", "qux");
Map<String, String> second = Map.of("quux", "corge");
CompositeMap<String, String> composite = new CompositeMap<>(first, second);
assertThat(composite).hasSize(3);
}
@Test
void isEmpty() {
Map<String, String> first = Map.of("foo", "bar", "baz", "qux");
Map<String, String> second = Map.of("quux", "corge");
CompositeMap<String, String> composite = new CompositeMap<>(first, second);
assertThat(composite).isNotEmpty();
composite = new CompositeMap<>(Collections.emptyMap(), Collections.emptyMap());
assertThat(composite).isEmpty();
}
@Test
void containsKey() {
Map<String, String> first = Map.of("foo", "bar", "baz", "qux");
Map<String, String> second = Map.of("quux", "corge");
CompositeMap<String, String> composite = new CompositeMap<>(first, second);
assertThat(composite.containsKey("foo")).isTrue();
assertThat(composite.containsKey("bar")).isFalse();
assertThat(composite.containsKey("baz")).isTrue();
assertThat(composite.containsKey("qux")).isFalse();
assertThat(composite.containsKey("quux")).isTrue();
assertThat(composite.containsKey("corge")).isFalse();
}
@Test
void containsValue() {
Map<String, String> first = Map.of("foo", "bar", "baz", "qux");
Map<String, String> second = Map.of("quux", "corge");
CompositeMap<String, String> composite = new CompositeMap<>(first, second);
assertThat(composite.containsValue("foo")).isFalse();
assertThat(composite.containsValue("bar")).isTrue();
assertThat(composite.containsValue("baz")).isFalse();
assertThat(composite.containsValue("qux")).isTrue();
assertThat(composite.containsValue("quux")).isFalse();
assertThat(composite.containsValue("corge")).isTrue();
}
@Test
void get() {
Map<String, String> first = Map.of("foo", "bar", "baz", "qux");
Map<String, String> second = Map.of("quux", "corge");
CompositeMap<String, String> composite = new CompositeMap<>(first, second);
assertThat(composite.get("foo")).isEqualTo("bar");
assertThat(composite.get("baz")).isEqualTo("qux");
assertThat(composite.get("quux")).isEqualTo("corge");
assertThat(composite.get("grault")).isNull();
}
@Test
void putUnsupported() {
Map<String, String> first = Map.of("foo", "bar");
Map<String, String> second = Map.of("baz", "qux");
CompositeMap<String, String> composite = new CompositeMap<>(first, second);
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> composite.put("grault", "garply"));
}
@Test
void putSupported() {
Map<String, String> first = Map.of("foo", "bar");
Map<String, String> second = Map.of("baz", "qux");
CompositeMap<String, String> composite = new CompositeMap<>(first, second, (k,v) -> {
assertThat(k).isEqualTo("quux");
assertThat(v).isEqualTo("corge");
return "grault";
}, null);
assertThat(composite.put("quux", "corge")).isEqualTo("grault");
}
@Test
void remove() {
Map<String, String> first = new HashMap<>(Map.of("foo", "bar", "baz", "qux"));
Map<String, String> second = new HashMap<>(Map.of("quux", "corge"));
CompositeMap<String, String> composite = new CompositeMap<>(first, second);
assertThat(composite.remove("foo")).isEqualTo("bar");
assertThat(composite.containsKey("foo")).isFalse();
assertThat(first).containsExactly(entry("baz", "qux"));
assertThat(composite.remove("grault")).isNull();
}
@Test
void putAllUnsupported() {
Map<String, String> first = Map.of("foo", "bar");
Map<String, String> second = Map.of("baz", "qux");
CompositeMap<String, String> composite = new CompositeMap<>(first, second);
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> composite.putAll(Map.of("quux", "corge", "grault", "garply")));
}
@Test
void putAllPutFunction() {
Map<String, String> first = Map.of("foo", "bar");
Map<String, String> second = Map.of("baz", "qux");
AtomicBoolean functionInvoked = new AtomicBoolean();
CompositeMap<String, String> composite = new CompositeMap<>(first, second, (k,v) -> {
assertThat(k).isEqualTo("quux");
assertThat(v).isEqualTo("corge");
functionInvoked.set(true);
return "grault";
}, null);
composite.putAll(Map.of("quux", "corge"));
assertThat(functionInvoked).isTrue();
}
@Test
void putAllPutAllFunction() {
Map<String, String> first = Map.of("foo", "bar");
Map<String, String> second = Map.of("baz", "qux");
AtomicBoolean functionInvoked = new AtomicBoolean();
Map<String, String> argument = Map.of("quux", "corge");
CompositeMap<String, String> composite = new CompositeMap<>(first, second, null,
m -> {
assertThat(m).isSameAs(argument);
functionInvoked.set(true);
});
composite.putAll(argument);
assertThat(functionInvoked).isTrue();
}
@Test
void clear() {
Map<String, String> first = new HashMap<>(Map.of("foo", "bar", "baz", "qux"));
Map<String, String> second = new HashMap<>(Map.of("quux", "corge"));
CompositeMap<String, String> composite = new CompositeMap<>(first, second);
composite.clear();
assertThat(composite).isEmpty();
assertThat(first).isEmpty();
assertThat(second).isEmpty();
}
@Test
void keySet() {
Map<String, String> first = Map.of("foo", "bar");
Map<String, String> second = Map.of("baz", "qux");
CompositeMap<String, String> composite = new CompositeMap<>(first, second);
Set<String> keySet = composite.keySet();
assertThat(keySet).containsExactly("foo", "baz");
}
@Test
void values() {
Map<String, String> first = Map.of("foo", "bar");
Map<String, String> second = Map.of("baz", "qux");
CompositeMap<String, String> composite = new CompositeMap<>(first, second);
Collection<String> values = composite.values();
assertThat(values).containsExactly("bar", "qux");
}
@Test
void entrySet() {
Map<String, String> first = Map.of("foo", "bar");
Map<String, String> second = Map.of("baz", "qux");
CompositeMap<String, String> composite = new CompositeMap<>(first, second);
Set<Map.Entry<String, String>> entries = composite.entrySet();
assertThat(entries).containsExactly(entry("foo", "bar"), entry("baz", "qux"));
}
}

47
spring-core/src/test/java/org/springframework/util/CompositeSetTests.java

@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
/*
* Copyright 2002-2024 the original author or 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
*
* https://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 org.springframework.util;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Arjen Poutsma
*/
class CompositeSetTests {
@Test
void testEquals() {
Set<String> first = Set.of("foo", "bar");
Set<String> second = Set.of("baz", "qux");
CompositeSet<String> composite = new CompositeSet<>(first, second);
Set<String> all = new HashSet<>(first);
all.addAll(second);
assertThat(composite.equals(all)).isTrue();
assertThat(composite.equals(first)).isFalse();
assertThat(composite.equals(second)).isFalse();
assertThat(composite.equals(Collections.emptySet())).isFalse();
}
}

43
spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java

@ -329,28 +329,6 @@ public abstract class RequestPredicates { @@ -329,28 +329,6 @@ public abstract class RequestPredicates {
}
}
private static <K, V> Map<K, V> mergeMaps(Map<K, V> left, Map<K, V> right) {
if (left.isEmpty()) {
if (right.isEmpty()) {
return Collections.emptyMap();
}
else {
return right;
}
}
else {
if (right.isEmpty()) {
return left;
}
else {
Map<K, V> result = CollectionUtils.newLinkedHashMap(left.size() + right.size());
result.putAll(left);
result.putAll(right);
return result;
}
}
}
/**
* Receives notifications from the logical structure of request predicates.
@ -640,7 +618,7 @@ public abstract class RequestPredicates { @@ -640,7 +618,7 @@ public abstract class RequestPredicates {
private void modifyAttributes(Map<String, Object> attributes, ServerRequest request,
Map<String, String> variables) {
Map<String, String> pathVariables = mergeMaps(request.pathVariables(), variables);
Map<String, String> pathVariables = CollectionUtils.compositeMap(request.pathVariables(), variables);
attributes.put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
Collections.unmodifiableMap(pathVariables));
@ -1334,7 +1312,9 @@ public abstract class RequestPredicates { @@ -1334,7 +1312,9 @@ public abstract class RequestPredicates {
public ExtendedAttributesServerRequestWrapper(ServerRequest delegate, Map<String, Object> newAttributes) {
super(delegate);
Assert.notNull(newAttributes, "NewAttributes must not be null");
this.attributes = mergeMaps(delegate.attributes(), newAttributes);
Map<String, Object> oldAttributes = delegate.attributes();
this.attributes = CollectionUtils.compositeMap(newAttributes, oldAttributes, newAttributes::put,
newAttributes::putAll);
}
@Override
@ -1383,12 +1363,21 @@ public abstract class RequestPredicates { @@ -1383,12 +1363,21 @@ public abstract class RequestPredicates {
Map<String, String> oldPathVariables = request.pathVariables();
Map<String, String> pathVariables;
if (oldPathVariables.isEmpty()) {
pathVariables = newPathVariables;
}
else {
pathVariables = CollectionUtils.compositeMap(oldPathVariables, newPathVariables);
}
PathPattern oldPathPattern = (PathPattern) request.attribute(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE)
.orElse(null);
PathPattern pathPattern = mergePatterns(oldPathPattern, newPathPattern);
Map<String, Object> result = new LinkedHashMap<>(2);
result.put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, mergeMaps(oldPathVariables, newPathVariables));
result.put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, mergePatterns(oldPathPattern, newPathPattern));
Map<String, Object> result = CollectionUtils.newLinkedHashMap(2);
result.put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, pathVariables);
result.put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, pathPattern);
return result;
}

41
spring-webmvc/src/main/java/org/springframework/web/servlet/function/RequestPredicates.java

@ -327,28 +327,6 @@ public abstract class RequestPredicates { @@ -327,28 +327,6 @@ public abstract class RequestPredicates {
}
}
private static <K, V> Map<K, V> mergeMaps(Map<K, V> left, Map<K, V> right) {
if (left.isEmpty()) {
if (right.isEmpty()) {
return Collections.emptyMap();
}
else {
return right;
}
}
else {
if (right.isEmpty()) {
return left;
}
else {
Map<K, V> result = CollectionUtils.newLinkedHashMap(left.size() + right.size());
result.putAll(left);
result.putAll(right);
return result;
}
}
}
/**
* Receives notifications from the logical structure of request predicates.
@ -638,7 +616,7 @@ public abstract class RequestPredicates { @@ -638,7 +616,7 @@ public abstract class RequestPredicates {
private void modifyAttributes(Map<String, Object> attributes, ServerRequest request,
Map<String, String> variables) {
Map<String, String> pathVariables = mergeMaps(request.pathVariables(), variables);
Map<String, String> pathVariables = CollectionUtils.compositeMap(request.pathVariables(), variables);
attributes.put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
Collections.unmodifiableMap(pathVariables));
@ -1302,7 +1280,9 @@ public abstract class RequestPredicates { @@ -1302,7 +1280,9 @@ public abstract class RequestPredicates {
public ExtendedAttributesServerRequestWrapper(ServerRequest delegate, Map<String, Object> newAttributes) {
super(delegate);
Assert.notNull(newAttributes, "NewAttributes must not be null");
this.attributes = mergeMaps(delegate.attributes(), newAttributes);
Map<String, Object> oldAttributes = delegate.attributes();
this.attributes = CollectionUtils.compositeMap(newAttributes, oldAttributes, newAttributes::put,
newAttributes::putAll);
}
@Override
@ -1351,12 +1331,21 @@ public abstract class RequestPredicates { @@ -1351,12 +1331,21 @@ public abstract class RequestPredicates {
Map<String, String> oldPathVariables = request.pathVariables();
Map<String, String> pathVariables;
if (oldPathVariables.isEmpty()) {
pathVariables = newPathVariables;
}
else {
pathVariables = CollectionUtils.compositeMap(oldPathVariables, newPathVariables);
}
PathPattern oldPathPattern = (PathPattern) request.attribute(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE)
.orElse(null);
PathPattern pathPattern = mergePatterns(oldPathPattern, newPathPattern);
Map<String, Object> result = CollectionUtils.newLinkedHashMap(2);
result.put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, mergeMaps(oldPathVariables, newPathVariables));
result.put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, mergePatterns(oldPathPattern, newPathPattern));
result.put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, pathVariables);
result.put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, pathPattern);
return result;
}

Loading…
Cancel
Save