Add immutable MultiValueMap wrapper
This commit introduces UnmodifiableMultiValueMap, an immutable wrapper around a MultiValueMap, similar to what is returned by Collections.unmodifiable*. CollectionUtils::unmodifiableMultiValueMap now returns UnmodifiableMultiValueMap. Closes gh-27608
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.assertj.core.api.ThrowableTypeAssert;
|
||||
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;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
class UnmodifiableMultiValueMapTests {
|
||||
|
||||
@Test
|
||||
void delegation() {
|
||||
MultiValueMap<String, String> mock = mock(MultiValueMap.class);
|
||||
UnmodifiableMultiValueMap<String, String> map = new UnmodifiableMultiValueMap<>(mock);
|
||||
|
||||
given(mock.size()).willReturn(1);
|
||||
assertThat(map.size()).isEqualTo(1);
|
||||
|
||||
given(mock.isEmpty()).willReturn(false);
|
||||
assertThat(map.isEmpty()).isFalse();
|
||||
|
||||
given(mock.containsKey("foo")).willReturn(true);
|
||||
assertThat(map.containsKey("foo")).isTrue();
|
||||
|
||||
given(mock.containsValue(List.of("bar"))).willReturn(true);
|
||||
assertThat(map.containsValue(List.of("bar"))).isTrue();
|
||||
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add("bar");
|
||||
given(mock.get("foo")).willReturn(list);
|
||||
List<String> result = map.get("foo");
|
||||
assertThat(result).isNotNull().containsExactly("bar");
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> result.add("baz"));
|
||||
|
||||
given(mock.getOrDefault("foo", List.of("bar"))).willReturn(List.of("baz"));
|
||||
assertThat(map.getOrDefault("foo", List.of("bar"))).containsExactly("baz");
|
||||
|
||||
given(mock.toSingleValueMap()).willReturn(Map.of("foo", "bar"));
|
||||
assertThat(map.toSingleValueMap()).containsExactly(entry("foo", "bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void unsupported() {
|
||||
UnmodifiableMultiValueMap<String, String> map = new UnmodifiableMultiValueMap<>(new LinkedMultiValueMap<>());
|
||||
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> map.put("foo", List.of("bar")));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> map.putIfAbsent("foo", List.of("bar")));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> map.putAll(Map.of("foo", List.of("bar"))));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> map.remove("foo"));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> map.add("foo", "bar"));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> map.addAll("foo", List.of("bar")));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> map.addAll(new LinkedMultiValueMap<>()));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> map.addIfAbsent("foo", "baz"));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> map.set("foo", "baz"));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> map.setAll(Map.of("foo", "baz")));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> map.replaceAll((s, strings) -> strings));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> map.remove("foo", List.of("bar")));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> map.replace("foo", List.of("bar")));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> map.replace("foo", List.of("bar"), List.of("baz")));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> map.computeIfAbsent("foo", s -> List.of("bar")));
|
||||
assertThatUnsupportedOperationException().isThrownBy(
|
||||
() -> map.computeIfPresent("foo", (s1, s2) -> List.of("bar")));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> map.compute("foo", (s1, s2) -> List.of("bar")));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> map.merge("foo", List.of("bar"), (s1, s2) -> s1));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> map.clear());
|
||||
}
|
||||
|
||||
@Test
|
||||
void entrySetDelegation() {
|
||||
MultiValueMap<String, String> mockMap = mock(MultiValueMap.class);
|
||||
Set<Map.Entry<String, List<String>>> mockSet = mock(Set.class);
|
||||
given(mockMap.entrySet()).willReturn(mockSet);
|
||||
Set<Map.Entry<String, List<String>>> set = new UnmodifiableMultiValueMap<>(mockMap).entrySet();
|
||||
|
||||
given(mockSet.size()).willReturn(1);
|
||||
assertThat(set.size()).isEqualTo(1);
|
||||
|
||||
given(mockSet.isEmpty()).willReturn(false);
|
||||
assertThat(set.isEmpty()).isFalse();
|
||||
|
||||
given(mockSet.contains("foo")).willReturn(true);
|
||||
assertThat(set.contains("foo")).isTrue();
|
||||
|
||||
List<Map.Entry<String, List<String>>> mockEntries = List.of(mock(Map.Entry.class));
|
||||
given(mockSet.containsAll(mockEntries)).willReturn(true);
|
||||
assertThat(set.containsAll(mockEntries)).isTrue();
|
||||
|
||||
Iterator<Map.Entry<String, List<String>>> mockIterator = mock(Iterator.class);
|
||||
given(mockSet.iterator()).willReturn(mockIterator);
|
||||
given(mockIterator.hasNext()).willReturn(false);
|
||||
assertThat(set.iterator()).isExhausted();
|
||||
}
|
||||
|
||||
@Test
|
||||
void entrySetUnsupported() {
|
||||
Set<Map.Entry<String, List<String>>> set = new UnmodifiableMultiValueMap<String, String>(new LinkedMultiValueMap<>()).entrySet();
|
||||
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> set.add(mock(Map.Entry.class)));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> set.remove("foo"));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> set.removeIf(e -> true));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> set.addAll(mock(List.class)));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> set.retainAll(mock(List.class)));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> set.removeAll(mock(List.class)));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> set.clear());
|
||||
}
|
||||
|
||||
@Test
|
||||
void valuesDelegation() {
|
||||
MultiValueMap<String, String> mockMap = mock(MultiValueMap.class);
|
||||
Collection<List<String>> mockValues = mock(Collection.class);
|
||||
given(mockMap.values()).willReturn(mockValues);
|
||||
Collection<List<String>> values = new UnmodifiableMultiValueMap<>(mockMap).values();
|
||||
|
||||
given(mockValues.size()).willReturn(1);
|
||||
assertThat(values.size()).isEqualTo(1);
|
||||
|
||||
given(mockValues.isEmpty()).willReturn(false);
|
||||
assertThat(values.isEmpty()).isFalse();
|
||||
|
||||
given(mockValues.contains(List.of("foo"))).willReturn(true);
|
||||
assertThat(mockValues.contains(List.of("foo"))).isTrue();
|
||||
|
||||
given(mockValues.containsAll(List.of(List.of("foo")))).willReturn(true);
|
||||
assertThat(mockValues.containsAll(List.of(List.of("foo")))).isTrue();
|
||||
|
||||
Iterator<List<String>> mockIterator = mock(Iterator.class);
|
||||
given(mockValues.iterator()).willReturn(mockIterator);
|
||||
given(mockIterator.hasNext()).willReturn(false);
|
||||
assertThat(values.iterator()).isExhausted();
|
||||
}
|
||||
|
||||
@Test
|
||||
void valuesUnsupported() {
|
||||
Collection<List<String>> values =
|
||||
new UnmodifiableMultiValueMap<String, String>(new LinkedMultiValueMap<>()).values();
|
||||
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> values.add(List.of("foo")));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> values.remove(List.of("foo")));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> values.addAll(List.of(List.of("foo"))));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> values.removeAll(List.of(List.of("foo"))));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> values.retainAll(List.of(List.of("foo"))));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> values.removeIf(s -> true));
|
||||
assertThatUnsupportedOperationException().isThrownBy(() -> values.clear());
|
||||
}
|
||||
|
||||
private static ThrowableTypeAssert<UnsupportedOperationException> assertThatUnsupportedOperationException() {
|
||||
return assertThatExceptionOfType(UnsupportedOperationException.class);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user