From 7e26e11295547efad14229111307091d49f541a8 Mon Sep 17 00:00:00 2001 From: JJ Brown Date: Sun, 12 Apr 2020 23:45:52 -0500 Subject: [PATCH] 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. This implements the change proposed in issue hamcrest/JavaHamcrest#172 --- .../hamcrest/collection/IsMapContaining.java | 65 ++++++++++++++++++- .../collection/IsMapContainingKeyTest.java | 6 +- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/hamcrest/src/main/java/org/hamcrest/collection/IsMapContaining.java b/hamcrest/src/main/java/org/hamcrest/collection/IsMapContaining.java index 4ed45392..f4f79f3e 100644 --- a/hamcrest/src/main/java/org/hamcrest/collection/IsMapContaining.java +++ b/hamcrest/src/main/java/org/hamcrest/collection/IsMapContaining.java @@ -72,9 +72,32 @@ public static Matcher> hasEntry(Matcher Matcher> 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. + *

+ * It preserves the same descriptors. + */ + private static class IsMapContainingEntry extends IsMapContaining + { + private final K key; + + public IsMapContainingEntry(K key, V value) + { + super(equalTo(key), equalTo(value)); + this.key = key; + } + + @Override + public boolean matchesSafely(Map 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. @@ -98,7 +121,43 @@ public static Matcher> hasEntry(K key, V valu * the key that satisfying maps must contain */ public static Matcher> 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. + *

+ * It preserves the same descriptors. + */ + private static class IsMapContainingKey extends IsMapContaining + { + private final K key; + + public IsMapContainingKey(K key) + { + super(equalTo(key), anything()); + this.key = key; + } + + @Override + public boolean matchesSafely(Map map) + { + return map.containsKey(key); + } + + @Override + public void describeMismatchSafely(Map 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); + } } /** diff --git a/hamcrest/src/test/java/org/hamcrest/collection/IsMapContainingKeyTest.java b/hamcrest/src/test/java/org/hamcrest/collection/IsMapContainingKeyTest.java index 13f067c8..66876d0d 100644 --- a/hamcrest/src/test/java/org/hamcrest/collection/IsMapContainingKeyTest.java +++ b/hamcrest/src/test/java/org/hamcrest/collection/IsMapContainingKeyTest.java @@ -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()); + assertMismatchDescription("map keys were []", hasKey("Foo"), new HashMap()); } public void testDoesNotMatchMapMissingKey() { @@ -79,6 +79,6 @@ public void testDoesNotMatchMapMissingKey() { map.put("b", 2); map.put("c", 3); - assertMismatchDescription("map was [, , ]", hasKey("d"), map); + assertMismatchDescription("map keys were [\"a\", \"b\", \"c\"]", hasKey("d"), map); } }