net.amygdalum.extensions.hamcrest.collections.ContainsMatcher Maven / Gradle / Ivy
package net.amygdalum.extensions.hamcrest.collections;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.hamcrest.core.IsNull.nullValue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
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 ContainsMatcher extends TypeSafeMatcher> {
private Class type;
private List> elements;
public ContainsMatcher(Class type) {
this.type = type;
this.elements = new ArrayList<>();
}
public ContainsMatcher and(T element) {
return and(match(element));
}
public ContainsMatcher and(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(Collection extends T> item, Description mismatchDescription) {
List> unmatched = new LinkedList<>(elements);
Matches matches = new Matches<>();
List notExpected = new ArrayList<>();
for (T element : item) {
boolean success = tryMatch(unmatched, element);
if (success) {
matches.match();
} else {
notExpected.add(element);
}
}
if (!notExpected.isEmpty()) {
matches.mismatch("found " + notExpected.size() + " elements surplus " + toDescriptionSet(notExpected));
}
if (!unmatched.isEmpty()) {
matches.mismatch("missing " + unmatched.size() + " elements");
}
mismatchDescription.appendText("mismatching elements ").appendDescriptionOf(matches);
}
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(Collection extends T> item) {
List> unmatched = new LinkedList<>(elements);
for (T element : item) {
boolean success = tryMatch(unmatched, element);
if (!success) {
return false;
}
}
return unmatched.isEmpty();
}
private boolean tryMatch(List> unmatched, T element) {
Iterator> matchers = unmatched.iterator();
while (matchers.hasNext()) {
Matcher matcher = matchers.next();
if (matcher.matches(element)) {
matchers.remove();
return true;
}
}
return false;
}
public static ContainsMatcher empty(Class type) {
return new ContainsMatcher<>(type);
}
@SuppressWarnings("unchecked")
@SafeVarargs
public static ContainsMatcher contains(Class key, Object... elements) {
ContainsMatcher set = new ContainsMatcher<>(key);
for (Object element : elements) {
if (element instanceof Matcher) {
set.and((Matcher) element);
} else {
set.and(key.cast(element));
}
}
return set;
}
}