/**
* 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.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.opengamma.core.position.Position;
import com.opengamma.core.position.Trade;
import com.opengamma.core.security.Security;
import com.opengamma.sesame.OutputName;
import com.opengamma.sesame.config.EngineUtils;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.tuple.Pair;
import com.opengamma.util.tuple.Pairs;
/**
*
*/
public class AvailableOutputsImpl implements AvailableOutputs {
private static final Set<Class<?>> s_defaultInputTypes =
ImmutableSet.<Class<?>>of(Trade.class, Position.class, Security.class);
private final Set<Class<?>> _inputTypes;
/** Output names registered for an input type. */
private final Map<Class<?>, Set<OutputName>> _outputsByInputType = Maps.newHashMap();
/** Map of output name / target type to the function type that provides it. */
private final Map<Pair<OutputName, Class<?>>, FunctionMetadata> _functionsForOutputs = Maps.newHashMap();
/**
* The same as {@link #_functionsForOutputs} but includes the function types for the target type's supertypes.
* This is lazily populated by walking up the type hierarchy from the target type querying
* {@link #_functionsForOutputs}.
*/
private final Map<Pair<OutputName, Class<?>>, FunctionMetadata> _allFunctionsForOutputs = Maps.newHashMap();
/**
* All output names available for a target type. This is lazily populated by walking up the type hierarchy from
* the target type querying {@link #_outputsByInputType}.
*/
private final Map<Class<?>, Set<OutputName>> _allOutputsByInputType = Maps.newHashMap();
private final Map<OutputName, FunctionMetadata> _nonPortfolioFunctions = Maps.newHashMap();
/**
* Creates an instance that expects {@link Trade}, {@link Position} or {@link Security} instances as inputs.
*/
public AvailableOutputsImpl() {
this(s_defaultInputTypes);
}
/**
* Creates an instance that expects the specified types as inputs.
* @param inputTypes the expected input types
*/
public AvailableOutputsImpl(Class<?>... inputTypes) {
_inputTypes = ImmutableSet.copyOf(inputTypes);
}
/**
* Creates an instance that expects the specified types as inputs.
* @param inputTypes the expected input types
*/
public AvailableOutputsImpl(Set<Class<?>> inputTypes) {
_inputTypes = ImmutableSet.copyOf(ArgumentChecker.notNull(inputTypes, "inputTypes"));
}
@Override
public Set<Class<?>> getInputTypes(OutputName outputName) {
// TODO need the reverse of _outputsByInputType
throw new UnsupportedOperationException("getInputTypes not implemented");
}
@Override
public synchronized Set<Class<?>> getInputTypes() {
return _outputsByInputType.keySet();
}
/**
* Returns the names of all outputs available for a target type
* @param inputType The type of the target
* @return All outputs that can be calculated for the target type
*/
@Override
public synchronized Set<OutputName> getAvailableOutputs(Class<?> inputType) {
if (_allOutputsByInputType.containsKey(inputType)) {
return _allOutputsByInputType.get(inputType);
}
Set<Class<?>> supertypes = EngineUtils.getSupertypes(inputType);
Set<OutputName> outputs = Sets.newTreeSet();
for (Class<?> supertype : supertypes) {
if (_outputsByInputType.containsKey(supertype)) {
outputs.addAll(_outputsByInputType.get(supertype));
}
}
_allOutputsByInputType.put(inputType, outputs);
return Collections.unmodifiableSet(outputs);
}
@Override
public Set<OutputName> getAvailableOutputs() {
// TODO implement getAvailableOutputs()
throw new UnsupportedOperationException("getAvailableOutputs not implemented");
}
@Override
public synchronized FunctionMetadata getOutputFunction(OutputName outputName, Class<?> inputType) {
ArgumentChecker.notNull(outputName, "outputName");
ArgumentChecker.notNull(inputType, "inputType");
Pair<OutputName, Class<?>> targetKey = Pairs.<OutputName, Class<?>>of(outputName, inputType);
if (_allFunctionsForOutputs.containsKey(targetKey)) {
return _allFunctionsForOutputs.get(targetKey);
}
Set<Class<?>> supertypes = EngineUtils.getSupertypes(inputType);
for (Class<?> supertype : supertypes) {
Pair<OutputName, Class<?>> key = Pairs.<OutputName, Class<?>>of(outputName, supertype);
if (_functionsForOutputs.containsKey(key)) {
FunctionMetadata function = _functionsForOutputs.get(key);
_allFunctionsForOutputs.put(targetKey, function);
return function;
}
}
return null;
}
@Override
public synchronized FunctionMetadata getOutputFunction(OutputName outputName) {
return _nonPortfolioFunctions.get(outputName);
}
@Override
public synchronized void register(Class<?>... functionInterfaces) {
for (Class<?> functionInterface : functionInterfaces) {
List<FunctionMetadata> functions = getOutputFunctions(functionInterface);
for (FunctionMetadata function : functions) {
OutputName outputName = function.getOutputName();
Set<OutputName> outputNames;
Class<?> targetType = function.getInputType();
if (targetType != null) { // portfolio output
if (_outputsByInputType.containsKey(targetType)) {
_outputsByInputType.get(targetType).add(outputName);
} else {
outputNames = Sets.newHashSet(outputName);
_outputsByInputType.put(targetType, outputNames);
}
_functionsForOutputs.put(Pairs.<OutputName, Class<?>>of(outputName, targetType), function);
} else { // non-portfolio output
_nonPortfolioFunctions.put(outputName, function);
}
}
}
}
private List<FunctionMetadata> getOutputFunctions(Class<?> type) {
List<FunctionMetadata> functions = Lists.newArrayList();
for (Method method : type.getMethods()) {
if (method.isAnnotationPresent(Output.class)) {
FunctionMetadata function = new FunctionMetadata(method, _inputTypes);
functions.add(function);
}
}
// TODO check that there aren't any clashes between constructor and method param names
// shouldn't matter if two methods have the same param names, the engine will only be calling one
return functions;
}
}