![JAR search and dependency download from the Maven repository](/logo.png)
co.unruly.matchers.StreamMatchers Maven / Gradle / Ivy
package co.unruly.matchers;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.PrimitiveIterator;
import java.util.Objects;
import java.util.stream.*;
public class StreamMatchers {
public static > Matcher> empty() {
return new TypeSafeMatcher>() {
private Iterator actualIterator;
@Override
protected boolean matchesSafely(BaseStream actual) {
actualIterator = actual.iterator();
return !actualIterator.hasNext();
}
@Override
public void describeTo(Description description) {
description.appendText("An empty Stream");
}
@Override
protected void describeMismatchSafely(BaseStream item, Description description) {
description.appendText("A non empty Stream starting with ").appendValue(actualIterator.next());
}
};
}
/**
* A matcher for a finite Stream producing the same number of items as the expected Stream,
* and producing equal items as expected in the same order.
*
* For infinite Streams use {@link #startsWith}
*
* @param expected A BaseStream against which to compare
* @param The type of items produced by each BaseStream
* @param The type of BaseStream
* @see #startsWith
* @see #startsWithInt
* @see #startsWithLong
* @see #startsWithDouble
*/
public static > Matcher> equalTo(BaseStream expected) {
return new BaseStreamMatcher>() {
@Override
protected boolean matchesSafely(BaseStream actual) {
return remainingItemsEqual(expected.iterator(), actual.iterator());
}
};
}
/**
* A matcher for potentially infinite Streams of objects where the first limit items from each must be
* equal
*
* @param expected A Stream to check against
* @param limit Only check this number of items from actual Stream
* @param The type of items produced by each Stream
* @see #equalTo
* @see #startsWithInt
* @see #startsWithLong
* @see #startsWithDouble
*/
public static Matcher> startsWith(Stream expected, long limit) {
return new BaseStreamMatcher>() {
@Override
protected boolean matchesSafely(Stream actual) {
return remainingItemsEqual(expected.limit(limit).iterator(), actual.limit(limit).iterator());
}
};
}
/**
* A matcher for potentially infinite Streams of primitive doubles where the first limit items from each must be
* equal
*
* @param expected A Stream to check against
* @param limit Only check this number of items from actual Stream
* @see #equalTo
* @see #startsWith
* @see #startsWithInt
* @see #startsWithLong
*/
public static Matcher startsWith(DoubleStream expected, long limit) {
return new BaseStreamMatcher() {
@Override
protected boolean matchesSafely(DoubleStream actual) {
return remainingItemsEqual(expected.limit(limit).iterator(), actual.limit(limit).iterator());
}
};
}
/**
* A matcher for potentially infinite Streams of primitive ints where the first limit items from each must be
* equal
*
* @param expected A Stream to check against
* @param limit Only check this number of items from actual Stream
* @see #equalTo
* @see #startsWith
* @see #startsWithLong
* @see #startsWithDouble
*/
public static Matcher startsWith(IntStream expected, long limit) {
return new BaseStreamMatcher() {
@Override
protected boolean matchesSafely(IntStream actual) {
return remainingItemsEqual(expected.limit(limit).iterator(), actual.limit(limit).iterator());
}
};
}
/**
* A matcher for potentially infinite Streams of primitive ints where the first limit items from each must be
* equal
*
* @param expected A Stream to check against
* @param limit Only check this number of items from actual Stream
* @see #equalTo
* @see #startsWith
* @see #startsWithInt
* @see #startsWithDouble
*/
public static Matcher startsWith(LongStream expected, long limit) {
return new BaseStreamMatcher() {
@Override
protected boolean matchesSafely(LongStream actual) {
return remainingItemsEqual(expected.limit(limit).iterator(), actual.limit(limit).iterator());
}
};
}
private static void describeToStartsAllWith(Description description, long limit, Matcher> matcher) {
description
.appendText("First ")
.appendText(Long.toString(limit))
.appendText(" to match ")
.appendValue(matcher);
}
/**
* A matcher for potentially infinite Streams of objects, the first limit of which must match the given Matcher
*
* @param matcher A matcher to apply to items produced from the Stream
* @param limit Only check this number of items from the Stream
* @see #allMatch
* @see #startsWithAllLong
* @see #startsWithAllInt
* @see #startsWithAllDouble
*/
public static Matcher> startsWithAll(Matcher matcher, long limit) {
return new StreamAllMatches(matcher) {
@Override
protected boolean matchesSafely(Stream actual) {
return super.matchesSafely(actual.limit(limit));
}
@Override
public void describeTo(Description description) {
describeToStartsAllWith(description, limit, matcher);
}
};
}
/**
* A matcher for potentially infinite Streams of primitive longs, the first limit of which must match the given Matcher
*
* @param matcher A matcher to apply to items produced from the Stream
* @param limit Only check this number of items from the Stream
* @see #allMatchLong
* @see #startsWithAll
* @see #startsWithAllInt
* @see #startsWithAllDouble
*/
public static Matcher startsWithAllLong(Matcher matcher, long limit) {
return new LongStreamAllMatches(matcher) {
@Override
protected boolean matchesSafely(LongStream actual) {
return super.matchesSafely(actual.limit(limit));
}
@Override
public void describeTo(Description description) {
describeToStartsAllWith(description, limit, matcher);
}
};
}
/**
* A matcher for potentially infinite Streams of primitive ints, the first limit of which must match the given Matcher
*
* @param matcher A matcher to apply to items produced from the Stream
* @param limit Only check this number of items from the Stream
* @see #allMatchInt
* @see #startsWithAll
* @see #startsWithAllLong
* @see #startsWithAllDouble
*/
public static Matcher startsWithAllInt(Matcher matcher, long limit) {
return new IntStreamAllMatches(matcher) {
@Override
protected boolean matchesSafely(IntStream actual) {
return super.matchesSafely(actual.limit(limit));
}
@Override
public void describeTo(Description description) {
describeToStartsAllWith(description, limit, matcher);
}
};
}
/**
* A matcher for potentially infinite Streams of primitive doubles, the first limit of which must match the given Matcher
*
* @param matcher A matcher to apply to items produced from the Stream
* @param limit Only check this number of items from the Stream
* @see #allMatchDouble
* @see #startsWithAll
* @see #startsWithAllInt
* @see #startsWithAllLong
*/
public static Matcher startsWithAllDouble(Matcher matcher, long limit) {
return new DoubleStreamAllMatches(matcher) {
@Override
protected boolean matchesSafely(DoubleStream actual) {
return super.matchesSafely(actual.limit(limit));
}
@Override
public void describeTo(Description description) {
describeToStartsAllWith(description, limit, matcher);
}
};
}
private static void describeToStartsAnyWith(Description description, long limit, Matcher> matcher) {
description
.appendText("Any of first ")
.appendText(Long.toString(limit))
.appendText(" to match ")
.appendValue(matcher);
}
/**
* A matcher for potentially infinite Streams of objects,
* at least one of the first limit of which must match the given Matcher
*
* @param matcher A matcher to apply to items produced from the Stream
* @param limit Only check this number of items from the Stream
* @see #anyMatch
* @see #startsWithAnyInt
* @see #startsWithAnyLong
* @see #startsWithAnyDouble
*/
public static Matcher> startsWithAny(Matcher matcher, long limit) {
return new StreamAnyMatches(matcher) {
@Override
protected boolean matchesSafely(Stream actual) {
return super.matchesSafely(actual.limit(limit));
}
@Override
public void describeTo(Description description) {
describeToStartsAnyWith(description, limit, matcher);
}
};
}
/**
* A matcher for potentially infinite Streams of primitive longs,
* at least one of the first limit of which must match the given Matcher
*
* @param matcher A matcher to apply to items produced from the Stream
* @param limit Only check this number of items from the Stream
* @see #anyMatchLong
* @see #startsWithAny
* @see #startsWithAnyInt
* @see #startsWithAnyDouble
*/
public static Matcher startsWithAnyLong(Matcher matcher, long limit) {
return new LongStreamAnyMatches(matcher) {
@Override
protected boolean matchesSafely(LongStream actual) {
return super.matchesSafely(actual.limit(limit));
}
@Override
public void describeTo(Description description) {
describeToStartsAnyWith(description, limit, matcher);
}
};
}
/**
* A matcher for potentially infinite Streams of primitive doubles,
* at least one of the first limit of which must match the given Matcher
*
* @param matcher A matcher to apply to items produced from the Stream
* @param limit Only check this number of items from the Stream
* @see #anyMatchDouble
* @see #startsWithAny
* @see #startsWithAnyInt
* @see #startsWithAnyLong
*/
public static Matcher startsWithAnyDouble(Matcher matcher, long limit) {
return new DoubleStreamAnyMatches(matcher) {
@Override
protected boolean matchesSafely(DoubleStream actual) {
return super.matchesSafely(actual.limit(limit));
}
@Override
public void describeTo(Description description) {
describeToStartsAnyWith(description, limit, matcher);
}
};
}
/**
* A matcher for potentially infinite Streams of primitive ints,
* at least one of the first limit of which must match the given Matcher
*
* @param matcher A matcher to apply to items produced from the Stream
* @param limit Only check this number of items from the Stream
* @see #anyMatchInt
* @see #startsWithAny
* @see #startsWithAnyLong
* @see #startsWithAnyDouble
*/
public static Matcher startsWithAnyInt(Matcher matcher, long limit) {
return new IntStreamAnyMatches(matcher) {
@Override
protected boolean matchesSafely(IntStream actual) {
return super.matchesSafely(actual.limit(limit));
}
@Override
public void describeTo(Description description) {
describeToStartsAnyWith(description, limit, matcher);
}
};
}
/**
* The BaseStream must produce exactly the given expected items in order, and no more.
*
* For infinite BaseStreams see {@link #startsWith(T...)} or a primitive stream variant
* @param expected The items that should be produced by the BaseStream
* @param The type of items
* @param The type of the BaseStream
* @see #startsWith(T...)
* @see #startsWithInt(int...)
* @see #startsWithLong(long...)
* @see #startsWithDouble(double...)
*/
@SafeVarargs
public static > Matcher> contains(T... expected) {
return new BaseStreamMatcher>() {
@Override
protected boolean matchesSafely(BaseStream actual) {
return remainingItemsEqual(new ArrayIterator<>(expected), actual.iterator());
}
};
}
/**
* A matcher for a finite Stream of objects, all of which must match the given Matcher.
*
* For infinite Streams see {@link #startsWithAll}
*
* @param matcher A Matcher against which to compare items from the Stream
* @param The type of items produced by the Stream
* @see #startsWithAll
* @see #allMatchInt
* @see #allMatchLong
* @see #allMatchDouble
*/
public static Matcher> allMatch(Matcher matcher) {
return new StreamAllMatches(matcher) {
@Override
public void describeTo(Description description) {
description.appendText("All to match ").appendValue(matcher);
}
};
}
/**
* A matcher for a finite Stream of primitive ints, all of which must match the given Matcher.
*
* For infinite Streams see {@link #startsWithAllInt}
*
* @param matcher A Matcher against which to compare items from the Stream
* @see #startsWithAllInt
* @see #allMatch
* @see #allMatchLong
* @see #allMatchDouble
*/
public static Matcher allMatchInt(Matcher matcher) {
return new IntStreamAllMatches(matcher) {
@Override
public void describeTo(Description description) {
description.appendText("All to match ").appendValue(matcher);
}
};
}
/**
* A matcher for a finite Stream of primitive longs, all of which must match the given Matcher.
*
* For infinite Streams see {@link #startsWithAllLong}
*
* @param matcher A Matcher against which to compare items from the Stream
* @see #startsWithAllLong
* @see #allMatch
* @see #allMatchInt
* @see #allMatchDouble
*/
public static Matcher allMatchLong(Matcher matcher) {
return new LongStreamAllMatches(matcher) {
@Override
public void describeTo(Description description) {
description.appendText("All to match ").appendValue(matcher);
}
};
}
/**
* A matcher for a finite Stream of primitive doubles, all of which must match the given Matcher.
*
* For infinite Streams see {@link #startsWithAllDouble}
*
* @param matcher A Matcher against which to compare items from the Stream
* @see #startsWithAllDouble
* @see #allMatch
* @see #allMatchInt
* @see #allMatchLong
*/
public static Matcher allMatchDouble(Matcher matcher) {
return new DoubleStreamAllMatches(matcher) {
@Override
public void describeTo(Description description) {
description.appendText("All to match ").appendValue(matcher);
}
};
}
/**
* A matcher for a finite Stream of objects, at least one of which must match the given Matcher.
*
* For infinite Streams see {@link #startsWithAny}
*
* @param matcher A Matcher against which to compare items from the Stream
* @param The type of items produced by the Stream
* @see #startsWithAny
* @see #anyMatchInt
* @see #anyMatchLong
* @see #anyMatchDouble
*/
public static Matcher> anyMatch(Matcher matcher) {
return new StreamAnyMatches(matcher) {
@Override
public void describeTo(Description description) {
description.appendText("Any to match ").appendValue(matcher);
}
};
}
/**
* A matcher for a finite Stream of primitive longs, at least one of which must match the given Matcher.
*
* For infinite Streams see {@link #startsWithAnyLong}
*
* @param matcher A Matcher against which to compare items from the Stream
* @see #startsWithAnyLong
* @see #anyMatch
* @see #anyMatchInt
* @see #anyMatchDouble
*/
public static Matcher anyMatchLong(Matcher matcher) {
return new LongStreamAnyMatches(matcher) {
@Override
public void describeTo(Description description) {
description.appendText("Any to match ").appendValue(matcher);
}
};
}
/**
* A matcher for a finite Stream of primitive doubles, at least one of which must match the given Matcher.
*
* For infinite Streams see {@link #startsWithAnyDouble}
*
* @param matcher A Matcher against which to compare items from the Stream
* @see #startsWithAnyDouble
* @see #anyMatch
* @see #anyMatchInt
* @see #anyMatchDouble
*/
public static Matcher anyMatchDouble(Matcher matcher) {
return new DoubleStreamAnyMatches(matcher) {
@Override
public void describeTo(Description description) {
description.appendText("Any to match ").appendValue(matcher);
}
};
}
/**
* A matcher for a finite Stream of primitive ints, at least one of which must match the given Matcher.
*
* For infinite Streams see {@link #startsWithAnyInt}
*
* @param matcher A Matcher against which to compare items from the Stream
* @see #startsWithAnyInt
* @see #anyMatch
* @see #anyMatchLong
* @see #anyMatchDouble
*/
public static Matcher anyMatchInt(Matcher matcher) {
return new IntStreamAnyMatches(matcher) {
@Override
public void describeTo(Description description) {
description.appendText("Any to match ").appendValue(matcher);
}
};
}
/**
* A matcher for a potentially infinite Stream of objects against n expected items, matching if the first n items
* produced by the Stream equal the expected items in order. Whether the Stream would subsequently produce
* additional items is irrelevant.
*
* @param expected The expected items produced first by the Stream
* @param The type of items
* @see #contains
* @see #startsWithInt
* @see #startsWithDouble
* @see #startsWithLong
*/
@SafeVarargs
public static Matcher> startsWith(T... expected) {
return new BaseStreamMatcher>() {
@Override
protected boolean matchesSafely(Stream actual) {
return remainingItemsEqual(new ArrayIterator<>(expected), actual.limit(expected.length).iterator());
}
};
}
/**
* A matcher for a potentially infinite Stream of primitive doubles against n expected items, matching if the first n items
* produced by the Stream equal the expected items in order. Whether the Stream would subsequently produce
* additional items is irrelevant.
*
* @param expected The expected items produced first by the Stream
* @see #contains
* @see #startsWith
* @see #startsWithInt
* @see #startsWithLong
*/
public static Matcher startsWithDouble(double... expected) {
return new BaseStreamMatcher() {
@Override
protected boolean matchesSafely(DoubleStream actual) {
return remainingItemsEqual(new DoubleArrayIterator(expected), actual.limit(expected.length).iterator());
}
};
}
/**
* A matcher for a potentially infinite Stream of primitive longs against n expected items, matching if the first n items
* produced by the Stream equal the expected items in order. Whether the Stream would subsequently produce
* additional items is irrelevant.
*
* @param expected The expected items produced first by the Stream
* @see #contains
* @see #startsWith
* @see #startsWithInt
* @see #startsWithDouble
*/
public static Matcher startsWithLong(long... expected) {
return new BaseStreamMatcher() {
@Override
protected boolean matchesSafely(LongStream actual) {
return remainingItemsEqual(new LongArrayIterator(expected), actual.limit(expected.length).iterator());
}
};
}
/**
* A matcher for a potentially infinite Stream of primitive ints against n expected items, matching if the first n items
* produced by the Stream equal the expected items in order. Whether the Stream would subsequently produce
* additional items is irrelevant.
*
* @param expected The expected items produced first by the Stream
* @see #contains
* @see #startsWith
* @see #startsWithLong
* @see #startsWithDouble
*/
public static Matcher startsWithInt(int... expected) {
return new BaseStreamMatcher() {
@Override
protected boolean matchesSafely(IntStream actual) {
return remainingItemsEqual(new IntArrayIterator(expected), actual.limit(expected.length).iterator());
}
};
}
private static abstract class BaseStreamMatcher> extends TypeSafeMatcher {
final List expectedAccumulator = new LinkedList<>();
final List actualAccumulator = new LinkedList<>();
@Override
public void describeTo(Description description) {
describe(description, expectedAccumulator);
}
@Override
protected void describeMismatchSafely(S item, Description description) {
describe(description, actualAccumulator);
}
private void describe(Description description, List values) {
description.appendText("Stream of ").appendValueList("[", ",", "]", values);
}
boolean remainingItemsEqual(Iterator expectedIterator, Iterator actualIterator) {
if (!expectedIterator.hasNext() && !actualIterator.hasNext()) {
return true;
}
if (expectedIterator.hasNext() && actualIterator.hasNext()) {
T nextExpected = expectedIterator.next();
expectedAccumulator.add(nextExpected);
T nextActual = actualIterator.next();
actualAccumulator.add(nextActual);
if(Objects.equals(nextExpected, nextActual)) {
return remainingItemsEqual(expectedIterator, actualIterator);
}
}
expectedIterator.forEachRemaining(expectedAccumulator::add);
actualIterator.forEachRemaining(actualAccumulator::add);
return false;
}
}
private static void allMatchMismatch(Description mismatchDescription, long position, Object nonMatch) {
mismatchDescription.appendText("Item ").appendText(Long.toString(position)).appendText(" failed to match: ").appendValue(nonMatch);
}
private static abstract class StreamAllMatches extends TypeSafeMatcher> {
private T nonMatching;
private long positionNonMatching = -1L;
private final Matcher matcher;
StreamAllMatches(Matcher matcher) {
this.matcher = matcher;
}
@Override
protected boolean matchesSafely(Stream actual) {
return actual
.peek(i -> {nonMatching = i; positionNonMatching++;})
.allMatch(matcher::matches);
}
@Override
protected void describeMismatchSafely(Stream actual, Description mismatchDescription) {
allMatchMismatch(mismatchDescription, positionNonMatching, nonMatching);
}
}
private static abstract class IntStreamAllMatches extends TypeSafeMatcher {
private int nonMatching;
private long positionNonMatching = -1L;
private final Matcher matcher;
IntStreamAllMatches(Matcher matcher) {
this.matcher = matcher;
}
@Override
protected boolean matchesSafely(IntStream actual) {
return actual
.peek(i -> {nonMatching = i; positionNonMatching++;})
.allMatch(matcher::matches);
}
@Override
protected void describeMismatchSafely(IntStream actual, Description mismatchDescription) {
allMatchMismatch(mismatchDescription, positionNonMatching, nonMatching);
}
}
private static abstract class LongStreamAllMatches extends TypeSafeMatcher {
private long nonMatching;
private long positionNonMatching = -1L;
private final Matcher matcher;
LongStreamAllMatches(Matcher matcher) {
this.matcher = matcher;
}
@Override
protected boolean matchesSafely(LongStream actual) {
return actual
.peek(i -> {nonMatching = i; positionNonMatching++;})
.allMatch(matcher::matches);
}
@Override
protected void describeMismatchSafely(LongStream actual, Description mismatchDescription) {
allMatchMismatch(mismatchDescription, positionNonMatching, nonMatching);
}
}
private static abstract class DoubleStreamAllMatches extends TypeSafeMatcher {
private double nonMatching;
private long positionNonMatching = -1L;
private final Matcher matcher;
DoubleStreamAllMatches(Matcher matcher) {
this.matcher = matcher;
}
@Override
protected boolean matchesSafely(DoubleStream actual) {
return actual
.peek(i -> {nonMatching = i; positionNonMatching++;})
.allMatch(matcher::matches);
}
@Override
protected void describeMismatchSafely(DoubleStream actual, Description mismatchDescription) {
allMatchMismatch(mismatchDescription, positionNonMatching, nonMatching);
}
}
private static void anyMatchMismatch(Description mismatchDescription, List> accumulator) {
mismatchDescription
.appendText("None of these items matched: ")
.appendValueList("[", ",", "]", accumulator);
}
private static abstract class StreamAnyMatches extends TypeSafeMatcher> {
final List accumulator = new LinkedList<>();
final Matcher matcher;
StreamAnyMatches(Matcher matcher) {
this.matcher = matcher;
}
@Override
protected boolean matchesSafely(Stream actual) {
return actual.peek(accumulator::add).anyMatch(matcher::matches);
}
@Override
protected void describeMismatchSafely(Stream actual, Description mismatchDescription) {
anyMatchMismatch(mismatchDescription,accumulator);
}
}
private static abstract class LongStreamAnyMatches extends TypeSafeMatcher {
final List accumulator = new LinkedList<>();
final Matcher matcher;
LongStreamAnyMatches(Matcher matcher) {
this.matcher = matcher;
}
@Override
protected boolean matchesSafely(LongStream actual) {
return actual.peek(accumulator::add).anyMatch(matcher::matches);
}
@Override
protected void describeMismatchSafely(LongStream actual, Description mismatchDescription) {
anyMatchMismatch(mismatchDescription, accumulator);
}
}
private static abstract class IntStreamAnyMatches extends TypeSafeMatcher {
final List accumulator = new LinkedList<>();
final Matcher matcher;
IntStreamAnyMatches(Matcher matcher) {
this.matcher = matcher;
}
@Override
protected boolean matchesSafely(IntStream actual) {
return actual.peek(accumulator::add).anyMatch(matcher::matches);
}
@Override
protected void describeMismatchSafely(IntStream actual, Description mismatchDescription) {
anyMatchMismatch(mismatchDescription, accumulator);
}
}
private static abstract class DoubleStreamAnyMatches extends TypeSafeMatcher {
final List accumulator = new LinkedList<>();
final Matcher matcher;
DoubleStreamAnyMatches(Matcher matcher) {
this.matcher = matcher;
}
@Override
protected boolean matchesSafely(DoubleStream actual) {
return actual.peek(accumulator::add).anyMatch(matcher::matches);
}
@Override
protected void describeMismatchSafely(DoubleStream actual, Description mismatchDescription) {
anyMatchMismatch(mismatchDescription, accumulator);
}
}
private static class ArrayIterator implements Iterator {
private final T[] expected;
private int currentPos = 0;
@SafeVarargs
public ArrayIterator(T... expected) {
this.expected = expected;
}
@Override
public boolean hasNext() {
return currentPos < expected.length;
}
@Override
public T next() {
return expected[currentPos++];
}
}
private static class IntArrayIterator implements PrimitiveIterator.OfInt {
private final int[] expected;
private int currentPos = 0;
public IntArrayIterator(int... expected) {
this.expected = expected;
}
@Override
public boolean hasNext() {
return currentPos < expected.length;
}
@Override
public int nextInt() {
return expected[currentPos++];
}
}
private static class LongArrayIterator implements PrimitiveIterator.OfLong {
private final long[] expected;
private int currentPos = 0;
public LongArrayIterator(long... expected) {
this.expected = expected;
}
@Override
public boolean hasNext() {
return currentPos < expected.length;
}
@Override
public long nextLong() {
return expected[currentPos++];
}
}
private static class DoubleArrayIterator implements PrimitiveIterator.OfDouble {
private final double[] expected;
private int currentPos = 0;
public DoubleArrayIterator(double... expected) {
this.expected = expected;
}
@Override
public boolean hasNext() {
return currentPos < expected.length;
}
@Override
public double nextDouble() {
return expected[currentPos++];
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy