Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.eclipse.xtext.util.PolymorphicDispatcher Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2008, 2017 itemis AG (http://www.itemis.eu) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
*******************************************************************************/
package org.eclipse.xtext.util;
import static org.eclipse.xtext.util.ReflectionUtil.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.apache.log4j.Logger;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
/**
* @author Sven Efftinge - Initial contribution and API
*/
public class PolymorphicDispatcher {
@SuppressWarnings("unused")
private static final Logger log = Logger.getLogger(PolymorphicDispatcher.class);
private final List extends Object> targets;
private final Predicate methodFilter;
private List methods;
public static class DefaultErrorHandler implements ErrorHandler {
@Override
public RT handle(Object[] params, Throwable e) {
return Exceptions.throwUncheckedException(e);
}
}
public static class NullErrorHandler implements ErrorHandler {
public static ErrorHandler get() {
return new NullErrorHandler();
}
@Override
public RT handle(Object[] params, Throwable throwable) {
// ignore
return null;
}
}
public static class WarningErrorHandler implements ErrorHandler {
private Logger logger;
public WarningErrorHandler(Logger logger) {
this.logger = logger;
}
public static ErrorHandler get(Logger logger) {
return new WarningErrorHandler(logger);
}
@Override
public RT handle(Object[] params, Throwable throwable) {
logger.warn("Error in polymorphic dispatcher : "+throwable.getMessage(), throwable);
return null;
}
}
public static class MethodNameFilter implements Predicate {
protected final int maxParams;
protected final String methodName;
protected final int minParams;
public MethodNameFilter(String methodName, int minParams, int maxParams) {
this.maxParams = maxParams;
this.methodName = methodName;
this.minParams = minParams;
}
@Override
public boolean apply(Method param) {
return param.getName().equals(methodName) && param.getParameterTypes().length >= minParams
&& param.getParameterTypes().length <= maxParams;
}
@Override
public String toString() {
return "'" + methodName + "'";
}
public int getMaxParams() {
return maxParams;
}
public int getMinParams() {
return minParams;
}
}
public static class Predicates {
public static Predicate forName(String name) {
return new MethodNameFilter(name, 1, 1);
}
public static Predicate forName(String name, int params) {
return new MethodNameFilter(name, params, params);
}
}
public static interface ErrorHandler {
P handle(Object[] params, Throwable throwable);
}
private final ErrorHandler handler;
public static PolymorphicDispatcher createForSingleTarget(final String methodName, final Object singleTarget) {
return new PolymorphicDispatcher(methodName, Collections.singletonList(singleTarget));
}
public static PolymorphicDispatcher createForSingleTarget(final String methodName, int min, int max, final Object singleTarget) {
return new PolymorphicDispatcher(methodName, min, max, Collections.singletonList(singleTarget));
}
public static PolymorphicDispatcher createForSingleTarget(Predicate methodFilter, Object singleTarget) {
return new PolymorphicDispatcher(Collections.singletonList(singleTarget), methodFilter);
}
public static PolymorphicDispatcher createForVarTarget(final String methodName, final Object... targets) {
return new PolymorphicDispatcher(methodName, Arrays.asList(targets));
}
public PolymorphicDispatcher(final String methodName, final List extends Object> targets) {
this(methodName, 1, 1, targets);
}
public PolymorphicDispatcher(final String methodName, final int minParams, final int maxParams, final List extends Object> targets) {
this(methodName, minParams, maxParams, targets, new DefaultErrorHandler());
}
public PolymorphicDispatcher(final String methodName, final int minParams, final int maxParams, final List extends Object> targets,
ErrorHandler handler) {
this(targets, new MethodNameFilter(methodName, minParams, maxParams), handler);
}
public PolymorphicDispatcher(final List extends Object> targets, Predicate methodFilter) {
this(targets, methodFilter, new DefaultErrorHandler());
}
public PolymorphicDispatcher(final List extends Object> targets, Predicate methodFilter, ErrorHandler handler) {
this.targets = targets;
this.methodFilter = methodFilter;
this.handler = handler;
this.methods = getCandidateMethods();
}
protected class MethodDesc {
private final Object target;
private final Method method;
protected MethodDesc(Object target, Method method) {
super();
this.target = target;
this.method = method;
}
public Class> getDeclaringClass() {
return method.getDeclaringClass();
}
public Class>[] getParameterTypes() {
return method.getParameterTypes();
}
public Method getMethod() {
return method;
}
public Object getTarget() {
return target;
}
public boolean isInvokeable(final List> paramTypes) {
if (getParameterTypes().length != paramTypes.size())
return false;
for (int i = 0; i < paramTypes.size(); i++) {
Class> paramClass = paramTypes.get(i);
if (paramClass!=null && !Void.class.equals(paramClass) && !(getObjectType(getParameterTypes()[i]).isAssignableFrom(getObjectType(paramClass))))
return false;
}
return true;
}
@Override
public String toString() {
return this.method.toString();
}
}
/**
* returns > 0 when o1 is more specific than o2,
*
* returns == 0 when o1 and o2 are equal or unrelated,
*
* returns < 0 when o2 is more specific than o1,
*/
protected int compare(MethodDesc o1, MethodDesc o2) {
final Class>[] paramTypes1 = o1.getParameterTypes();
final Class>[] paramTypes2 = o2.getParameterTypes();
// 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.target)).compareTo(targets.indexOf(o1.target));
return compareTo;
}
private final SimpleCache>, List> cache =
new SimpleCache>, List>(
new Function>, List>() {
@Override
public List apply(List> paramTypes) {
// 'result' contains all best-matched MethodDesc for which
// pairwise compare(m1, m2) == 0, meaning they're equal or unrelated.
List result = new ArrayList();
Iterator iterator = methods.iterator();
NEXT: while (iterator.hasNext()) {
MethodDesc methodDesc = iterator.next();
if (methodDesc.isInvokeable(paramTypes)) {
if (result.isEmpty()) {
result.add(methodDesc);
} else {
Iterator 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;
}
}
);
@SuppressWarnings("unchecked")
public RT invoke(Object... params) {
if (methodFilter instanceof MethodNameFilter) {
MethodNameFilter filter = (MethodNameFilter) methodFilter;
if (params.length filter.getMaxParams()) {
throw new IllegalArgumentException("Wrong number of arguments. Expected "+filter.getMinParams()+" to "+filter.getMaxParams()+".");
}
}
List 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.method.setAccessible(true);
return (RT) current.method.invoke(current.target, 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);
}
}
protected RT handleNoSuchMethod(Object... params) {
return handler.handle(params, new NoSuchMethodException(methodFilter, params));
}
protected RT handleAmbigousMethods(List result, Object... params) {
throw new IllegalStateException("Ambiguous methods " + result + " for params " + Arrays.toString(params));
}
/**
* @param params
* @return
*/
private List> getTypes(Object[] params) {
List> result = new ArrayList>(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;
}
/**
* @return {@code Void.class}
*/
protected Class> getDefaultClass(int paramIndex) {
return Void.class;
}
private List getCandidateMethods() {
ArrayList result = new ArrayList();
for (Object target : targets) {
Class> current = target.getClass();
while (current != Object.class) {
Method[] methods = current.getDeclaredMethods();
for (Method method : methods) {
if (methodFilter.apply(method)) {
result.add(createMethodDesc(target, method));
}
}
current = current.getSuperclass();
}
}
return result;
}
protected MethodDesc createMethodDesc(Object target, Method method) {
return new MethodDesc(target, method);
}
private static class NoSuchMethodException extends java.lang.NoSuchMethodException {
private static final long serialVersionUID = 1L;
private final Predicate methodFilter;
private final Object[] params;
public NoSuchMethodException(Predicate methodFilter, Object[] params) {
this.methodFilter = methodFilter;
this.params = params;
}
@Override
public String getMessage() {
return "Couldn't find method '" + methodFilter.toString() + "' for objects " + Arrays.toString(params);
}
}
}