/**
*
*/
package it.xsemantics.runtime.internal;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.apache.log4j.Logger;
import org.eclipse.xtext.util.PolymorphicDispatcher;
import org.eclipse.xtext.util.SimpleCache;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
/**
* Temporary patched version:
* https://github.com/LorenzoBettini/xsemantics/issues/82
* https://github.com/eclipse/xtext-core/issues/238
*
* @author Lorenzo Bettini
*
*/
public class PatchedPolymorphicDispatcher<RT> extends PolymorphicDispatcher<RT> {
@SuppressWarnings("unused")
private static final Logger log = Logger.getLogger(PolymorphicDispatcher.class);
private final List<? extends Object> targets;
private final Predicate<Method> methodFilter;
private Collection<MethodDesc> methods;
private final ErrorHandler<RT> handler;
public PatchedPolymorphicDispatcher(final List<? extends Object> targets, Predicate<Method> methodFilter) {
this(targets, methodFilter, new DefaultErrorHandler<RT>());
}
public PatchedPolymorphicDispatcher(final List<? extends Object> targets, Predicate<Method> methodFilter, ErrorHandler<RT> handler) {
super(Collections.emptyList(), methodFilter, handler);
this.targets = targets;
this.methodFilter = methodFilter;
this.handler = handler;
methods = getCandidateMethods();
}
@Override
protected int compare(MethodDesc o1, MethodDesc o2) {
final Class<?>[] paramTypes1 = o1.getParameterTypes();
final Class<?>[] paramTypes2 = o2.getParameterTypes();
// sort by number of parameters
if (paramTypes1.length > paramTypes2.length)
return 1;
if (paramTypes2.length > paramTypes1.length)
return -1;
// sort by parameter types from left to right
for (int i = 0; i < paramTypes1.length; i++) {
final Class<?> class1 = paramTypes1[i];
final Class<?> class2 = paramTypes2[i];
if (class1.equals(class2))
continue;
if (class1.isAssignableFrom(class2) || Void.class.equals(class2))
return -1;
if (class2.isAssignableFrom(class1) || Void.class.equals(class1))
return 1;
}
// sort by declaring class (more specific comes first).
if (!o1.getDeclaringClass().equals(o2.getDeclaringClass())) {
if (o1.getDeclaringClass().isAssignableFrom(o2.getDeclaringClass()))
return 1;
if (o2.getDeclaringClass().isAssignableFrom(o1.getDeclaringClass()))
return -1;
}
// sort by target
final int compareTo = ((Integer) targets.indexOf(o2.getTarget())).compareTo(targets.indexOf(o1.getTarget()));
return compareTo;
}
private final SimpleCache<List<Class<?>>, List<MethodDesc>> cache =
new SimpleCache<List<Class<?>>, List<MethodDesc>>(
new Function<List<Class<?>>, List<MethodDesc>>() {
@Override
public List<MethodDesc> apply(List<Class<?>> paramTypes) {
// 'result' contains all best-matched MethodDesc for which
// pairwise compare(m1, m2) == 0, meaning they're equal or unrelated.
List<MethodDesc> result = new ArrayList<MethodDesc>();
Iterator<MethodDesc> iterator = methods.iterator();
NEXT: while (iterator.hasNext()) {
MethodDesc methodDesc = iterator.next();
if (methodDesc.isInvokeable(paramTypes)) {
if (result.isEmpty()) {
result.add(methodDesc);
} else {
Iterator<MethodDesc> it = result.iterator();
while(it.hasNext()) {
MethodDesc next = it.next();
int compare = compare(next, methodDesc);
if (compare < 0) {
it.remove();
} else if (compare > 0) {
continue NEXT;
}
}
result.add(methodDesc);
}
}
}
return result;
}
}
);
@Override
@SuppressWarnings("unchecked")
public RT invoke(Object... params) {
if (methodFilter instanceof MethodNameFilter) {
MethodNameFilter filter = (MethodNameFilter) methodFilter;
if (params.length<filter.getMinParams() || params.length > filter.getMaxParams()) {
throw new IllegalArgumentException("Wrong number of arguments. Expected "+filter.getMinParams()+" to "+filter.getMaxParams()+".");
}
}
List<MethodDesc> result = cache.get(getTypes(params));
// check if ambiguous
if (result.size()>1)
return handleAmbigousMethods(result, params);
if (result.isEmpty())
return handleNoSuchMethod(params);
try {
MethodDesc current = result.get(0);
current.getMethod().setAccessible(true);
return (RT) current.getMethod().invoke(current.getTarget(), params);
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof Error)
throw (Error) e.getTargetException();
return handler.handle(params, e.getTargetException());
} catch (IllegalArgumentException e) {
return handler.handle(params, e);
} catch (IllegalAccessException e) {
return handler.handle(params, e);
}
}
/**
* @param params
* @return
*/
private List<Class<?>> getTypes(Object[] params) {
List<Class<?>> result = new ArrayList<Class<?>>(params.length);
for (int i = 0; i < params.length; i++) {
if (params[i]!=null) {
result.add(params[i].getClass());
} else {
result.add(getDefaultClass(i));
}
}
return result;
}
private Collection<MethodDesc> getCandidateMethods() {
Collection<MethodDesc> cachedDescriptors = new ArrayList<MethodDesc>();
for (Object target : targets) {
Class<?> current = target.getClass();
while (current != Object.class) {
Method[] methods = current.getDeclaredMethods();
for (Method method : methods) {
if (methodFilter.apply(method)) {
cachedDescriptors.add(createMethodDesc(target, method));
}
}
current = current.getSuperclass();
}
}
return cachedDescriptors;
}
}