@@ -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 /**
0 commit comments