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:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user