Various MultiValueMap improvements
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-32832
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* 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.
|
||||
@@ -89,8 +89,57 @@ public interface MultiValueMap<K, V> extends Map<K, List<V>> {
|
||||
|
||||
/**
|
||||
* Return a {@code Map} with the first values contained in this {@code MultiValueMap}.
|
||||
* The difference between this method and {@link #asSingleValueMap()} is
|
||||
* that this method returns a copy of the entries of this map, whereas
|
||||
* the latter returns a view.
|
||||
* @return a single value representation of this map
|
||||
*/
|
||||
Map<K, V> toSingleValueMap();
|
||||
|
||||
/**
|
||||
* Return this map as a {@code Map} with the first values contained in this {@code MultiValueMap}.
|
||||
* The difference between this method and {@link #toSingleValueMap()} is
|
||||
* that this method returns a view of the entries of this map, whereas
|
||||
* the latter returns a copy.
|
||||
* @return a single value representation of this map
|
||||
* @since 6.2
|
||||
*/
|
||||
default Map<K, V> asSingleValueMap() {
|
||||
return new MultiToSingleValueMapAdapter<>(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a {@code MultiValueMap<K, V>} that adapts the given single-value
|
||||
* {@code Map<K, V>}.
|
||||
* The returned map cannot map multiple values to the same key, and doing so
|
||||
* results in an {@link UnsupportedOperationException}. Use
|
||||
* {@link #fromMultiValue(Map)} to support multiple values.
|
||||
* @param map the map to be adapted
|
||||
* @param <K> the key type
|
||||
* @param <V> the value element type
|
||||
* @return a multi-value-map that delegates to {@code map}
|
||||
* @since 6.2
|
||||
* @see #fromMultiValue(Map)
|
||||
*/
|
||||
static <K, V> MultiValueMap<K, V> fromSingleValue(Map<K, V> map) {
|
||||
Assert.notNull(map, "Map must not be null");
|
||||
return new SingleToMultiValueMapAdapter<>(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@code MultiValueMap<K, V>} that adapts the given multi-value
|
||||
* {@code Map<K, List<V>>}.
|
||||
* @param map the map to be adapted
|
||||
* @param <K> the key type
|
||||
* @param <V> the value element type
|
||||
* @return a multi-value-map that delegates to {@code map}
|
||||
* @since 6.2
|
||||
* @see #fromSingleValue(Map)
|
||||
*/
|
||||
static <K, V> MultiValueMap<K, V> fromMultiValue(Map<K, List<V>> map) {
|
||||
Assert.notNull(map, "Map must not be null");
|
||||
return new MultiValueMapAdapter<>(map);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
@@ -120,6 +120,10 @@ final class UnmodifiableMultiValueMap<K,V> implements MultiValueMap<K,V>, Serial
|
||||
return this.delegate.toSingleValueMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<K, V> asSingleValueMap() {
|
||||
return this.delegate.asSingleValueMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
|
||||
@@ -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 @@
|
||||
/*
|
||||
* 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")));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* 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.
|
||||
@@ -109,7 +109,7 @@ public class MockMultipartHttpServletRequest extends MockHttpServletRequest impl
|
||||
|
||||
@Override
|
||||
public Map<String, MultipartFile> getFileMap() {
|
||||
return this.multipartFiles.toSingleValueMap();
|
||||
return this.multipartFiles.asSingleValueMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1774,6 +1774,10 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
|
||||
return this.headers.toSingleValueMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> asSingleValueMap() {
|
||||
return this.headers.asSingleValueMap();
|
||||
}
|
||||
|
||||
// Map implementation
|
||||
|
||||
|
||||
@@ -122,6 +122,11 @@ class ReadOnlyHttpHeaders extends HttpHeaders {
|
||||
return Collections.unmodifiableMap(this.headers.toSingleValueMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> asSingleValueMap() {
|
||||
return Collections.unmodifiableMap(this.headers.asSingleValueMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return Collections.unmodifiableSet(this.headers.keySet());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* 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.
|
||||
@@ -102,7 +102,7 @@ public abstract class AbstractMultipartHttpServletRequest extends HttpServletReq
|
||||
|
||||
@Override
|
||||
public Map<String, MultipartFile> getFileMap() {
|
||||
return getMultipartFiles().toSingleValueMap();
|
||||
return getMultipartFiles().asSingleValueMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -206,7 +206,7 @@ class MultipartRouterFunctionIntegrationTests extends AbstractRouterFunctionInte
|
||||
return request
|
||||
.body(BodyExtractors.toMultipartData())
|
||||
.flatMap(map -> {
|
||||
Map<String, Part> parts = map.toSingleValueMap();
|
||||
Map<String, Part> parts = map.asSingleValueMap();
|
||||
try {
|
||||
assertThat(parts).hasSize(2);
|
||||
assertThat(((FilePart) parts.get("fooPart")).filename()).isEqualTo("foo.txt");
|
||||
|
||||
@@ -146,7 +146,7 @@ class RedirectViewTests {
|
||||
assertThat(response.getHeader("Location")).isEqualTo("https://url.somewhere.com/path?id=1");
|
||||
|
||||
assertThat(flashMap.getTargetRequestPath()).isEqualTo("/path");
|
||||
assertThat(flashMap.getTargetRequestParams().toSingleValueMap()).isEqualTo(model);
|
||||
assertThat(flashMap.getTargetRequestParams().asSingleValueMap()).isEqualTo(model);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user