All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 List> elements;
	private boolean anyOrder;

	public ArrayMatcher(Class type) {
		this.type = type;
		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 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 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) {
		if (item.length != elements.size()) {
			return false;
		}

		if (anyOrder) {
			List> pending = new ArrayList<>(elements);
			Iterator 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();
		} else {
			Iterator> elementIterator = elements.iterator();
			Iterator 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;
		}
	}

	@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.anyOrder = true;
		return this;
	}

}