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
This commit is contained in:
Arjen Poutsma
2024-02-14 09:55:52 +01:00
parent 89d746ddf8
commit aee03c5201
9 changed files with 953 additions and 53 deletions

View File

@@ -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 {
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);
}
}

View File

@@ -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();
}
}

View File

@@ -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(' ');
}
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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"));
}
}

View File

@@ -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();
}
}

View File

@@ -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 {
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 {
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 {
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;
}

View File

@@ -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 {
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 {
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 {
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;
}