Browse Source
This commit makes several improvements to MultiValueMap: - asSingleValueMap offers a single-value view (as opposed to the existing toSingleValueMap, which offers a copy) - fromSingleValue is a static method that adapts a Map<?,?> to the MultiValueMap interface - fromMultiValue is a static method that adapts a Map<?,List<?>> to the MultiValueMap interface Closes gh-32832pull/32896/head
12 changed files with 978 additions and 8 deletions
@ -0,0 +1,274 @@
@@ -0,0 +1,274 @@
|
||||
/* |
||||
* 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.io.Serializable; |
||||
import java.util.AbstractCollection; |
||||
import java.util.AbstractMap; |
||||
import java.util.AbstractSet; |
||||
import java.util.Collection; |
||||
import java.util.Collections; |
||||
import java.util.Iterator; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
import java.util.function.BiConsumer; |
||||
|
||||
import org.springframework.lang.Nullable; |
||||
|
||||
/** |
||||
* Adapts a given {@link MultiValueMap} to the {@link Map} contract. The |
||||
* difference with {@link SingleToMultiValueMapAdapter} and |
||||
* {@link MultiValueMapAdapter} is that this class adapts in the opposite |
||||
* direction. |
||||
* |
||||
* @author Arjen Poutsma |
||||
* @since 6.2 |
||||
* @param <K> the key type |
||||
* @param <V> the value element type |
||||
*/ |
||||
@SuppressWarnings("serial") |
||||
final class MultiToSingleValueMapAdapter<K, V> implements Map<K, V>, Serializable { |
||||
|
||||
private final MultiValueMap<K, V> targetMap; |
||||
|
||||
@Nullable |
||||
private transient Collection<V> values; |
||||
|
||||
@Nullable |
||||
private transient Set<Entry<K, V>> entries; |
||||
|
||||
|
||||
/** |
||||
* Wrap the given target {@link MultiValueMap} as a {@link Map} adapter. |
||||
* @param targetMap the target {@code MultiValue} |
||||
*/ |
||||
public MultiToSingleValueMapAdapter(MultiValueMap<K, V> targetMap) { |
||||
Assert.notNull(targetMap, "'targetMap' must not be null"); |
||||
this.targetMap = targetMap; |
||||
} |
||||
|
||||
@Override |
||||
public int size() { |
||||
return this.targetMap.size(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isEmpty() { |
||||
return this.targetMap.isEmpty(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean containsKey(Object key) { |
||||
return this.targetMap.containsKey(key); |
||||
} |
||||
|
||||
@Override |
||||
public boolean containsValue(@Nullable Object value) { |
||||
Iterator<Entry<K, V>> i = entrySet().iterator(); |
||||
if (value == null) { |
||||
while (i.hasNext()) { |
||||
Entry<K, V> e = i.next(); |
||||
if (e.getValue() == null) { |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
else { |
||||
while (i.hasNext()) { |
||||
Entry<K, V> e = i.next(); |
||||
if (value.equals(e.getValue())) { |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
@Nullable |
||||
public V get(Object key) { |
||||
return adaptValue(this.targetMap.get(key)); |
||||
} |
||||
|
||||
@Nullable |
||||
@Override |
||||
public V put(K key, @Nullable V value) { |
||||
return adaptValue(this.targetMap.put(key, adaptValue(value))); |
||||
} |
||||
|
||||
@Override |
||||
@Nullable |
||||
public V remove(Object key) { |
||||
return adaptValue(this.targetMap.remove(key)); |
||||
} |
||||
|
||||
@Override |
||||
public void putAll(Map<? extends K, ? extends V> map) { |
||||
for (Entry<? extends K, ? extends V> entry : map.entrySet()) { |
||||
put(entry.getKey(), entry.getValue()); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void clear() { |
||||
this.targetMap.clear(); |
||||
} |
||||
|
||||
@Override |
||||
public Set<K> keySet() { |
||||
return this.targetMap.keySet(); |
||||
} |
||||
|
||||
@Override |
||||
public Collection<V> values() { |
||||
Collection<V> values = this.values; |
||||
if (values == null) { |
||||
Collection<List<V>> targetValues = this.targetMap.values(); |
||||
values = new AbstractCollection<V>() { |
||||
@Override |
||||
public Iterator<V> iterator() { |
||||
Iterator<List<V>> targetIterator = targetValues.iterator(); |
||||
return new Iterator<V>() { |
||||
@Override |
||||
public boolean hasNext() { |
||||
return targetIterator.hasNext(); |
||||
} |
||||
|
||||
@Override |
||||
public V next() { |
||||
return targetIterator.next().get(0); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
@Override |
||||
public int size() { |
||||
return targetValues.size(); |
||||
} |
||||
}; |
||||
this.values = values; |
||||
} |
||||
return values; |
||||
} |
||||
|
||||
|
||||
|
||||
@Override |
||||
public Set<Entry<K, V>> entrySet() { |
||||
Set<Entry<K, V>> entries = this.entries; |
||||
if (entries == null) { |
||||
Set<Entry<K, List<V>>> targetEntries = this.targetMap.entrySet(); |
||||
entries = new AbstractSet<>() { |
||||
@Override |
||||
public Iterator<Entry<K, V>> iterator() { |
||||
Iterator<Entry<K, List<V>>> targetIterator = targetEntries.iterator(); |
||||
return new Iterator<>() { |
||||
@Override |
||||
public boolean hasNext() { |
||||
return targetIterator.hasNext(); |
||||
} |
||||
|
||||
@Override |
||||
public Entry<K, V> next() { |
||||
Entry<K, List<V>> entry = targetIterator.next(); |
||||
return new AbstractMap.SimpleImmutableEntry<>(entry.getKey(), entry.getValue().get(0)); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
@Override |
||||
public int size() { |
||||
return targetEntries.size(); |
||||
} |
||||
}; |
||||
this.entries = entries; |
||||
} |
||||
return entries; |
||||
} |
||||
|
||||
@Override |
||||
public void forEach(BiConsumer<? super K, ? super V> action) { |
||||
this.targetMap.forEach((k, vs) -> action.accept(k, vs.get(0))); |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(@Nullable Object o) { |
||||
if (o == this) { |
||||
return true; |
||||
} |
||||
else if (o instanceof Map<?,?> other) { |
||||
if (this.size() != other.size()) { |
||||
return false; |
||||
} |
||||
try { |
||||
for (Entry<K, V> e : entrySet()) { |
||||
K key = e.getKey(); |
||||
V value = e.getValue(); |
||||
if (value == null) { |
||||
if (other.get(key) != null || !other.containsKey(key)) { |
||||
return false; |
||||
} |
||||
} |
||||
else { |
||||
if (!value.equals(other.get(key))) { |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
catch (ClassCastException | NullPointerException ignore) { |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
else { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return this.targetMap.hashCode(); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return this.targetMap.toString(); |
||||
} |
||||
|
||||
@Nullable |
||||
private V adaptValue(@Nullable List<V> values) { |
||||
if (!CollectionUtils.isEmpty(values)) { |
||||
return values.get(0); |
||||
} |
||||
else { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
@Nullable |
||||
private List<V> adaptValue(@Nullable V value) { |
||||
if (value != null) { |
||||
return Collections.singletonList(value); |
||||
} |
||||
else { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,319 @@
@@ -0,0 +1,319 @@
|
||||
/* |
||||
* 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.io.Serializable; |
||||
import java.util.AbstractCollection; |
||||
import java.util.AbstractMap; |
||||
import java.util.AbstractSet; |
||||
import java.util.Collection; |
||||
import java.util.Collections; |
||||
import java.util.Iterator; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
import java.util.function.BiConsumer; |
||||
|
||||
import org.springframework.lang.Nullable; |
||||
|
||||
/** |
||||
* Adapts a given {@link MultiValueMap} to the {@link Map} contract. The |
||||
* difference with {@link MultiValueMapAdapter} is that this class delegates to |
||||
* a {@code Map<K, V>}, whereas {@link MultiValueMapAdapter} needs a |
||||
* {@code Map<K, List<V>>}. {@link MultiToSingleValueMapAdapter} adapts in the |
||||
* opposite direction as this class. |
||||
* |
||||
* @author Arjen Poutsma |
||||
* @since 6.2 |
||||
* @param <K> the key type |
||||
* @param <V> the value element type |
||||
*/ |
||||
@SuppressWarnings("serial") |
||||
final class SingleToMultiValueMapAdapter<K, V> implements MultiValueMap<K, V>, Serializable { |
||||
|
||||
private final Map<K, V> targetMap; |
||||
|
||||
@Nullable |
||||
private transient Collection<List<V>> values; |
||||
|
||||
@Nullable |
||||
private transient Set<Entry<K, List<V>>> entries; |
||||
|
||||
|
||||
/** |
||||
* Wrap the given target {@link Map} as a {@link MultiValueMap} adapter. |
||||
* @param targetMap the plain target {@code Map} |
||||
*/ |
||||
public SingleToMultiValueMapAdapter(Map<K, V> targetMap) { |
||||
Assert.notNull(targetMap, "'targetMap' must not be null"); |
||||
this.targetMap = targetMap; |
||||
} |
||||
|
||||
|
||||
// MultiValueMap implementation
|
||||
|
||||
@Override |
||||
@Nullable |
||||
public V getFirst(K key) { |
||||
return this.targetMap.get(key); |
||||
} |
||||
|
||||
@Override |
||||
public void add(K key, @Nullable V value) { |
||||
if (!this.targetMap.containsKey(key)) { |
||||
this.targetMap.put(key, value); |
||||
} |
||||
else { |
||||
throw new UnsupportedOperationException("Duplicate key: " + key); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
@SuppressWarnings("unchecked") |
||||
public void addAll(K key, List<? extends V> values) { |
||||
if (!this.targetMap.containsKey(key)) { |
||||
put(key, (List<V>) values); |
||||
} |
||||
else { |
||||
throw new UnsupportedOperationException("Duplicate key: " + key); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void addAll(MultiValueMap<K, V> values) { |
||||
values.forEach(this::addAll); |
||||
} |
||||
|
||||
@Override |
||||
public void set(K key, @Nullable V value) { |
||||
this.targetMap.put(key, value); |
||||
} |
||||
|
||||
@Override |
||||
public void setAll(Map<K, V> values) { |
||||
this.targetMap.putAll(values); |
||||
} |
||||
|
||||
@Override |
||||
public Map<K, V> toSingleValueMap() { |
||||
return Collections.unmodifiableMap(this.targetMap); |
||||
} |
||||
|
||||
|
||||
// Map implementation
|
||||
|
||||
@Override |
||||
public int size() { |
||||
return this.targetMap.size(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isEmpty() { |
||||
return this.targetMap.isEmpty(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean containsKey(Object key) { |
||||
return this.targetMap.containsKey(key); |
||||
} |
||||
|
||||
@Override |
||||
public boolean containsValue(@Nullable Object value) { |
||||
Iterator<Entry<K, List<V>>> i = entrySet().iterator(); |
||||
if (value == null) { |
||||
while (i.hasNext()) { |
||||
Entry<K, List<V>> e = i.next(); |
||||
if (e.getValue() == null || e.getValue().isEmpty()) { |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
else { |
||||
while (i.hasNext()) { |
||||
Entry<K, List<V>> e = i.next(); |
||||
if (value.equals(e.getValue())) { |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
@Nullable |
||||
public List<V> get(Object key) { |
||||
V value = this.targetMap.get(key); |
||||
return (value != null) ? Collections.singletonList(value) : null; |
||||
} |
||||
|
||||
@Override |
||||
@Nullable |
||||
public List<V> put(K key, List<V> values) { |
||||
if (values.isEmpty()) { |
||||
V result = this.targetMap.put(key, null); |
||||
return (result != null) ? Collections.singletonList(result) : null; |
||||
} |
||||
else if (values.size() == 1) { |
||||
V result = this.targetMap.put(key, values.get(0)); |
||||
return (result != null) ? Collections.singletonList(result) : null; |
||||
} |
||||
else { |
||||
throw new UnsupportedOperationException("Duplicate key: " + key); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
@Nullable |
||||
public List<V> remove(Object key) { |
||||
V result = this.targetMap.remove(key); |
||||
return (result != null) ? Collections.singletonList(result) : null; |
||||
} |
||||
|
||||
@Override |
||||
public void putAll(Map<? extends K, ? extends List<V>> map) { |
||||
for (Entry<? extends K, ? extends List<V>> entry : map.entrySet()) { |
||||
put(entry.getKey(), entry.getValue()); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void clear() { |
||||
this.targetMap.clear(); |
||||
} |
||||
|
||||
@Override |
||||
public Set<K> keySet() { |
||||
return this.targetMap.keySet(); |
||||
} |
||||
|
||||
@Override |
||||
public Collection<List<V>> values() { |
||||
Collection<List<V>> values = this.values; |
||||
if (values == null) { |
||||
Collection<V> targetValues = this.targetMap.values(); |
||||
values = new AbstractCollection<>() { |
||||
@Override |
||||
public Iterator<List<V>> iterator() { |
||||
Iterator<V> targetIterator = targetValues.iterator(); |
||||
return new Iterator<>() { |
||||
@Override |
||||
public boolean hasNext() { |
||||
return targetIterator.hasNext(); |
||||
} |
||||
|
||||
@Override |
||||
public List<V> next() { |
||||
return Collections.singletonList(targetIterator.next()); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
@Override |
||||
public int size() { |
||||
return targetValues.size(); |
||||
} |
||||
}; |
||||
this.values = values; |
||||
} |
||||
return values; |
||||
} |
||||
|
||||
@Override |
||||
public Set<Entry<K, List<V>>> entrySet() { |
||||
Set<Entry<K, List<V>>> entries = this.entries; |
||||
if (entries == null) { |
||||
Set<Entry<K, V>> targetEntries = this.targetMap.entrySet(); |
||||
entries = new AbstractSet<>() { |
||||
@Override |
||||
public Iterator<Entry<K, List<V>>> iterator() { |
||||
Iterator<Entry<K, V>> targetIterator = targetEntries.iterator(); |
||||
return new Iterator<>() { |
||||
@Override |
||||
public boolean hasNext() { |
||||
return targetIterator.hasNext(); |
||||
} |
||||
|
||||
@Override |
||||
public Entry<K, List<V>> next() { |
||||
Entry<K, V> entry = targetIterator.next(); |
||||
return new AbstractMap.SimpleImmutableEntry<>(entry.getKey(), |
||||
Collections.singletonList(entry.getValue())); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
@Override |
||||
public int size() { |
||||
return targetEntries.size(); |
||||
} |
||||
}; |
||||
this.entries = entries; |
||||
} |
||||
return entries; |
||||
} |
||||
|
||||
@Override |
||||
public void forEach(BiConsumer<? super K, ? super List<V>> action) { |
||||
this.targetMap.forEach((k, v) -> action.accept(k, Collections.singletonList(v))); |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(@Nullable Object o) { |
||||
if (o == this) { |
||||
return true; |
||||
} |
||||
else if (o instanceof Map<?,?> other) { |
||||
if (this.size() != other.size()) { |
||||
return false; |
||||
} |
||||
try { |
||||
for (Entry<K, List<V>> e : entrySet()) { |
||||
K key = e.getKey(); |
||||
List<V> values = e.getValue(); |
||||
if (values == null) { |
||||
if (other.get(key) != null || !other.containsKey(key)) { |
||||
return false; |
||||
} |
||||
} |
||||
else { |
||||
if (!values .equals(other.get(key))) { |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
catch (ClassCastException | NullPointerException ignore) { |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
else { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return this.targetMap.hashCode(); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return this.targetMap.toString(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,131 @@
@@ -0,0 +1,131 @@
|
||||
/* |
||||
* 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.LinkedHashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.entry; |
||||
|
||||
/** |
||||
* @author Arjen Poutsma |
||||
*/ |
||||
class MultiToSingleValueMapAdapterTests { |
||||
|
||||
private LinkedMultiValueMap<String, String> delegate; |
||||
|
||||
private Map<String, String> adapter; |
||||
|
||||
|
||||
@BeforeEach |
||||
void setUp() { |
||||
this.delegate = new LinkedMultiValueMap<>(); |
||||
this.delegate.add("foo", "bar"); |
||||
this.delegate.add("foo", "baz"); |
||||
this.delegate.add("qux", "quux"); |
||||
|
||||
this.adapter = new MultiToSingleValueMapAdapter<>(this.delegate); |
||||
} |
||||
|
||||
@Test |
||||
void size() { |
||||
assertThat(this.adapter.size()).isEqualTo(this.delegate.size()).isEqualTo(2); |
||||
} |
||||
|
||||
@Test |
||||
void isEmpty() { |
||||
assertThat(this.adapter.isEmpty()).isFalse(); |
||||
|
||||
this.adapter = new MultiToSingleValueMapAdapter<>(new LinkedMultiValueMap<>()); |
||||
assertThat(this.adapter.isEmpty()).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void containsKey() { |
||||
assertThat(this.adapter.containsKey("foo")).isTrue(); |
||||
assertThat(this.adapter.containsKey("qux")).isTrue(); |
||||
assertThat(this.adapter.containsKey("corge")).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void containsValue() { |
||||
assertThat(this.adapter.containsValue("bar")).isTrue(); |
||||
assertThat(this.adapter.containsValue("quux")).isTrue(); |
||||
assertThat(this.adapter.containsValue("corge")).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void get() { |
||||
assertThat(this.adapter.get("foo")).isEqualTo("bar"); |
||||
assertThat(this.adapter.get("qux")).isEqualTo("quux"); |
||||
assertThat(this.adapter.get("corge")).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void put() { |
||||
String result = this.adapter.put("foo", "bar"); |
||||
assertThat(result).isEqualTo("bar"); |
||||
assertThat(this.delegate.get("foo")).containsExactly("bar"); |
||||
} |
||||
|
||||
@Test |
||||
void remove() { |
||||
this.adapter.remove("foo"); |
||||
assertThat(this.adapter.containsKey("foo")).isFalse(); |
||||
assertThat(this.delegate.containsKey("foo")).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void putAll() { |
||||
LinkedHashMap<String, String> map = new LinkedHashMap<>(); |
||||
map.put("foo", "bar"); |
||||
map.put("qux", null); |
||||
this.adapter.putAll(map); |
||||
assertThat(this.adapter.get("foo")).isEqualTo("bar"); |
||||
assertThat(this.adapter.get("qux")).isNull(); |
||||
|
||||
assertThat(this.delegate.get("foo")).isEqualTo(List.of("bar")); |
||||
assertThat(this.adapter.get("qux")).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void clear() { |
||||
this.adapter.clear(); |
||||
assertThat(this.adapter).isEmpty(); |
||||
assertThat(this.delegate).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
void keySet() { |
||||
assertThat(this.adapter.keySet()).containsExactly("foo", "qux"); |
||||
} |
||||
|
||||
@Test |
||||
void values() { |
||||
assertThat(this.adapter.values()).containsExactly("bar", "quux"); |
||||
} |
||||
|
||||
@Test |
||||
void entrySet() { |
||||
assertThat(this.adapter.entrySet()).containsExactly(entry("foo", "bar"), entry("qux", "quux")); |
||||
} |
||||
} |
||||
@ -0,0 +1,184 @@
@@ -0,0 +1,184 @@
|
||||
/* |
||||
* 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.LinkedHashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import org.junit.jupiter.api.BeforeEach; |
||||
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 SingleToMultiValueMapAdapterTests { |
||||
|
||||
|
||||
private Map<String, String> delegate; |
||||
|
||||
private MultiValueMap<String, String> adapter; |
||||
|
||||
@BeforeEach |
||||
void setUp() { |
||||
this.delegate = new LinkedHashMap<>(); |
||||
this.delegate.put("foo", "bar"); |
||||
this.delegate.put("qux", "quux"); |
||||
|
||||
this.adapter = new SingleToMultiValueMapAdapter<>(this.delegate); |
||||
} |
||||
|
||||
@Test |
||||
void getFirst() { |
||||
assertThat(this.adapter.getFirst("foo")).isEqualTo("bar"); |
||||
assertThat(this.adapter.getFirst("qux")).isEqualTo("quux"); |
||||
assertThat(this.adapter.getFirst("corge")).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void add() { |
||||
this.adapter.add("corge", "grault"); |
||||
assertThat(this.adapter.getFirst("corge")).isEqualTo("grault"); |
||||
assertThat(this.delegate.get("corge")).isEqualTo("grault"); |
||||
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> |
||||
this.adapter.add("foo", "garply")); |
||||
} |
||||
|
||||
@Test |
||||
void addAll() { |
||||
MultiValueMap<String, String> map = new LinkedMultiValueMap<>(); |
||||
map.add("corge", "grault"); |
||||
this.adapter.addAll(map); |
||||
|
||||
assertThat(this.adapter.getFirst("corge")).isEqualTo("grault"); |
||||
assertThat(this.delegate.get("corge")).isEqualTo("grault"); |
||||
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> |
||||
this.adapter.addAll(map)); |
||||
} |
||||
|
||||
@Test |
||||
void set() { |
||||
this.adapter.set("foo", "baz"); |
||||
assertThat(this.delegate.get("foo")).isEqualTo("baz"); |
||||
} |
||||
|
||||
@Test |
||||
void setAll() { |
||||
this.adapter.setAll(Map.of("foo", "baz")); |
||||
assertThat(this.delegate.get("foo")).isEqualTo("baz"); |
||||
} |
||||
|
||||
@Test |
||||
void size() { |
||||
assertThat(this.adapter.size()).isEqualTo(this.delegate.size()).isEqualTo(2); |
||||
} |
||||
|
||||
@Test |
||||
void isEmpty() { |
||||
assertThat(this.adapter.isEmpty()).isFalse(); |
||||
|
||||
this.adapter = new SingleToMultiValueMapAdapter<>(Collections.emptyMap()); |
||||
assertThat(this.adapter.isEmpty()).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void containsKey() { |
||||
assertThat(this.adapter.containsKey("foo")).isTrue(); |
||||
assertThat(this.adapter.containsKey("qux")).isTrue(); |
||||
assertThat(this.adapter.containsKey("corge")).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void containsValue() { |
||||
assertThat(this.adapter.containsValue(List.of("bar"))).isTrue(); |
||||
assertThat(this.adapter.containsValue(List.of("quux"))).isTrue(); |
||||
assertThat(this.adapter.containsValue(List.of("corge"))).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void get() { |
||||
assertThat(this.adapter.get("foo")).isEqualTo(List.of("bar")); |
||||
assertThat(this.adapter.get("qux")).isEqualTo(List.of("quux")); |
||||
assertThat(this.adapter.get("corge")).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void put() { |
||||
assertThat(this.adapter.put("foo", List.of("baz"))).containsExactly("bar"); |
||||
assertThat(this.adapter.put("qux", Collections.emptyList())).containsExactly("quux"); |
||||
assertThat(this.adapter.put("grault", List.of("garply"))).isNull(); |
||||
|
||||
assertThat(this.delegate).containsExactly(entry("foo", "baz"), entry("qux", null), entry("grault", "garply")); |
||||
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> |
||||
this.adapter.put("foo", List.of("bar", "baz"))); |
||||
} |
||||
|
||||
@Test |
||||
void remove() { |
||||
assertThat(this.adapter.remove("foo")).isEqualTo(List.of("bar")); |
||||
assertThat(this.adapter.containsKey("foo")).isFalse(); |
||||
assertThat(this.delegate.containsKey("foo")).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void putAll() { |
||||
MultiValueMap<String, String> map = new LinkedMultiValueMap<>(); |
||||
map.add("foo", "baz"); |
||||
map.add("qux", null); |
||||
map.add("grault", "garply"); |
||||
this.adapter.putAll(map); |
||||
|
||||
assertThat(this.delegate).containsExactly(entry("foo", "baz"), entry("qux", null), entry("grault", "garply")); |
||||
} |
||||
|
||||
@Test |
||||
void clear() { |
||||
this.adapter.clear(); |
||||
assertThat(this.adapter).isEmpty(); |
||||
assertThat(this.delegate).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
void keySet() { |
||||
assertThat(this.adapter.keySet()).containsExactly("foo", "qux"); |
||||
} |
||||
|
||||
@Test |
||||
void values() { |
||||
assertThat(this.adapter.values()).containsExactly(List.of("bar"), List.of("quux")); |
||||
} |
||||
|
||||
@Test |
||||
void entrySet() { |
||||
assertThat(this.adapter.entrySet()).containsExactly(entry("foo", List.of("bar")), entry("qux", List.of("quux"))); |
||||
} |
||||
|
||||
@Test |
||||
void forEach() { |
||||
MultiValueMap<String, String> seen = new LinkedMultiValueMap<>(); |
||||
this.adapter.forEach(seen::put); |
||||
assertThat(seen).containsExactly(entry("foo", List.of("bar")), entry("qux", List.of("quux"))); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue