package org.testory.plumbing.im.wildcard;
import static java.lang.String.format;
import static java.util.Arrays.asList;
import static org.testory.common.Collections.last;
import static org.testory.common.Matchers.equalDeep;
import static org.testory.common.Matchers.listOf;
import static org.testory.plumbing.PlumbingException.check;
import static org.testory.plumbing.im.wildcard.WildcardInvocation.wildcardInvocation;
import static org.testory.plumbing.im.wildcard.Wildcarded.wildcarded;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.testory.common.DelegatingMatcher;
import org.testory.common.Formatter;
import org.testory.common.Matcher;
import org.testory.common.Matchers;
import org.testory.plumbing.history.History;
import org.testory.plumbing.im.Matcherizer;
import org.testory.proxy.Invocation;
import org.testory.proxy.InvocationMatcher;
public class WildcardMatcherizer implements Matcherizer {
private final Repairer repairer;
private final History history;
private final Formatter formatter;
private WildcardMatcherizer(History history, Repairer repairer, Formatter formatter) {
this.history = history;
this.repairer = repairer;
this.formatter = formatter;
}
public static Matcherizer wildcardMatcherizer(
History history,
Repairer repairer,
Formatter formatter) {
check(history != null);
check(repairer != null);
check(formatter != null);
return new WildcardMatcherizer(history, repairer, formatter);
}
public InvocationMatcher matcherize(Invocation invocation) {
List<Wildcard> wildcards = consumeWildcards();
WildcardInvocation wildcardInvocation = wildcardInvocation(
invocation.method,
invocation.instance,
invocation.arguments,
wildcards);
return matcherize(repairer.repair(wildcardInvocation));
}
private List<Wildcard> consumeWildcards() {
List<Wildcard> wildcards = new ArrayList<>();
for (Object event : history.get()) {
if (event instanceof Wildcard) {
wildcards.add(0, (Wildcard) event);
} else if (event instanceof Wildcarded) {
break;
}
}
history.add(wildcarded());
return wildcards;
}
public InvocationMatcher matcherize(WildcardInvocation invocation) {
List<Object> arguments = invocation.mayBeFolded()
? unfold(invocation.arguments)
: invocation.arguments;
List<Matcher> matchers = matcherize(invocation.wildcards, arguments);
List<Matcher> argumentsMatchers = invocation.mayBeFolded()
? fold(invocation.method.getParameterTypes().length, matchers)
: matchers;
return matcherize(invocation.method, invocation.instance, argumentsMatchers);
}
private List<Matcher> fold(int length, List<Matcher> unfolded) {
List<Matcher> folded = new ArrayList<>();
folded.addAll(unfolded.subList(0, length - 1));
folded.add(arrayOf(unfolded.subList(length - 1, unfolded.size())));
return folded;
}
private static List<Object> unfold(List<?> folded) {
ArrayList<Object> unfolded = new ArrayList<>();
unfolded.addAll(folded.subList(0, folded.size() - 1));
unfolded.addAll(asList((Object[]) last(folded)));
return unfolded;
}
private List<Matcher> matcherize(List<Wildcard> wildcards, List<Object> arguments) {
List<Wildcard> wildcardQueue = new ArrayList<>(wildcards);
List<Matcher> matchers = new ArrayList<>();
for (int i = 0; i < arguments.size(); i++) {
Matcher matcher = !wildcardQueue.isEmpty() && wildcardQueue.get(0).token == arguments.get(i)
? wildcardQueue.remove(0).matcher
: matcherize(arguments.get(i));
matchers.add(matcher);
}
return matchers;
}
private Matcher matcherize(final Object argument) {
return new DelegatingMatcher(equalDeep(argument)) {
public String toString() {
return formatter.format(argument);
}
};
}
private InvocationMatcher matcherize(final Method method, final Object instance,
final List<Matcher> arguments) {
return new InvocationMatcher() {
public boolean matches(Invocation invocation) {
return invocation.method.equals(method)
&& invocation.instance == instance
&& listOf(arguments).matches(invocation.arguments);
}
public String toString() {
return format("%s.%s(%s)", instance, method.getName(), formatter.formatSequence(arguments));
}
};
}
private Matcher arrayOf(final List<Matcher> elements) {
return new DelegatingMatcher(Matchers.arrayOf(elements)) {
public String toString() {
return format("[%s]", formatter.formatSequence(elements));
}
};
}
}