Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IsIterableIntersecting #338

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
56 changes: 56 additions & 0 deletions hamcrest/src/main/java/org/hamcrest/Matchers.java
Expand Up @@ -1712,5 +1712,61 @@ public static org.hamcrest.Matcher<org.w3c.dom.Node> hasXPath(java.lang.String x
return org.hamcrest.xml.HasXPath.hasXPath(xPath, namespaceContext);
}

/**
* <p>
* Creates an order agnostic matcher for {@link Iterable}s that matches when a single pass over
* the examined {@link Iterable} yields a series of items. For a positive match, the examined
* iterable must have have at least one item in common with specified matchers.
* </p>
* <p>
* For example:
* </p>
* <pre>assertThat("Intersect with", Arrays.asList(1, 2, 3, 4), intersectWith(equalTo(2), equalTo(3)));</pre>
*
* @param itemMatchers
* the matchers that must be satisfied by the items provided by an examined {@link Iterable} in the same relative order
*/
@SafeVarargs
public static <T> org.hamcrest.Matcher<java.lang.Iterable<? extends T>> intersectWith(org.hamcrest.Matcher<? super T>... itemMatchers) {
return org.hamcrest.collection.IsIterableIntersecting.intersectWith(itemMatchers);
}

/**
* <p>
* Creates an order agnostic matcher for {@link Iterable}s that matches when a single pass over
* the examined {@link Iterable} yields a series of items. For a positive match, the examined
* iterable must have have at least one item in common with specified matchers.
* </p>
* <p>
* For example:
* </p>
* <pre>assertThat("Intersect with", Arrays.asList(1, 2, 3, 4), intersectWith(2, 3));</pre>
*
* @param items
* the items that must equal the items provided by an examined {@link Iterable} in any order
*/
@SafeVarargs
public static <T> org.hamcrest.Matcher<java.lang.Iterable<? extends T>> intersectWith(T... items) {
return org.hamcrest.collection.IsIterableIntersecting.intersectWith(items);
}

/**
* <p>
* Creates an order agnostic matcher for {@link Iterable}s that matches when a single pass over
* the examined {@link Iterable} yields a series of items. For a positive match, the examined
* iterable must have have at least one item in common with specified matchers.
* </p>
* <p>
* For example:
* </p>
* <pre>assertThat("Intersect with", Arrays.asList(1, 2, 3, 4), intersectWith(Arrays.asList(equalTo(2), equalTo(3))));</pre>
*
* @param itemMatchers
* a list of matchers, each of which must be satisfied by the items provided by
* an examined {@link Iterable} in the same relative order
*/
public static <T> org.hamcrest.Matcher<java.lang.Iterable<? extends T>> intersectWith(java.util.Collection<org.hamcrest.Matcher<? super T>> itemMatchers) {
return org.hamcrest.collection.IsIterableIntersecting.intersectWith(itemMatchers);
}

}
@@ -0,0 +1,138 @@
package org.hamcrest.collection;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeDiagnosingMatcher;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import static org.hamcrest.core.IsEqual.equalTo;

