package org.testory.plumbing.im.wildcard;
import static java.util.Arrays.asList;
import static java.util.Collections.nCopies;
import static java.util.Objects.deepEquals;
import static org.testory.common.Collections.flip;
import static org.testory.common.Collections.last;
import static org.testory.plumbing.PlumbingException.check;
import static org.testory.plumbing.im.wildcard.WildcardInvocation.wildcardInvocation;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
public class Repairer {
private Repairer() {}
public static Repairer repairer() {
return new Repairer();
}
public WildcardInvocation repair(WildcardInvocation invocation) {
check(invocation != null);
List<Class<?>> parameters = asList(invocation.method.getParameterTypes());
boolean mayBeFolded = invocation.mayBeFolded();
List<Object> unfolded = mayBeFolded
? unfoldArguments(invocation.arguments)
: invocation.arguments;
List<Class<?>> unfoldedParameters = mayBeFolded
? unfoldParameters(unfolded.size(), parameters)
: parameters;
List<Object> repairedUnfolded = repair(unfoldedParameters, unfolded, invocation);
List<Object> repaired = mayBeFolded
? foldArguments(parameters.size(), repairedUnfolded)
: repairedUnfolded;
return wildcardInvocation(
invocation.method,
invocation.instance,
repaired,
invocation.wildcards);
}
private static List<Object> repair(
List<Class<?>> parameters,
List<Object> arguments,
WildcardInvocation invocation) {
List<Boolean> solution = trySolveEager(invocation.wildcards, parameters, arguments);
if (!deepEquals(
flip(solution),
trySolveEager(flip(invocation.wildcards), flip(parameters), flip(arguments)))) {
throw new WildcardException("found more than one solution");
}
List<Wildcard> wildcards = new ArrayList<>(invocation.wildcards);
List<Object> repaired = new ArrayList<>();
for (int i = 0; i < solution.size(); i++) {
repaired.add(solution.get(i)
? wildcards.remove(0).token
: arguments.get(i));
}
return repaired;
}
private static List<Boolean> trySolveEager(
List<Wildcard> wildcards,
List<Class<?>> parameters,
List<Object> arguments) {
List<Boolean> solution = new ArrayList<>(nCopies(arguments.size(), false));
int nextIndex = 0;
nextWildcard: for (Wildcard wildcard : wildcards) {
for (int i = nextIndex; i < arguments.size(); i++) {
if (wildcard.token == arguments.get(i)) {
solution.set(i, true);
nextIndex = i + 1;
continue nextWildcard;
}
}
for (int i = nextIndex; i < arguments.size(); i++) {
if (parameters.get(i).isPrimitive()) {
solution.set(i, true);
nextIndex = i + 1;
continue nextWildcard;
}
}
throw new WildcardException("cannot find any solution");
}
return solution;
}
private static List<Class<?>> unfoldParameters(int size, List<Class<?>> parameters) {
List<Class<?>> unfolded = new ArrayList<>();
unfolded.addAll(parameters.subList(0, parameters.size() - 1));
unfolded.addAll(nCopies(size - (parameters.size() - 1), last(parameters).getComponentType()));
return unfolded;
}
private static List<Object> unfoldArguments(List<?> folded) {
ArrayList<Object> unfolded = new ArrayList<>();
unfolded.addAll(folded.subList(0, folded.size() - 1));
unfolded.addAll(asBoxingList(last(folded)));
return unfolded;
}
private static List<Object> asBoxingList(Object array) {
List<Object> list = new ArrayList<>();
for (int i = 0; i < Array.getLength(array); i++) {
list.add(Array.get(array, i));
}
return list;
}
private static List<Object> foldArguments(int length, List<Object> arguments) {
List<Object> folded = new ArrayList<>();
folded.addAll(arguments.subList(0, length - 1));
folded.add(asArray(arguments.subList(length - 1, arguments.size())));
return folded;
}
private static Object asArray(List<Object> elements) {
Object array = Array.newInstance(Object.class, 0);
return elements.toArray((Object[]) array);
}
}