net.amygdalum.extensions.hamcrest.arrays.ArrayMatcher Maven / Gradle / Ivy
package net.amygdalum.extensions.hamcrest.arrays;
import static java.util.Arrays.asList;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.hamcrest.core.IsNull.nullValue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.StringDescription;
import org.hamcrest.TypeSafeMatcher;
import org.hamcrest.core.IsNull;
import net.amygdalum.extensions.hamcrest.util.Matches;
public class ArrayMatcher extends TypeSafeMatcher {
private Class type;
private Mode match;
private List> elements;
public ArrayMatcher(Class type) {
this.type = type;
this.match = new Exact();
this.elements = new ArrayList<>();
}
public ArrayMatcher element(T element) {
return element(match(element));
}
public ArrayMatcher element(Matcher element) {
elements.add(element);
return this;
}
private Matcher match(T element) {
if (element == null) {
return nullValue(type);
} else {
return equalTo(element);
}
}
@Override
public void describeTo(Description description) {
description.appendValue(elements);
}
@Override
protected void describeMismatchSafely(T[] item, Description mismatchDescription) {
Matches matches = new Matches<>();
Iterator> elementIterator = elements.iterator();
Iterator extends T> itemIterator = asList(item).iterator();
while (elementIterator.hasNext() && itemIterator.hasNext()) {
Matcher matcher = elementIterator.next();
T element = itemIterator.next();
if (!matcher.matches(element)) {
matches.mismatch(matcher, element);
} else {
matches.match();
}
}
if (elementIterator.hasNext()) {
int count = count(elementIterator);
matches.mismatch("missing " + count + " elements");
}
if (itemIterator.hasNext()) {
List items = collect(itemIterator);
matches.mismatch("found " + items.size() + " elements surplus " + toDescriptionSet(items));
}
if (matches.containsMismatches()) {
mismatchDescription.appendText("mismatching elements ").appendDescriptionOf(matches);
}
}
private int count(Iterator> iterator) {
int count = 0;
while (iterator.hasNext()) {
iterator.next();
count++;
}
return count;
}
private List collect(Iterator extends T> iterator) {
List collected = new ArrayList<>();
while (iterator.hasNext()) {
collected.add(iterator.next());
}
return collected;
}
private Set toDescriptionSet(List elements) {
Matcher matcher = bestMatcher();
Set set = new LinkedHashSet<>();
for (T element : elements) {
String desc = descriptionOf(matcher, element);
set.add(desc);
}
return set;
}
private Matcher bestMatcher() {
for (Matcher matcher : elements) {
if (matcher.getClass() != IsNull.class) {
return matcher;
}
}
return equalTo(null);
}
private String descriptionOf(Matcher matcher, S value) {
StringDescription description = new StringDescription();
matcher.describeMismatch(value, description);
return description.toString();
}
@Override
protected boolean matchesSafely(T[] item) {
return match.matchesSafely(item);
}
@SuppressWarnings("unchecked")
@SafeVarargs
public static ArrayMatcher arrayContaining(Class key, Object... elements) {
ArrayMatcher set = new ArrayMatcher<>(key);
for (Object element : elements) {
if (element instanceof Matcher) {
set.element((Matcher) element);
} else {
set.element(key.cast(element));
}
}
return set;
}
public ArrayMatcher inAnyOrder() {
this.match = new AnyOrder();
return this;
}
public ArrayMatcher atLeast() {
this.match = new AtLeast();
return this;
}
interface Mode {
public abstract boolean matchesSafely(T[] item);
}
private class Exact implements Mode {
@Override
public boolean matchesSafely(T[] item) {
if (item.length != elements.size()) {
return false;
}
Iterator> elementIterator = elements.iterator();
Iterator extends T> itemIterator = Arrays.asList(item).iterator();
while (elementIterator.hasNext() && itemIterator.hasNext()) {
Matcher matcher = elementIterator.next();
T element = itemIterator.next();
if (!matcher.matches(element)) {
return false;
}
}
return true;
}
}
private class AnyOrder implements Mode {
@Override
public boolean matchesSafely(T[] item) {
if (item.length != elements.size()) {
return false;
}
List> pending = new ArrayList<>(elements);
Iterator extends T> itemIterator = Arrays.asList(item).iterator();
nextItem: while (itemIterator.hasNext()) {
T element = itemIterator.next();
Iterator> elementIterator = pending.iterator();
while (elementIterator.hasNext()) {
Matcher matcher = elementIterator.next();
if (matcher.matches(element)) {
elementIterator.remove();
continue nextItem;
}
}
return false;
}
return pending.isEmpty();
}
}
private class AtLeast implements Mode {
@Override
public boolean matchesSafely(T[] item) {
if (item.length < elements.size()) {
return false;
}
List> pending = new ArrayList<>(elements);
Iterator extends T> itemIterator = Arrays.asList(item).iterator();
nextItem: while (itemIterator.hasNext()) {
T element = itemIterator.next();
Iterator> elementIterator = pending.iterator();
while (elementIterator.hasNext()) {
Matcher matcher = elementIterator.next();
if (matcher.matches(element)) {
elementIterator.remove();
continue nextItem;
}
}
continue;
}
return pending.isEmpty();
}
}
}