Skip to content

Commit 9a2975b

Browse files
author
smgfreeman
committed
Issue 59. IsIterableContainingInOrder matches the elements of a collection exactly but -InAnyOrder does not
Also improved mismatch reporting.
1 parent 7698752 commit 9a2975b

File tree

4 files changed

+97
-54
lines changed

4 files changed

+97
-54
lines changed

hamcrest-library/src/main/java/org/hamcrest/collection/IsIterableContainingInAnyOrder.java

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,55 +5,85 @@
55
import java.util.ArrayList;
66
import java.util.Arrays;
77
import java.util.Collection;
8-
import java.util.Iterator;
98
import java.util.List;
109

1110
import org.hamcrest.Description;
1211
import org.hamcrest.Factory;
1312
import org.hamcrest.Matcher;
14-
import org.hamcrest.TypeSafeMatcher;
13+
import org.hamcrest.TypeSafeDiagnosingMatcher;
1514

16-
public class IsIterableContainingInAnyOrder<T> extends TypeSafeMatcher<Iterable<T>> {
15+
public class IsIterableContainingInAnyOrder<T> extends TypeSafeDiagnosingMatcher<Iterable<T>> {
1716
private final Collection<Matcher<? super T>> matchers;
1817

1918
public IsIterableContainingInAnyOrder(Collection<Matcher<? super T>> matchers) {
2019
this.matchers = matchers;
2120
}
22-
21+
2322
@Override
24-
public boolean matchesSafely(Iterable<T> iterable) {
25-
Iterator<T> items = iterable.iterator();
26-
List<Matcher<? super T>> currentMatchers = copyOfMatchers();
27-
while (items.hasNext() && !currentMatchers.isEmpty()) {
28-
removeFirstMatcherThatMatchesItem(currentMatchers, items.next());
23+
protected boolean matchesSafely(Iterable<T> items, Description mismatchDescription) {
24+
Matching<T> matching = new Matching<T>(matchers, mismatchDescription);
25+
for (T item : items) {
26+
if (! matching.matches(item)) {
27+
return false;
2928
}
30-
return currentMatchers.isEmpty() && !items.hasNext();
29+
}
30+
31+
return matching.isFinished(items);
32+
}
33+
34+
public void describeTo(Description description) {
35+
description.appendText("iterable over ")
36+
.appendList("[", ", ", "]", matchers)
37+
.appendText(" in any order");
3138
}
3239

33-
private void removeFirstMatcherThatMatchesItem(List<Matcher<? super T>> currentMatchers, T item) {
34-
Iterator<Matcher<? super T>> availableMatchers = currentMatchers.iterator();
35-
while (availableMatchers.hasNext()) {
36-
Matcher<? super T> matcher = availableMatchers.next();
40+
private static class Matching<S> {
41+
private final Collection<Matcher<? super S>> matchers;
42+
private final Description mismatchDescription;
43+
44+
public Matching(Collection<Matcher<? super S>> matchers, Description mismatchDescription) {
45+
this.matchers = new ArrayList<Matcher<? super S>>(matchers);;
46+
this.mismatchDescription = mismatchDescription;
47+
}
48+
49+
public boolean matches(S item) {
50+
return isNotSurplus(item) && isMatched(item);
51+
}
52+
53+
public boolean isFinished(Iterable<S> items) {
54+
if (matchers.isEmpty()) {
55+
return true;
56+
}
57+
mismatchDescription
58+
.appendText("No item matches: ").appendList("", ", ", "", matchers)
59+
.appendText(" in ").appendValueList("[", ", ", "]", items);
60+
return false;
61+
}
62+
63+
private boolean isNotSurplus(S item) {
64+
if (matchers.isEmpty()) {
65+
mismatchDescription.appendText("Not matched: ").appendValue(item);
66+
return false;
67+
}
68+
return true;
69+
}
70+
71+
private boolean isMatched(S item) {
72+
for (Matcher<? super S> matcher : matchers) {
3773
if (matcher.matches(item)) {
38-
availableMatchers.remove();
39-
break;
74+
matchers.remove(matcher);
75+
return true;
4076
}
77+
}
78+
mismatchDescription.appendText("Not matched: ").appendValue(item);
79+
return false;
4180
}
42-
}
4381

44-
@Override
45-
public void describeMismatchSafely(Iterable<T> actual, Description mismatchDescription) {
46-
mismatchDescription.appendText("iterable was ").appendValueList("[", ", ", "]", actual);
47-
}
48-
49-
private List<Matcher<? super T>> copyOfMatchers() {
50-
return new ArrayList<Matcher<? super T>>(matchers);
5182
}
5283

53-
public void describeTo(Description description) {
54-
description.appendText("iterable over ")
55-
.appendList("[", ", ", "]", matchers)
56-
.appendText(" in any order");
84+
@Factory
85+
public static <E> Matcher<Iterable<E>> containsInAnyOrder(final Matcher<E> item) {
86+
return containsInAnyOrder(new ArrayList<Matcher<? super E>>() {{ add(item); }});
5787
}
5888

5989
@Factory
@@ -175,3 +205,4 @@ public static <T> Matcher<Iterable<T>> containsInAnyOrder(Collection<Matcher<? s
175205
return new IsIterableContainingInAnyOrder<T>(matchers);
176206
}
177207
}
208+

hamcrest-library/src/main/java/org/hamcrest/collection/IsIterableContainingInOrder.java

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ public IsIterableContainingInOrder(List<Matcher<? super E>> matchers) {
2020

2121
@Override
2222
protected boolean matchesSafely(Iterable<E> iterable, Description mismatchDescription) {
23-
MatchSeries<E> matching = new MatchSeries<E>(matchers, mismatchDescription);
23+
MatchSeries<E> matchSeries = new MatchSeries<E>(matchers, mismatchDescription);
2424
for (E item : iterable) {
25-
if (! matching.matches(item)) {
25+
if (! matchSeries.matches(item)) {
2626
return false;
2727
}
2828
}
2929

30-
return matching.isFinished();
30+
return matchSeries.isFinished();
3131
}
3232

3333
public void describeTo(Description description) {
@@ -51,6 +51,14 @@ public boolean matches(F item) {
5151
return isNotSurplus(item) && isMatched(item);
5252
}
5353

54+
public boolean isFinished() {
55+
if (nextMatchIx < matchers.size()) {
56+
mismatchDescription.appendText("No item: ").appendDescriptionOf(matchers.get(nextMatchIx));
57+
return false;
58+
}
59+
return true;
60+
}
61+
5462
private boolean isMatched(F item) {
5563
Matcher<? super F> matcher = matchers.get(nextMatchIx);
5664
if (!matcher.matches(item)) {
@@ -63,19 +71,12 @@ private boolean isMatched(F item) {
6371

6472
private boolean isNotSurplus(F item) {
6573
if (matchers.size() <= nextMatchIx) {
66-
mismatchDescription.appendText("surplus item: ").appendValue(item);
74+
mismatchDescription.appendText("Not matched: ").appendValue(item);
6775
return false;
6876
}
6977
return true;
7078
}
7179

72-
public boolean isFinished() {
73-
if (nextMatchIx < matchers.size()) {
74-
mismatchDescription.appendText("surplus matcher: ").appendDescriptionOf(matchers.get(nextMatchIx));
75-
return false;
76-
}
77-
return true;
78-
}
7980
private void describeMismatch(Matcher<? super F> matcher, F item) {
8081
mismatchDescription.appendText("item " + nextMatchIx + ": ");
8182
matcher.describeMismatch(item, mismatchDescription);
@@ -141,6 +142,11 @@ public static <E> Matcher<Iterable<E>> contains(E first, E second, E third, E fo
141142
return contains(matchers);
142143
}
143144

145+
@Factory
146+
public static <E> Matcher<Iterable<E>> contains(final Matcher<E> item) {
147+
return contains(new ArrayList<Matcher<? super E>>() {{ add(item); }});
148+
}
149+
144150
@Factory
145151
public static <E> Matcher<Iterable<E>> contains(Matcher<? super E>... items) {
146152
return contains(Arrays.asList(items));

hamcrest-unit-test/src/main/java/org/hamcrest/collection/IsIterableContainingInAnyOrderTest.java

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
package org.hamcrest.collection;
22

3+
import static java.util.Arrays.asList;
4+
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
5+
import static org.hamcrest.collection.IsIterableContainingInOrderTest.make;
6+
import static org.hamcrest.collection.IsIterableContainingInOrderTest.value;
7+
38
import java.util.Collections;
49

510
import org.hamcrest.AbstractMatcherTest;
611
import org.hamcrest.Matcher;
712

8-
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.*;
9-
10-
import static java.util.Arrays.asList;
11-
1213
public class IsIterableContainingInAnyOrderTest extends AbstractMatcherTest {
1314

1415
@Override
@@ -21,7 +22,7 @@ public void testMatchesSingleItemIterable() {
2122
}
2223

2324
public void testDoesNotMatchEmpty() {
24-
assertMismatchDescription("iterable was []", containsInAnyOrder(1, 2), Collections.<Integer>emptyList());
25+
assertMismatchDescription("No item matches: <1>, <2> in []", containsInAnyOrder(1, 2), Collections.<Integer>emptyList());
2526
}
2627

2728
public void testMatchesIterableOutOfOrder() {
@@ -33,18 +34,24 @@ public void testMatchesIterableInOrder() {
3334
}
3435

3536
public void testDoesNotMatchIfOneOfMultipleElementsMismatches() {
36-
assertMismatchDescription("iterable was [<1>, <2>, <4>]", containsInAnyOrder(1, 2, 3), asList(1, 2, 4));
37+
assertMismatchDescription("Not matched: <4>", containsInAnyOrder(1, 2, 3), asList(1, 2, 4));
3738
}
3839

3940
public void testDoesNotMatchIfThereAreMoreElementsThanMatchers() {
40-
assertMismatchDescription("iterable was [<1>, <2>, <3>, <4>]", containsInAnyOrder(1, 2, 3), asList(1, 2, 3, 4));
41+
assertMismatchDescription("Not matched: <WithValue 2>", containsInAnyOrder(value(1), value(3)), asList(make(1), make(2), make(3)));
4142
}
4243

4344
public void testDoesNotMatchIfThereAreMoreMatchersThanElements() {
44-
assertMismatchDescription("iterable was [<1>, <2>, <3>]", containsInAnyOrder(1, 2, 3, 4), asList(1, 2, 3));
45+
assertMismatchDescription("No item matches: <4> in [<1>, <2>, <3>]", containsInAnyOrder(1, 2, 3, 4), asList(1, 2, 3));
4546
}
4647

4748
public void testHasAReadableDescription() {
4849
assertDescription("iterable over [<1>, <2>] in any order", containsInAnyOrder(1, 2));
4950
}
51+
52+
public void testDoesNotMatchIfSingleItemMismatches() throws Exception {
53+
assertMismatchDescription("Not matched: <WithValue 3>", containsInAnyOrder(value(4)), asList(make(3)));
54+
}
55+
56+
5057
}

hamcrest-unit-test/src/main/java/org/hamcrest/collection/IsIterableContainingInOrderTest.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,13 @@ public void testMatchingMultipleItemIterable() throws Exception {
2626
}
2727

2828
public void testDoesNotMatchWithMoreElementsThanExpected() throws Exception {
29-
assertMismatchDescription("surplus item: <4>", contains(1, 2, 3), asList(1, 2, 3, 4));
29+
assertMismatchDescription("Not matched: <4>", contains(1, 2, 3), asList(1, 2, 3, 4));
3030
}
3131

3232
public void testDoesNotMatchWithFewerElementsThanExpected() throws Exception {
33-
assertMismatchDescription("surplus matcher: value with <3>", contains(value(1), value(2), value(3)), asList(make(1), make(2)));
33+
assertMismatchDescription("No item: value with <3>", contains(value(1), value(2), value(3)), asList(make(1), make(2)));
3434
}
3535

36-
@SuppressWarnings("unchecked")
3736
public void testDoesNotMatchIfSingleItemMismatches() throws Exception {
3837
assertMismatchDescription("item 0: value was <3>", contains(value(4)), asList(make(3)));
3938
}
@@ -42,9 +41,8 @@ public void testDoesNotMatchIfOneOfMultipleItemsMismatch() throws Exception {
4241
assertMismatchDescription("item 2: value was <4>", contains(value(1), value(2), value(3)), asList(make(1), make(2), make(4)));
4342
}
4443

45-
@SuppressWarnings("unchecked")
4644
public void testDoesNotMatchEmptyIterable() throws Exception {
47-
assertMismatchDescription("surplus matcher: value with <4>", contains(value(4)), new ArrayList<WithValue>());
45+
assertMismatchDescription("No item: value with <4>", contains(value(4)), new ArrayList<WithValue>());
4846
}
4947

5048
public void testHasAReadableDescription() {
@@ -55,13 +53,14 @@ public static class WithValue {
5553
private final int value;
5654
public WithValue(int value) { this.value = value; }
5755
public int getValue() { return value; }
56+
@Override public String toString() { return "WithValue " + value; }
5857
}
5958

60-
private static WithValue make(int value) {
59+
public static WithValue make(int value) {
6160
return new WithValue(value);
6261
}
6362

64-
private static Matcher<WithValue> value(int value) {
63+
public static Matcher<WithValue> value(int value) {
6564
return new FeatureMatcher<WithValue, Integer>(equalTo(value), "value with", "value") {
6665
@Override protected Integer featureValueOf(WithValue actual) { return actual.getValue(); }
6766
};

0 commit comments

Comments
 (0)