Skip to content

Commit a338551

Browse files
Provides optimized matchers for IsMapContaining.hasKey() and IsMapContaining.hasEntry(),
which use the map's own containsKey(K key) method to avoid an O(n) worst-case linear search for every match. Incorporated null-safety checks to account for maps that do not support null keys, per @nibsi 's recommendation
1 parent 8522353 commit a338551

File tree

2 files changed

+80
-6
lines changed

2 files changed

+80
-6
lines changed

hamcrest/src/main/java/org/hamcrest/collection/IsMapContaining.java

+77-3
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,39 @@ public static <K,V> Matcher<Map<? extends K,? extends V>> hasEntry(Matcher<? sup
7272
* the value that, in combination with the key, must be describe at least one entry
7373
*/
7474
public static <K,V> Matcher<Map<? extends K,? extends V>> hasEntry(K key, V value) {
75-
return new IsMapContaining<>(equalTo(key), equalTo(value));
75+
return new IsMapContainingEntry<>(key, value);
7676
}
77-
77+
78+
/**
79+
* Provides a type-safe optimization over the O(n) linear search in {@link IsMapContaining#matchesSafely(Map)},
80+
* by leveraging the speed of the map's own {@link Map#containsKey(Object)} check.
81+
* <p>
82+
* It preserves the same descriptors.
83+
*/
84+
private static class IsMapContainingEntry<K, V> extends IsMapContaining<K, V>
85+
{
86+
private final K key;
87+
88+
public IsMapContainingEntry(K key, V value)
89+
{
90+
super(equalTo(key), equalTo(value));
91+
this.key = key;
92+
}
93+
94+
@Override
95+
public boolean matchesSafely(Map<? extends K, ? extends V> map)
96+
{
97+
try{
98+
return map.containsKey(key) && super.valueMatcher.matches(map.get(key));
99+
} catch (NullPointerException e){
100+
// some maps (like Hashtable) don't want to let you check for a null key.
101+
// to be consistent with previous behavior checking each entry,
102+
// we squash any error coming from that to indicate simply that there's no entry with that key.
103+
return false;
104+
}
105+
}
106+
}
107+
78108
/**
79109
* Creates a matcher for {@link java.util.Map}s matching when the examined {@link java.util.Map} contains
80110
* at least one key that satisfies the specified matcher.
@@ -98,7 +128,51 @@ public static <K,V> Matcher<Map<? extends K,? extends V>> hasEntry(K key, V valu
98128
* the key that satisfying maps must contain
99129
*/
100130
public static <K> Matcher<Map<? extends K, ?>> hasKey(K key) {
101-
return new IsMapContaining<>(equalTo(key), anything());
131+
return new IsMapContainingKey<>(key);
132+
}
133+
134+
/**
135+
* Provides a type-safe optimization over the O(n) linear search in {@link IsMapContaining#matchesSafely(Map)},
136+
* by leveraging the speed of the map's own {@link Map#containsKey(Object)} check.
137+
* <p>
138+
* It preserves the same descriptors.
139+
*/
140+
private static class IsMapContainingKey<K, V> extends IsMapContaining<K, V>
141+
{
142+
private final K key;
143+
144+
public IsMapContainingKey(K key)
145+
{
146+
super(equalTo(key), anything());
147+
this.key = key;
148+
}
149+
150+
@Override
151+
public boolean matchesSafely(Map<? extends K, ? extends V> map)
152+
{
153+
try
154+
{
155+
return map.containsKey(key);
156+
} catch (NullPointerException e){
157+
// some maps (like Hashtable) don't want to let you check for a null key.
158+
// to be consistent with previous behavior checking each entry,
159+
// we squash any error coming from that to indicate simply that there's no entry with that key.
160+
return false;
161+
}
162+
}
163+
164+
@Override
165+
public void describeMismatchSafely(Map<? extends K, ? extends V> map, Description mismatchDescription)
166+
{
167+
mismatchDescription.appendText("map keys were ").appendValueList("[", ", ", "]", map.keySet());
168+
}
169+
170+
@Override
171+
public void describeTo(Description description)
172+
{
173+
description.appendText("map containing key ")
174+
.appendDescriptionOf(super.keyMatcher);
175+
}
102176
}
103177

104178
/**

hamcrest/src/test/java/org/hamcrest/collection/IsMapContainingKeyTest.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ public void testMatchesMapContainingKeyWithNumberKeys() throws Exception {
6666
}
6767

6868
public void testHasReadableDescription() {
69-
assertDescription("map containing [\"a\"->ANYTHING]", hasKey("a"));
69+
assertDescription("map containing key \"a\"", hasKey("a"));
7070
}
7171

7272
public void testDoesNotMatchEmptyMap() {
73-
assertMismatchDescription("map was []", hasKey("Foo"), new HashMap<String,Integer>());
73+
assertMismatchDescription("map keys were []", hasKey("Foo"), new HashMap<String,Integer>());
7474
}
7575

7676
public void testDoesNotMatchMapMissingKey() {
@@ -79,6 +79,6 @@ public void testDoesNotMatchMapMissingKey() {
7979
map.put("b", 2);
8080
map.put("c", 3);
8181

82-
assertMismatchDescription("map was [<a=1>, <b=2>, <c=3>]", hasKey("d"), map);
82+
assertMismatchDescription("map keys were [\"a\", \"b\", \"c\"]", hasKey("d"), map);
8383
}
8484
}

0 commit comments

Comments
 (0)