Skip to content

Commit

Permalink
Provides optimized matchers for IsMapContaining.hasKey() and IsMapCon…
Browse files Browse the repository at this point in the history
…taining.hasEntry(),

which use the map's own containsKey(K key) method to avoid an O(n) worst-case linear search for every match.

This implements the change proposed in issue #172
  • Loading branch information
brownian-motion committed Apr 13, 2020
1 parent 6b880bf commit 7e26e11
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,32 @@ public static <K,V> Matcher<Map<? extends K,? extends V>> hasEntry(Matcher<? sup
* the value that, in combination with the key, must be describe at least one entry
*/
public static <K,V> Matcher<Map<? extends K,? extends V>> hasEntry(K key, V value) {
return new IsMapContaining<>(equalTo(key), equalTo(value));
return new IsMapContainingEntry<>(key, value);
}


/**
* Provides a type-safe optimization over the O(n) linear search in {@link IsMapContaining#matchesSafely(Map)},
* by leveraging the speed of the map's own {@link Map#containsKey(Object)} check.
* <p>
* It preserves the same descriptors.
*/
private static class IsMapContainingEntry<K, V> extends IsMapContaining<K, V>
{
private final K key;

public IsMapContainingEntry(K key, V value)
{
super(equalTo(key), equalTo(value));
this.key = key;
}

@Override
public boolean matchesSafely(Map<? extends K, ? extends V> map)
{
return map.containsKey(key) && super.valueMatcher.matches(map.get(key));
}
}

/**
* Creates a matcher for {@link java.util.Map}s matching when the examined {@link java.util.Map} contains
* at least one key that satisfies the specified matcher.
Expand All @@ -98,7 +121,43 @@ public static <K,V> Matcher<Map<? extends K,? extends V>> hasEntry(K key, V valu
* the key that satisfying maps must contain
*/
public static <K> Matcher<Map<? extends K, ?>> hasKey(K key) {
return new IsMapContaining<>(equalTo(key), anything());
return new IsMapContainingKey<>(key);
}

/**
* Provides a type-safe optimization over the O(n) linear search in {@link IsMapContaining#matchesSafely(Map)},
* by leveraging the speed of the map's own {@link Map#containsKey(Object)} check.
* <p>
* It preserves the same descriptors.
*/
private static class IsMapContainingKey<K, V> extends IsMapContaining<K, V>
{
private final K key;

public IsMapContainingKey(K key)
{
super(equalTo(key), anything());
this.key = key;
}

@Override
public boolean matchesSafely(Map<? extends K, ? extends V> map)
{
return map.containsKey(key);
}

@Override
public void describeMismatchSafely(Map<? extends K, ? extends V> map, Description mismatchDescription)
{
mismatchDescription.appendText("map keys were ").appendValueList("[", ", ", "]", map.keySet());
}

@Override
public void describeTo(Description description)
{
description.appendText("map containing key ")
.appendDescriptionOf(super.keyMatcher);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ public void testMatchesMapContainingKeyWithNumberKeys() throws Exception {
}

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

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

public void testDoesNotMatchMapMissingKey() {
Expand All @@ -79,6 +79,6 @@ public void testDoesNotMatchMapMissingKey() {
map.put("b", 2);
map.put("c", 3);

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

0 comments on commit 7e26e11

Please sign in to comment.