From 9f919ccf66a2481cf09cfcffc5434824aff472f7 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 29 May 2020 15:48:19 +0200 Subject: [PATCH] Consistent MultiValueMap behavior for empty list values This commit extracts MultiValueMapAdapter from CollectionUtils and reuses its implementation as base class of LinkedMultiValueMap. Closes gh-25140 --- .../springframework/util/CollectionUtils.java | 164 ++-------------- .../util/LinkedCaseInsensitiveMap.java | 6 +- .../util/LinkedMultiValueMap.java | 153 +-------------- .../util/MultiValueMapAdapter.java | 178 ++++++++++++++++++ 4 files changed, 202 insertions(+), 299 deletions(-) create mode 100644 spring-core/src/main/java/org/springframework/util/MultiValueMapAdapter.java diff --git a/spring-core/src/main/java/org/springframework/util/CollectionUtils.java b/spring-core/src/main/java/org/springframework/util/CollectionUtils.java index 9e739ac358b8..309a129c2482 100644 --- a/spring-core/src/main/java/org/springframework/util/CollectionUtils.java +++ b/spring-core/src/main/java/org/springframework/util/CollectionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -16,7 +16,6 @@ package org.springframework.util; -import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -24,7 +23,6 @@ import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; @@ -379,25 +377,28 @@ public static Iterator toIterator(@Nullable Enumeration enumeration) { /** * Adapt a {@code Map>} to an {@code MultiValueMap}. - * @param map the original map - * @return the multi-value map + * @param targetMap the original map + * @return the adapted multi-value map (wrapping the original map) * @since 3.1 */ - public static MultiValueMap toMultiValueMap(Map> map) { - return new MultiValueMapAdapter<>(map); + public static MultiValueMap toMultiValueMap(Map> targetMap) { + Assert.notNull(targetMap, "'targetMap' must not be null"); + return new MultiValueMapAdapter<>(targetMap); } /** * Return an unmodifiable view of the specified multi-value map. - * @param map the map for which an unmodifiable view is to be returned. - * @return an unmodifiable view of the specified multi-value map. + * @param targetMap the map for which an unmodifiable view is to be returned. + * @return an unmodifiable view of the specified multi-value map * @since 3.1 */ @SuppressWarnings("unchecked") - public static MultiValueMap unmodifiableMultiValueMap(MultiValueMap map) { - Assert.notNull(map, "'map' must not be null"); - Map> result = new LinkedHashMap<>(map.size()); - map.forEach((key, value) -> { + public static MultiValueMap unmodifiableMultiValueMap( + MultiValueMap targetMap) { + + Assert.notNull(targetMap, "'targetMap' must not be null"); + Map> result = new LinkedHashMap<>(targetMap.size()); + targetMap.forEach((key, value) -> { List values = Collections.unmodifiableList(value); result.put(key, (List) values); }); @@ -434,141 +435,4 @@ public void remove() throws UnsupportedOperationException { } - /** - * Adapts a Map to the MultiValueMap contract. - */ - @SuppressWarnings("serial") - private static class MultiValueMapAdapter implements MultiValueMap, Serializable { - - private final Map> map; - - public MultiValueMapAdapter(Map> map) { - Assert.notNull(map, "'map' must not be null"); - this.map = map; - } - - @Override - @Nullable - public V getFirst(K key) { - List values = this.map.get(key); - return (values != null ? values.get(0) : null); - } - - @Override - public void add(K key, @Nullable V value) { - List values = this.map.computeIfAbsent(key, k -> new LinkedList<>()); - values.add(value); - } - - @Override - public void addAll(K key, List values) { - List currentValues = this.map.computeIfAbsent(key, k -> new LinkedList<>()); - currentValues.addAll(values); - } - - @Override - public void addAll(MultiValueMap values) { - for (Entry> entry : values.entrySet()) { - addAll(entry.getKey(), entry.getValue()); - } - } - - @Override - public void set(K key, @Nullable V value) { - List values = new LinkedList<>(); - values.add(value); - this.map.put(key, values); - } - - @Override - public void setAll(Map values) { - values.forEach(this::set); - } - - @Override - public Map toSingleValueMap() { - LinkedHashMap singleValueMap = new LinkedHashMap<>(this.map.size()); - this.map.forEach((key, value) -> singleValueMap.put(key, value.get(0))); - return singleValueMap; - } - - @Override - public int size() { - return this.map.size(); - } - - @Override - public boolean isEmpty() { - return this.map.isEmpty(); - } - - @Override - public boolean containsKey(Object key) { - return this.map.containsKey(key); - } - - @Override - public boolean containsValue(Object value) { - return this.map.containsValue(value); - } - - @Override - public List get(Object key) { - return this.map.get(key); - } - - @Override - public List put(K key, List value) { - return this.map.put(key, value); - } - - @Override - public List remove(Object key) { - return this.map.remove(key); - } - - @Override - public void putAll(Map> map) { - this.map.putAll(map); - } - - @Override - public void clear() { - this.map.clear(); - } - - @Override - public Set keySet() { - return this.map.keySet(); - } - - @Override - public Collection> values() { - return this.map.values(); - } - - @Override - public Set>> entrySet() { - return this.map.entrySet(); - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - return this.map.equals(other); - } - - @Override - public int hashCode() { - return this.map.hashCode(); - } - - @Override - public String toString() { - return this.map.toString(); - } - } - } diff --git a/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java b/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java index 1b70fa555093..a7e24a762070 100644 --- a/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java +++ b/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -273,8 +273,8 @@ public LinkedCaseInsensitiveMap clone() { } @Override - public boolean equals(@Nullable Object obj) { - return this.targetMap.equals(obj); + public boolean equals(@Nullable Object other) { + return (this == other || this.targetMap.equals(other)); } @Override diff --git a/spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java b/spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java index fa3ed112d46d..00ef6b4d6bd6 100644 --- a/spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java +++ b/spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -17,14 +17,10 @@ package org.springframework.util; import java.io.Serializable; -import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Set; - -import org.springframework.lang.Nullable; /** * Simple implementation of {@link MultiValueMap} that wraps a {@link LinkedHashMap}, @@ -39,18 +35,16 @@ * @param the key type * @param the value element type */ -public class LinkedMultiValueMap implements MultiValueMap, Serializable, Cloneable { +public class LinkedMultiValueMap extends MultiValueMapAdapter implements Serializable, Cloneable { private static final long serialVersionUID = 3801124242820219131L; - private final Map> targetMap; - /** * Create a new LinkedMultiValueMap that wraps a {@link LinkedHashMap}. */ public LinkedMultiValueMap() { - this.targetMap = new LinkedHashMap<>(); + super(new LinkedHashMap<>()); } /** @@ -59,7 +53,7 @@ public LinkedMultiValueMap() { * @param initialCapacity the initial capacity */ public LinkedMultiValueMap(int initialCapacity) { - this.targetMap = new LinkedHashMap<>(initialCapacity); + super(new LinkedHashMap<>(initialCapacity)); } /** @@ -71,125 +65,7 @@ public LinkedMultiValueMap(int initialCapacity) { * @see #deepCopy() */ public LinkedMultiValueMap(Map> otherMap) { - this.targetMap = new LinkedHashMap<>(otherMap); - } - - - // MultiValueMap implementation - - @Override - @Nullable - public V getFirst(K key) { - List values = this.targetMap.get(key); - return (values != null && !values.isEmpty() ? values.get(0) : null); - } - - @Override - public void add(K key, @Nullable V value) { - List values = this.targetMap.computeIfAbsent(key, k -> new LinkedList<>()); - values.add(value); - } - - @Override - public void addAll(K key, List values) { - List currentValues = this.targetMap.computeIfAbsent(key, k -> new LinkedList<>()); - currentValues.addAll(values); - } - - @Override - public void addAll(MultiValueMap values) { - for (Entry> entry : values.entrySet()) { - addAll(entry.getKey(), entry.getValue()); - } - } - - @Override - public void set(K key, @Nullable V value) { - List values = new LinkedList<>(); - values.add(value); - this.targetMap.put(key, values); - } - - @Override - public void setAll(Map values) { - values.forEach(this::set); - } - - @Override - public Map toSingleValueMap() { - LinkedHashMap singleValueMap = new LinkedHashMap<>(this.targetMap.size()); - this.targetMap.forEach((key, values) -> { - if (values != null && !values.isEmpty()) { - singleValueMap.put(key, values.get(0)); - } - }); - return singleValueMap; - } - - - // 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(Object value) { - return this.targetMap.containsValue(value); - } - - @Override - @Nullable - public List get(Object key) { - return this.targetMap.get(key); - } - - @Override - @Nullable - public List put(K key, List value) { - return this.targetMap.put(key, value); - } - - @Override - @Nullable - public List remove(Object key) { - return this.targetMap.remove(key); - } - - @Override - public void putAll(Map> map) { - this.targetMap.putAll(map); - } - - @Override - public void clear() { - this.targetMap.clear(); - } - - @Override - public Set keySet() { - return this.targetMap.keySet(); - } - - @Override - public Collection> values() { - return this.targetMap.values(); - } - - @Override - public Set>> entrySet() { - return this.targetMap.entrySet(); + super(new LinkedHashMap<>(otherMap)); } @@ -203,8 +79,8 @@ public Set>> entrySet() { * @see #clone() */ public LinkedMultiValueMap deepCopy() { - LinkedMultiValueMap copy = new LinkedMultiValueMap<>(this.targetMap.size()); - this.targetMap.forEach((key, value) -> copy.put(key, new LinkedList<>(value))); + LinkedMultiValueMap copy = new LinkedMultiValueMap<>(size()); + forEach((key, values) -> copy.put(key, new LinkedList<>(values))); return copy; } @@ -224,19 +100,4 @@ public LinkedMultiValueMap clone() { return new LinkedMultiValueMap<>(this); } - @Override - public boolean equals(Object obj) { - return this.targetMap.equals(obj); - } - - @Override - public int hashCode() { - return this.targetMap.hashCode(); - } - - @Override - public String toString() { - return this.targetMap.toString(); - } - } diff --git a/spring-core/src/main/java/org/springframework/util/MultiValueMapAdapter.java b/spring-core/src/main/java/org/springframework/util/MultiValueMapAdapter.java new file mode 100644 index 000000000000..cfbf79cb3b7b --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/MultiValueMapAdapter.java @@ -0,0 +1,178 @@ +/* + * Copyright 2002-2020 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.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.lang.Nullable; + +/** + * Adapts a given {@link Map} to the {@link MultiValueMap} contract. + * + * @author Arjen Poutsma + * @author Juergen Hoeller + * @since 3.1 + * @param the key type + * @param the value element type + * @see CollectionUtils#toMultiValueMap + * @see LinkedMultiValueMap + */ +@SuppressWarnings("serial") +class MultiValueMapAdapter implements MultiValueMap, Serializable { + + private final Map> targetMap; + + + MultiValueMapAdapter(Map> targetMap) { + this.targetMap = targetMap; + } + + + @Override + @Nullable + public V getFirst(K key) { + List values = this.targetMap.get(key); + return (values != null && !values.isEmpty() ? values.get(0) : null); + } + + @Override + public void add(K key, @Nullable V value) { + List values = this.targetMap.computeIfAbsent(key, k -> new LinkedList<>()); + values.add(value); + } + + @Override + public void addAll(K key, List values) { + List currentValues = this.targetMap.computeIfAbsent(key, k -> new LinkedList<>()); + currentValues.addAll(values); + } + + @Override + public void addAll(MultiValueMap values) { + for (Entry> entry : values.entrySet()) { + addAll(entry.getKey(), entry.getValue()); + } + } + + @Override + public void set(K key, @Nullable V value) { + List values = new LinkedList<>(); + values.add(value); + this.targetMap.put(key, values); + } + + @Override + public void setAll(Map values) { + values.forEach(this::set); + } + + @Override + public Map toSingleValueMap() { + Map singleValueMap = new LinkedHashMap<>(this.targetMap.size()); + this.targetMap.forEach((key, values) -> { + if (values != null && !values.isEmpty()) { + singleValueMap.put(key, values.get(0)); + } + }); + return singleValueMap; + } + + @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(Object value) { + return this.targetMap.containsValue(value); + } + + @Override + @Nullable + public List get(Object key) { + return this.targetMap.get(key); + } + + @Override + @Nullable + public List put(K key, List value) { + return this.targetMap.put(key, value); + } + + @Override + @Nullable + public List remove(Object key) { + return this.targetMap.remove(key); + } + + @Override + public void putAll(Map> map) { + this.targetMap.putAll(map); + } + + @Override + public void clear() { + this.targetMap.clear(); + } + + @Override + public Set keySet() { + return this.targetMap.keySet(); + } + + @Override + public Collection> values() { + return this.targetMap.values(); + } + + @Override + public Set>> entrySet() { + return this.targetMap.entrySet(); + } + + @Override + public boolean equals(@Nullable Object other) { + return (this == other || this.targetMap.equals(other)); + } + + @Override + public int hashCode() { + return this.targetMap.hashCode(); + } + + @Override + public String toString() { + return this.targetMap.toString(); + } + +}