/**
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.sesame.function;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.core.link.Link;
import com.opengamma.sesame.Environment;
import com.opengamma.sesame.config.EngineUtils;
import com.opengamma.sesame.config.FunctionArguments;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.result.FailureStatus;
import com.opengamma.util.result.Result;
/**
* {@link InvokableFunction} implementation that wraps a {@link Method} object and invokes it using reflection.
*/
/* package */ class MethodInvokableFunction implements InvokableFunction {
private static final Logger s_logger = LoggerFactory.getLogger(MethodInvokableFunction.class);
/** The receiver of the method call. */
private final Object _receiver;
/** The underlying (i.e. non-proxied) receiver of the method call. */
private final Object _underlyingReceiver;
// TODO if this class needs to be serializable this needs to be stored in a different way
/** The method that is the function implementation. */
private final Method _method;
/** Method parameters keyed by name. */
private final Map<String, Parameter> _parameters;
/** The environment parameter, null if there isn't one. */
private final Parameter _environmentParameter;
/** The input parameter, null if there isn't one. */
private final Parameter _inputParameter;
/* package */ MethodInvokableFunction(Object receiver,
Map<String, Parameter> parameters,
Parameter environmentParameter,
Parameter inputParameter,
Method method) {
_environmentParameter = environmentParameter;
_method = ArgumentChecker.notNull(method, "method");
// TODO check the receiver is compatible with _declaringType
_receiver = ArgumentChecker.notNull(receiver, "receiver");
_underlyingReceiver = EngineUtils.getProxiedObject(_receiver);
_parameters = ArgumentChecker.notNull(parameters, "parameters");
_inputParameter = inputParameter;
}
@Override
public Object invoke(Environment env, Object input, FunctionArguments args) {
Object[] argArray = new Object[_parameters.size()];
if (_environmentParameter != null) {
argArray[_environmentParameter.getOrdinal()] = env;
}
if (_inputParameter != null) {
argArray[_inputParameter.getOrdinal()] = input;
}
boolean argsMissing = false;
for (Parameter parameter : _parameters.values()) {
if (parameter != _inputParameter && parameter != _environmentParameter) {
Object arg = args.getArgument(parameter.getName());
Object resolvedArg = resolveArgument(parameter, arg);
argArray[parameter.getOrdinal()] = resolvedArg;
if (resolvedArg == null && !parameter.isNullable()) {
argsMissing = true;
}
}
}
if (argsMissing) {
return missingArguments(argArray);
}
// TODO check for unexpected parameters?
try {
return _method.invoke(_receiver, argArray);
} catch (IllegalAccessException e) {
throw new OpenGammaRuntimeException("Unable to access method", e);
} catch (InvocationTargetException | IllegalArgumentException e) {
Exception cause = EngineUtils.getCause(e);
String methodName = _method.getDeclaringClass().getSimpleName() + "." + _method.getName() + "()";
s_logger.warn("Exception invoking " + methodName, cause);
return Result.failure(cause);
}
}
/**
* Returns a failure result when no arguments or null arguments are provided for non-nullable parameters.
*
* @param argArray the method arguments
* @return a failure result with an error message about missing arguments
*/
private Result<Object> missingArguments(Object[] argArray) {
Result<Object> result;
List<Parameter> missingArgs = new ArrayList<>();
for (Parameter parameter : _parameters.values()) {
Object arg = argArray[parameter.getOrdinal()];
if (arg == null && !parameter.isNullable()) {
missingArgs.add(parameter);
}
}
Collections.sort(missingArgs, new Comparator<Parameter>() {
@Override
public int compare(Parameter o1, Parameter o2) {
return Integer.compare(o1.getOrdinal(), o2.getOrdinal());
}
});
String message;
String methodName = _method.getDeclaringClass().getSimpleName() + "." + _method.getName() + "()";
if (missingArgs.size() == 1) {
Parameter parameter = missingArgs.get(0);
message = "No argument provided for non-nullable parameter for method " + methodName + ", " +
"parameter '" + parameter.getName() + "', type " + parameter.getParameterType().getName();
} else {
String messagePrefix = "No arguments provided for non-nullable parameters of method " + methodName + ", parameters ";
List<String> paramDescriptions = new ArrayList<>(missingArgs.size());
for (Parameter parameter : missingArgs) {
paramDescriptions.add(parameter.getParameterType().getName() + " " + parameter.getName());
}
message = messagePrefix + paramDescriptions;
}
result = Result.failure(FailureStatus.MISSING_ARGUMENT, message);
return result;
}
/**
* If the argument is a {@link Link} and the parameter doesn't expect a link then this method resolves and returns
* the link, otherwise it returns the argument unchanged.
*
* @param parameter The parameter corresponding to the argument
* @param arg The argument
* @return The resolved argument
*/
private static Object resolveArgument(Parameter parameter, Object arg) {
if (!(arg instanceof Link) || Link.class.isAssignableFrom(parameter.getClass())) {
// the arg isn't a link, just return it
return arg;
} else {
return ((Link<?>) arg).resolve();
}
}
@Override
public Object getReceiver() {
return _receiver;
}
@Override
public Object getUnderlyingReceiver() {
return _underlyingReceiver;
}
@Override
public Class<?> getDeclaringClass() {
return _method.getDeclaringClass();
}
}