Resolve collisions in composite collections

Before this commit, creating a CompositeMap from two maps with the same
key has strange results, such as entrySet returning duplicate entries
with the same key.

After this commit, we give precedence to the first map by filtering out
all entries in the second map that are also mapped by the first map.

See gh-32245
This commit is contained in:
Arjen Poutsma
2024-05-02 10:48:48 +02:00
parent d5664ba01a
commit 3897ea78bb
11 changed files with 717 additions and 1 deletions

View File

@@ -23,6 +23,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -218,4 +219,85 @@ class CompositeMapTests {
Set<Map.Entry<String, String>> entries = composite.entrySet();
assertThat(entries).containsExactly(entry("foo", "bar"), entry("baz", "qux"));
}
@Nested
class CollisionTests {
@Test
void size() {
Map<String, String> first = Map.of("foo", "bar", "baz", "qux");
Map<String, String> second = Map.of("baz", "quux", "corge", "grault");
CompositeMap<String, String> composite = new CompositeMap<>(first, second);
assertThat(composite).hasSize(3);
}
@Test
void containsValue() {
Map<String, String> first = Map.of("foo", "bar", "baz", "qux");
Map<String, String> second = Map.of("baz", "quux", "corge", "grault");
CompositeMap<String, String> composite = new CompositeMap<>(first, second);
assertThat(composite.containsValue("bar")).isTrue();
assertThat(composite.containsValue("qux")).isTrue();
assertThat(composite.containsValue("quux")).isFalse();
assertThat(composite.containsValue("grault")).isTrue();
}
@Test
void get() {
Map<String, String> first = Map.of("foo", "bar", "baz", "qux");
Map<String, String> second = Map.of("baz", "quux", "corge", "grault");
CompositeMap<String, String> composite = new CompositeMap<>(first, second);
assertThat(composite.get("foo")).isEqualTo("bar");
assertThat(composite.get("baz")).isEqualTo("qux");
assertThat(composite.get("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("baz", "quux", "corge", "grault"));
CompositeMap<String, String> composite = new CompositeMap<>(first, second);
assertThat(composite.remove("baz")).isEqualTo("qux");
assertThat(composite.containsKey("baz")).isFalse();
assertThat(first).containsExactly(entry("foo", "bar"));
assertThat(second).containsExactly(entry("corge", "grault"));
}
@Test
void keySet() {
Map<String, String> first = Map.of("foo", "bar", "baz", "qux");
Map<String, String> second = Map.of("baz", "quux", "corge", "grault");
CompositeMap<String, String> composite = new CompositeMap<>(first, second);
Set<String> keySet = composite.keySet();
assertThat(keySet).containsExactlyInAnyOrder("foo", "baz", "corge");
}
@Test
void values() {
Map<String, String> first = Map.of("foo", "bar", "baz", "qux");
Map<String, String> second = Map.of("baz", "quux", "corge", "grault");
CompositeMap<String, String> composite = new CompositeMap<>(first, second);
Collection<String> values = composite.values();
assertThat(values).containsExactlyInAnyOrder("bar", "qux", "grault");
}
@Test
void entrySet() {
Map<String, String> first = Map.of("foo", "bar", "baz", "qux");
Map<String, String> second = Map.of("baz", "quux", "corge", "grault");
CompositeMap<String, String> composite = new CompositeMap<>(first, second);
Set<Map.Entry<String, String>> entries = composite.entrySet();
assertThat(entries).containsExactlyInAnyOrder(entry("foo", "bar"), entry("baz", "qux"), entry("corge", "grault"));
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.List;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Arjen Poutsma
*/
class FilteredCollectionTests {
@Test
void size() {
List<String> list = List.of("foo", "bar", "baz");
FilteredCollection<String> filtered = new FilteredCollection<>(list, s -> !s.equals("bar"));
assertThat(filtered).hasSize(2);
}
@Test
void iterator() {
List<String> list = List.of("foo", "bar", "baz");
FilteredCollection<String> filtered = new FilteredCollection<>(list, s -> !s.equals("bar"));
assertThat(filtered.iterator()).toIterable().containsExactly("foo", "baz");
}
@Test
void add() {
List<String> list = new ArrayList<>(List.of("foo"));
FilteredCollection<String> filtered = new FilteredCollection<>(list, s -> !s.equals("bar"));
boolean added = filtered.add("bar");
assertThat(added).isFalse();
assertThat(filtered).containsExactly("foo");
assertThat(list).containsExactly("foo", "bar");
}
@Test
void remove() {
List<String> list = new ArrayList<>(List.of("foo", "bar"));
FilteredCollection<String> filtered = new FilteredCollection<>(list, s -> !s.equals("bar"));
assertThat(list).contains("bar");
assertThat(filtered).doesNotContain("bar");
boolean removed = filtered.remove("bar");
assertThat(removed).isFalse();
assertThat(filtered).doesNotContain("bar");
assertThat(list).doesNotContain("bar");
}
@Test
void contains() {
List<String> list = List.of("foo", "bar", "baz");
FilteredCollection<String> filtered = new FilteredCollection<>(list, s -> !s.equals("bar"));
boolean contained = filtered.contains("bar");
assertThat(contained).isFalse();
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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.List;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Arjen Poutsma
*/
final class FilteredIteratorTests {
@Test
void filter() {
List<String> list = List.of("foo", "bar", "baz");
FilteredIterator<String> filtered = new FilteredIterator<>(list.iterator(), s -> !s.equals("bar"));
assertThat(filtered).toIterable().containsExactly("foo", "baz");
}
}

View File

@@ -0,0 +1,103 @@
/*
* 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.HashMap;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.Test;
import static java.util.Map.entry;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Arjen Poutsma
*/
class FilteredMapTests {
@Test
void size() {
Map<String, String> map = Map.of("foo", "bar", "baz", "qux", "quux", "corge");
FilteredMap<String, String> filtered = new FilteredMap<>(map, s -> !s.equals("baz"));
assertThat(filtered).hasSize(2);
}
@Test
void entrySet() {
Map<String, String> map = Map.of("foo", "bar", "baz", "qux", "quux", "corge");
FilteredMap<String, String> filtered = new FilteredMap<>(map, s -> !s.equals("baz"));
assertThat(filtered.entrySet()).containsExactlyInAnyOrder(entry("foo", "bar"), entry("quux", "corge"));
}
@Test
void containsKey() {
Map<String, String> map = Map.of("foo", "bar", "baz", "qux", "quux", "corge");
FilteredMap<String, String> filtered = new FilteredMap<>(map, s -> !s.equals("baz"));
boolean contained = filtered.containsKey("baz");
assertThat(contained).isFalse();
}
@Test
void get() {
Map<String, String> map = Map.of("foo", "bar", "baz", "qux", "quux", "corge");
FilteredMap<String, String> filtered = new FilteredMap<>(map, s -> !s.equals("baz"));
String value = filtered.get("baz");
assertThat(value).isNull();
}
@Test
void put() {
Map<String, String> map = new HashMap<>(Map.of("foo", "bar", "quux", "corge"));
FilteredMap<String, String> filtered = new FilteredMap<>(map, s -> !s.equals("baz"));
String value = filtered.put("baz", "qux");
assertThat(value).isNull();
assertThat(filtered.containsKey("baz")).isFalse();
assertThat(map.get("baz")).isEqualTo("qux");
// overwrite
value = filtered.put("baz", "QUX");
assertThat(value).isNull();
assertThat(filtered.containsKey("baz")).isFalse();
assertThat(map.get("baz")).isEqualTo("QUX");
}
@Test
void remove() {
Map<String, String> map = new HashMap<>(Map.of("foo", "bar", "baz", "qux", "quux", "corge"));
FilteredMap<String, String> filtered = new FilteredMap<>(map, s -> !s.equals("baz"));
String value = filtered.remove("baz");
assertThat(value).isNull();
assertThat(filtered.containsKey("baz")).isFalse();
assertThat(map.containsKey("baz")).isFalse();
}
@Test
void keySet() {
Map<String, String> map = Map.of("foo", "bar", "baz", "qux", "quux", "corge");
FilteredMap<String, String> filtered = new FilteredMap<>(map, s -> !s.equals("baz"));
Set<String> keySet = filtered.keySet();
assertThat(keySet).containsExactlyInAnyOrder("foo", "quux");
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.Set;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Arjen Poutsma
*/
class FilteredSetTests {
@Test
void testEquals() {
Set<String> set = Set.of("foo", "bar", "baz");
FilteredSet<String> filtered = new FilteredSet<>(set, s -> !s.equals("bar"));
Set<String> expected = Set.of("foo", "baz");
assertThat(filtered.equals(expected)).isTrue();
assertThat(filtered.equals(set)).isFalse();
assertThat(filtered.equals(Collections.emptySet())).isFalse();
}
}