/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.sesame.function;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.annotation.Nullable;
import com.google.common.collect.Maps;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.sesame.Environment;
import com.opengamma.sesame.OutputName;
import com.opengamma.sesame.config.EngineUtils;
import com.opengamma.util.ArgumentChecker;
/**
* TODO joda bean?
*/
public class FunctionMetadata {
// 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;
/** The name of the output produced by the function. */
private final OutputName _outputName;
/** The types of arguments provided by the engine, e.g. trades, positions etc. */
private final Set<Class<?>> _inputTypes;
public FunctionMetadata(FunctionMetadata copyFrom) {
_method = copyFrom._method;
_parameters = copyFrom._parameters;
_environmentParameter = copyFrom._environmentParameter;
_inputParameter = copyFrom._inputParameter;
_outputName = copyFrom._outputName;
_inputTypes = copyFrom._inputTypes;
}
public FunctionMetadata(Method method) {
this(method, Collections.<Class<?>>emptySet());
}
public FunctionMetadata(Method method, Set<Class<?>> inputTypes) {
_inputTypes = inputTypes;
_method = ArgumentChecker.notNull(method, "method");
Output annotation = method.getAnnotation(Output.class);
if (annotation == null) {
throw new IllegalArgumentException("method " + method + " isn't annotated with @Output");
}
_outputName = OutputName.of(annotation.value());
FunctionParameters parameters = getParameters(method, inputTypes);
_parameters = parameters._parametersByName;
_environmentParameter = parameters._environmentParameter;
_inputParameter = parameters._inputParameter;
}
private static FunctionParameters getParameters(Method method, Set<Class<?>> inputTypes) {
Map<String, Parameter> parameterMap = Maps.newHashMap();
List<Parameter> parameters = EngineUtils.getParameters(method);
List<Parameter> inputParameters = new ArrayList<>();
List<Parameter> environmentParameters = new ArrayList<>();
for (Parameter parameter : parameters) {
parameterMap.put(parameter.getName(), parameter);
if (parameter.getType().equals(Environment.class)) {
environmentParameters.add(parameter);
} else {
Parameter inputParameter = findInputParameter(inputTypes, parameter);
if (inputParameter != null) {
inputParameters.add(inputParameter);
}
}
}
Parameter environmentParameter = getSingleParameter(environmentParameters);
Parameter inputParameter = getSingleParameter(inputParameters);
return new FunctionParameters(parameterMap, environmentParameter, inputParameter);
}
private static Parameter getSingleParameter(List<Parameter> parameters) {
if (parameters.size() > 1) {
throw new IllegalArgumentException("Multiple parameters found where only one is expected");
} else if (parameters.size() == 1) {
return parameters.get(0);
} else {
return null;
}
}
private static Parameter findInputParameter(Set<Class<?>> inputTypes, Parameter parameter) {
for (Class<?> allowedInputType : inputTypes) {
if (allowedInputType.isAssignableFrom(parameter.getType())) {
return parameter;
}
}
return null;
}
public InvokableFunction getInvokableFunction(Object receiver) {
// receiver might be a proxy. we need the real object so we can get the parameter names from the bytecode
Object realReceiver = EngineUtils.getProxiedObject(receiver);
if (_method.getDeclaringClass().isInterface()) {
Class<?> receiverClass = realReceiver.getClass();
Method method;
try {
method = receiverClass.getMethod(_method.getName(), _method.getParameterTypes());
} catch (NoSuchMethodException e) {
// this shouldn't happen - the receiver must have the same method as the proxy
throw new OpenGammaRuntimeException("Unexpected exception", e);
}
FunctionParameters parameters = getParameters(method, _inputTypes);
return new MethodInvokableFunction(receiver,
parameters._parametersByName,
parameters._environmentParameter,
parameters._inputParameter,
_method);
} else {
return new MethodInvokableFunction(receiver,
_parameters,
_environmentParameter,
_inputParameter,
_method);
}
}
public Class<?> getDeclaringType() {
return _method.getDeclaringClass();
}
public OutputName getOutputName() {
return _outputName;
}
public Class<?> getInputType() {
if (_inputParameter == null) {
return null;
} else {
return _inputParameter.getType();
}
}
@Override
public int hashCode() {
return Objects.hash(_method, _parameters, _inputParameter, _outputName);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
final FunctionMetadata other = (FunctionMetadata) obj;
return
Objects.equals(this._method, other._method) &&
Objects.equals(this._parameters, other._parameters) &&
Objects.equals(this._inputParameter, other._inputParameter) &&
Objects.equals(this._outputName, other._outputName);
}
@Override
public String toString() {
return "FunctionMetadata [" +
"_method=" + _method +
", _parameters=" + _parameters +
", _inputParameter=" + _inputParameter +
", _outputName='" + _outputName + "'" +
"]";
}
private static final class FunctionParameters {
private final Map<String, Parameter> _parametersByName;
@Nullable
private final Parameter _environmentParameter;
@Nullable
private final Parameter _inputParameter;
private FunctionParameters(Map<String, Parameter> parametersByName,
Parameter environmentParameter,
Parameter inputParameter) {
_parametersByName = parametersByName;
_environmentParameter = environmentParameter;
_inputParameter = inputParameter;
}
}
}