public class IsIterableIntersecting<T> extends TypeSafeDiagnosingMatcher<Iterable<? extends T>> {
private final Collection<Matcher<? super T>> matchers;

public IsIterableIntersecting(Collection<Matcher<? super T>> matchers) {
this.matchers = matchers;
}

@Override
protected boolean matchesSafely(Iterable<? extends T> items, Description mismatchDescription) {
final Matching<T> matching = new Matching<>(matchers, mismatchDescription);
for (T item : items) {
if (matching.matches(item)) {
return true;
}
}

return matching.isFinished(items);
}

@Override
public void describeTo(Description description) {
description.appendText("iterable with items ")
.appendList("[", ", ", "]", matchers)
.appendText(" intersecting");
}

private static class Matching<S> {
private final Collection<Matcher<? super S>> matchers;
private final Description mismatchDescription;

private Matching(Collection<Matcher<? super S>> matchers, Description mismatchDescription) {
this.matchers = new ArrayList<>(matchers);
this.mismatchDescription = mismatchDescription;
}

public boolean matches(S item) {
if (matchers.isEmpty()) {
mismatchDescription.appendText("no match for: ").appendValue(item);
return false;
}
return isMatched(item);
}

public boolean isFinished(Iterable<? extends S> items) {
if (matchers.isEmpty()) {
return true;
}
mismatchDescription
.appendText("no item intersects: ").appendList("", ", ", "", matchers)
.appendText(" with ").appendValueList("[", ", ", "]", items);
return false;
}

private boolean isMatched(S item) {
for (Matcher<? super S> matcher : matchers) {
if (matcher.matches(item)) {
matchers.clear();
return true;
}
}
return false;
}
}

/**
* <p>
* Creates an order agnostic matcher for {@link Iterable}s that matches when a single pass over
* the examined {@link Iterable} yields a series of items. For a positive match, the examined
* iterable must have have at least one item in common with specified matchers.
* </p>
* <p>
* For example:
* </p>
* <pre>assertThat("Intersect with", Arrays.asList(1, 2, 3, 4), intersectWith(equalTo(2), equalTo(3)));</pre>
*
* @param itemMatchers
* the matchers that must be satisfied by the items provided by an examined {@link Iterable} in the same relative order
*/
@SafeVarargs
public static <T> Matcher<Iterable<? extends T>> intersectWith(Matcher<? super T>... itemMatchers) {
return intersectWith(Arrays.asList(itemMatchers));
}

/**
* <p>
* Creates an order agnostic matcher for {@link Iterable}s that matches when a single pass over
* the examined {@link Iterable} yields a series of items. For a positive match, the examined
* iterable must have have at least one item in common with specified matchers.
* </p>
* <p>
* For example:
* </p>
* <pre>assertThat("Intersect with", Arrays.asList(1, 2, 3, 4), intersectWith(2, 3));</pre>
*
* @param items
* the items that must equal the items provided by an examined {@link Iterable} in any order
*/
@SafeVarargs
public static <T> Matcher<Iterable<? extends T>> intersectWith(T... items) {
List<Matcher<? super T>> matchers = new ArrayList<>();
for (T item : items) {
matchers.add(equalTo(item));
}
return new IsIterableIntersecting<>(matchers);
}

/**
* <p>
* Creates an order agnostic matcher for {@link Iterable}s that matches when a single pass over
* the examined {@link Iterable} yields a series of items. For a positive match, the examined
* iterable must have have at least one item in common with specified matchers.
* </p>
* <p>
* For example:
* </p>
* <pre>assertThat("Intersect with", Arrays.asList(1, 2, 3, 4), intersectWith(Arrays.asList(equalTo(2), equalTo(3))));</pre>
*
* @param itemMatchers
* a list of matchers, each of which must be satisfied by the items provided by
* an examined {@link Iterable} in the same relative order
*/
public static <T> Matcher<Iterable<? extends T>> intersectWith(Collection<Matcher<? super T>> itemMatchers) {
return new IsIterableIntersecting<>(itemMatchers);
}
}
@@ -0,0 +1,46 @@
package org.hamcrest.collection;

import org.hamcrest.AbstractMatcherTest;
import org.hamcrest.Matcher;

import java.lang.reflect.Array;
import java.util.Collections;
import java.util.Arrays;

import static org.hamcrest.collection.IsIterableIntersecting.intersectWith;

public class IsIterableIntersectingTest extends AbstractMatcherTest {
@Override
protected Matcher<?> createMatcher() { return intersectWith(1, 2); }

public void testEmptyNotIntersect() {
assertMismatchDescription("no item intersects: <1>, <2> with []", intersectWith(1, 2), Collections.<Integer>emptyList());
}

public void testNotIntersect() {
assertMismatchDescription("no item intersects: <1>, <2>, <3> with [<4>, <5>]", intersectWith(1, 2, 3), Arrays.asList(4, 5));
}

public void testSingleIntersect() {
assertMatches("Single intersect", intersectWith(1), Collections.singletonList(1));
}

public void testPartlyIntersect() {
assertMatches("Partly intersect", intersectWith(1, 2, 3), Arrays.asList(4, 2, 5));
}

public void testFullyIntersect() {
assertMatches("Fully intersect", intersectWith(1, 2, 3), Arrays.asList(3, 4, 2, 1, 5));
assertMatches("Fully intersect", intersectWith(1, 2, 3), Arrays.asList(2, 3));
assertMatches("Fully intersect", intersectWith(1, 2, 3), Arrays.asList(1, 2, 3));
}

public void testDuplicateIntersect() {
assertMatches("Duplicate intersect", intersectWith(1, 2, 3), Arrays.asList(2, 2, 2, 4));
assertMatches("Duplicate intersect", intersectWith(1, 2, 2, 2, 3), Arrays.asList(2, 4, 4, 4));
}

public void testHasAReadableDescription() {
assertDescription("iterable with items [<1>, <2>] intersecting", intersectWith(1, 2));
}
}