/*-
* Copyright (c) 2012-2016 Diamond Light Source Ltd.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package uk.ac.diamond.scisoft.analysis.rpc;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.eclipse.dawnsci.analysis.api.rpc.AnalysisRpcException;
import org.eclipse.dawnsci.analysis.api.rpc.IAnalysisRpcHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.ac.diamond.scisoft.analysis.rpc.flattening.TypedNone;
import uk.ac.diamond.scisoft.analysis.rpc.internal.TypeHelper;
/**
* Abstract implementation of {@link IAnalysisRpcHandler} that uses reflection to resolve method name to call.
* <p>
* Concrete classes can:
* <ul>
* <li>filter methods that are legal by overriding {@link #isMethodOk(Method, String, Class[])}.</li>
* <li>provide an instance to invoke method on by overriding {@link #getInvokeObject()}</li>
* <li>provide an implementation for {@link #invoke(Method, Object, Object[])} if some code wants to be run before or
* after the method invocation, or if the method invocation wants to be wrapped in some other way.</li>
* </ul>
*/
abstract public class AbstractAnalysisRpcGenericDispatcher implements IAnalysisRpcHandler {
private static final Logger logger = LoggerFactory.getLogger(AbstractAnalysisRpcGenericDispatcher.class);
private Class<?> delegate;
/**
* Create a new dispatcher
*
* @param delegate
* the type of class to delegate to.
* @throws NullPointerException
* if delegate is <code>null</code>
*/
public AbstractAnalysisRpcGenericDispatcher(Class<?> delegate) {
if (delegate == null)
throw new NullPointerException("Instance must be non-null");
this.delegate = delegate;
}
/**
* Override to do custom or additional checks on whether method is legal.
*
* @param method
* Method to check
* @return true if method is ok to use, false otherwise.
*/
protected boolean isMethodOk(Method method, String dispatchMethodName, Class<?>[] dispatchArgTypes) {
if (!method.getName().equals(dispatchMethodName)) {
return false;
}
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != dispatchArgTypes.length) {
return false;
}
boolean methodOk = true;
for (int i = 0; i < parameterTypes.length; i++) {
if (dispatchArgTypes[i] == null && !TypeHelper.isPrimitive(parameterTypes[i])) {
// good
} else if (dispatchArgTypes[i] != null && parameterTypes[i].isAssignableFrom(dispatchArgTypes[i])) {
// good
} else if (dispatchArgTypes[i] != null && TypeHelper.isBoxed(dispatchArgTypes[i], parameterTypes[i])) {
// good
} else {
// no match
methodOk = false;
break;
}
}
return methodOk;
}
/**
* Return the object to invoke on. e.g. static methods can return null, others should return an instance of the
* delegate passed at construction time.
*
* @return object to invoke
*/
protected abstract Object getInvokeObject();
private Object dispatch(String methodName, Object[] args) throws AnalysisRpcException {
Class<?>[] types = new Class<?>[args.length];
for (int i = 0; i < args.length; i++) {
if (args[i] == null) {
types[i] = null;
} else if (args[i] instanceof TypedNone) {
types[i] = ((TypedNone) args[i]).getType();
args[i] = null;
} else {
types[i] = args[i].getClass();
}
}
Method m = null;
for (Method method : delegate.getMethods()) {
if (isMethodOk(method, methodName, types)) {
m = method;
break;
}
}
if (m == null) {
String msg = "Failed to find method " + delegate.toString() + "." + methodName + "("
+ Arrays.toString(types) + ")";
logger.error(msg);
throw new AnalysisRpcException(msg);
}
try {
return invoke(m, getInvokeObject(), args);
} catch (Exception e) {
String msg = "Failed to invoke method " + delegate.toString() + "." + methodName + "("
+ Arrays.toString(types) + ")";
logger.error(msg, e);
if (e instanceof InvocationTargetException) {
throw new AnalysisRpcException(((InvocationTargetException) e).getTargetException());
}
throw new AnalysisRpcException(e);
}
}
/**
* Invoke the actual method reflexively
*
* @param method
* method to invoke
* @param instance
* instance to invoke method on
* @param args
* arguments to pass
* @return what the method invokes
* @throws Exception
*/
protected Object invoke(Method method, Object instance, Object[] args) throws Exception {
return method.invoke(instance, args);
}
@Override
public Object run(Object[] args) throws AnalysisRpcException {
String methodName = (String) args[0];
Object[] methodArgs = new Object[args.length - 1];
System.arraycopy(args, 1, methodArgs, 0, methodArgs.length);
return dispatch(methodName, methodArgs);
}
}