/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.currency;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.opengamma.engine.ComputationTarget;
import com.opengamma.engine.ComputationTargetSpecification;
import com.opengamma.engine.function.AbstractFunction;
import com.opengamma.engine.function.FunctionCompilationContext;
import com.opengamma.engine.function.FunctionExecutionContext;
import com.opengamma.engine.function.FunctionInputs;
import com.opengamma.engine.target.ComputationTargetType;
import com.opengamma.engine.value.ComputedValue;
import com.opengamma.engine.value.ValueProperties;
import com.opengamma.engine.value.ValuePropertyNames;
import com.opengamma.engine.value.ValueRequirement;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.financial.currency.CurrencyMatrixValue.CurrencyMatrixCross;
import com.opengamma.financial.currency.CurrencyMatrixValue.CurrencyMatrixFixed;
import com.opengamma.financial.currency.CurrencyMatrixValue.CurrencyMatrixValueRequirement;
import com.opengamma.timeseries.DoubleTimeSeries;
import com.opengamma.util.money.Currency;
/**
* Injects values from a {@link CurrencyMatrix} into a dependency graph to satisfy the requirements generated by {@link CurrencyMatrixLookupFunction}.
*/
public abstract class AbstractCurrencyMatrixSourcingFunction extends AbstractFunction.NonCompiledInvoker {
/**
* Property name when applied to a {@link CurrencyMatrix} target to allow the counter currency to be identified.
*/
protected static final String SOURCE_CURRENCY_PROPERTY = "Counter";
/**
* Property name when applied to a {@link CurrencyMatrix} target to allow the base currency to be identified.
*/
protected static final String TARGET_CURRENCY_PROPERTY = "Base";
/**
* Property name for tagging input values.
*/
private static final String SOURCE_CURRENCY_TAG = ValuePropertyNames.OUTPUT_RESERVED_PREFIX + SOURCE_CURRENCY_PROPERTY;
/**
* Property name for tagging input values.
*/
private static final String TARGET_CURRENCY_TAG = ValuePropertyNames.OUTPUT_RESERVED_PREFIX + TARGET_CURRENCY_PROPERTY;
private final String _valueRequirementName;
protected AbstractCurrencyMatrixSourcingFunction(final String valueRequirementName) {
_valueRequirementName = valueRequirementName;
}
protected String getValueRequirementName() {
return _valueRequirementName;
}
@Override
public ComputationTargetType getTargetType() {
return CurrencyMatrixResolver.TYPE;
}
@Override
public Set<ValueSpecification> getResults(final FunctionCompilationContext context, final ComputationTarget target) {
final String valueRequirementName = getValueRequirementName();
final ComputationTargetSpecification targetSpec = target.toSpecification();
final CurrencyMatrix matrix = (CurrencyMatrix) target.getValue();
final Collection<Currency> sourceCurrencies = matrix.getSourceCurrencies();
final Collection<Currency> targetCurrencies = matrix.getTargetCurrencies();
final Set<ValueSpecification> results = Sets.<ValueSpecification>newHashSetWithExpectedSize(sourceCurrencies.size() * targetCurrencies.size());
final ValueProperties.Builder properties = createValueProperties();
for (Currency sourceCurrency : sourceCurrencies) {
properties.withoutAny(SOURCE_CURRENCY_PROPERTY).with(SOURCE_CURRENCY_PROPERTY, sourceCurrency.getCode());
for (Currency targetCurrency : targetCurrencies) {
if (!targetCurrency.equals(sourceCurrency)) {
final CurrencyMatrixValue conversion = matrix.getConversion(sourceCurrency, targetCurrency);
if (conversion != null) {
results.add(new ValueSpecification(valueRequirementName, targetSpec, properties.withoutAny(TARGET_CURRENCY_PROPERTY).with(TARGET_CURRENCY_PROPERTY, targetCurrency.getCode()).get()));
}
}
}
}
return results;
}
protected ValueRequirement tagInput(final ValueRequirement requirement, final Currency source, final Currency target) {
return new ValueRequirement(requirement.getValueName(), requirement.getTargetReference(), requirement.getConstraints().copy().with(SOURCE_CURRENCY_TAG, source.getCode())
.withOptional(SOURCE_CURRENCY_TAG).with(TARGET_CURRENCY_TAG, target.getCode()).withOptional(TARGET_CURRENCY_TAG).get());
}
protected abstract boolean getRequirements(FunctionCompilationContext context, ValueRequirement desiredValue, CurrencyMatrix matrix, Set<ValueRequirement> requirements, Currency source,
Currency target);
@Override
public Set<ValueRequirement> getRequirements(final FunctionCompilationContext context, final ComputationTarget target, final ValueRequirement desiredValue) {
final CurrencyMatrix matrix = (CurrencyMatrix) target.getValue();
final Currency sourceCurrency = Currency.of(desiredValue.getConstraint(SOURCE_CURRENCY_PROPERTY));
final Currency targetCurrency = Currency.of(desiredValue.getConstraint(TARGET_CURRENCY_PROPERTY));
final Set<ValueRequirement> requirements = new HashSet<ValueRequirement>();
if (getRequirements(context, desiredValue, matrix, requirements, sourceCurrency, targetCurrency)) {
return requirements;
} else {
return null;
}
}
private static final class DetermineResults implements CurrencyMatrixValueVisitor<Boolean> {
private final CurrencyMatrix _matrix;
private final Collection<Currency> _sourceCurrencies;
private final Collection<Currency> _targetCurrencies;
private final Map<Currency, Map<Currency, Boolean>> _valid;
private Currency _currentSourceCurrency;
private Currency _currentTargetCurrency;
public DetermineResults(final CurrencyMatrix matrix) {
_matrix = matrix;
_sourceCurrencies = _matrix.getSourceCurrencies();
_targetCurrencies = _matrix.getTargetCurrencies();
_valid = Maps.newHashMapWithExpectedSize(_sourceCurrencies.size());
}
private void input(final Currency source, final Currency target, final Boolean status) {
Map<Currency, Boolean> target2status = _valid.get(source);
if (target2status == null) {
target2status = Maps.newHashMapWithExpectedSize(_targetCurrencies.size());
_valid.put(source, target2status);
}
target2status.put(target, status);
}
public void present(final Currency source, final Currency target) {
input(source, target, Boolean.TRUE);
}
public void missing(final Currency source, final Currency target) {
input(source, target, Boolean.FALSE);
}
public boolean hasInputFor(final Currency source, final Currency target) {
if (source.equals(target)) {
return false;
}
Map<Currency, Boolean> target2status = _valid.get(source);
if (target2status != null) {
Boolean status = target2status.get(target);
if (status != null) {
return status;
}
}
final CurrencyMatrixValue conversion = _matrix.getConversion(source, target);
if (conversion == null) {
missing(source, target);
return false;
}
_currentSourceCurrency = source;
_currentTargetCurrency = target;
return conversion.accept(this);
}
// CurrentMatrixVisitor
@Override
public Boolean visitFixed(CurrencyMatrixFixed fixedValue) {
present(_currentSourceCurrency, _currentTargetCurrency);
return Boolean.TRUE;
}
@Override
public Boolean visitValueRequirement(CurrencyMatrixValueRequirement uniqueId) {
return Boolean.FALSE;
}
@Override
public Boolean visitCross(CurrencyMatrixCross cross) {
final Currency sourceCurrency = _currentSourceCurrency;
final Currency targetCurrency = _currentTargetCurrency;
// Declare as missing to avoid a loop forming
missing(sourceCurrency, targetCurrency);
boolean result = hasInputFor(sourceCurrency, cross.getCrossCurrency()) && hasInputFor(cross.getCrossCurrency(), targetCurrency);
if (result) {
// Is present after all
present(sourceCurrency, targetCurrency);
}
return result;
}
}
@Override
public Set<ValueSpecification> getResults(final FunctionCompilationContext context, final ComputationTarget target, final Map<ValueSpecification, ValueRequirement> inputs) {
final String valueRequirementName = getValueRequirementName();
final ComputationTargetSpecification targetSpec = target.toSpecification();
final CurrencyMatrix matrix = (CurrencyMatrix) target.getValue();
final DetermineResults resultBuilder = new DetermineResults(matrix);
for (ValueRequirement input : inputs.values()) {
final Currency sourceCurrency = Currency.of(input.getConstraint(SOURCE_CURRENCY_TAG));
final Currency targetCurrency = Currency.of(input.getConstraint(TARGET_CURRENCY_TAG));
resultBuilder.present(sourceCurrency, targetCurrency);
}
final Set<ValueSpecification> results = Sets.newHashSetWithExpectedSize(resultBuilder._sourceCurrencies.size() * resultBuilder._targetCurrencies.size());
final ValueProperties.Builder properties = createValueProperties();
for (Currency sourceCurrency : resultBuilder._sourceCurrencies) {
properties.withoutAny(SOURCE_CURRENCY_PROPERTY).with(SOURCE_CURRENCY_PROPERTY, sourceCurrency.getCode());
for (Currency targetCurrency : resultBuilder._targetCurrencies) {
if (resultBuilder.hasInputFor(sourceCurrency, targetCurrency)) {
results.add(new ValueSpecification(valueRequirementName, targetSpec, properties.withoutAny(TARGET_CURRENCY_PROPERTY).with(TARGET_CURRENCY_PROPERTY, targetCurrency.getCode()).get()));
}
}
}
return results;
}
@Override
public boolean canHandleMissingInputs() {
return true;
}
protected abstract Object getRate(CurrencyMatrix matrix, ValueRequirement desiredValue, FunctionExecutionContext executionContext, FunctionInputs inputs, Currency source, Currency target);
@Override
public Set<ComputedValue> execute(final FunctionExecutionContext executionContext, final FunctionInputs inputs, final ComputationTarget target, final Set<ValueRequirement> desiredValues) {
final CurrencyMatrix matrix = (CurrencyMatrix) target.getValue();
final Set<ComputedValue> result = Sets.newHashSetWithExpectedSize(desiredValues.size());
for (ValueRequirement desiredValue : desiredValues) {
final Currency sourceCurrency = Currency.of(desiredValue.getConstraint(SOURCE_CURRENCY_PROPERTY));
final Currency targetCurrency = Currency.of(desiredValue.getConstraint(TARGET_CURRENCY_PROPERTY));
final ValueSpecification valueSpec = new ValueSpecification(desiredValue.getValueName(), target.toSpecification(), desiredValue.getConstraints());
final Object resultValue = getRate(matrix, desiredValue, executionContext, inputs, sourceCurrency, targetCurrency);
if (resultValue != null) {
result.add(new ComputedValue(valueSpec, resultValue));
}
}
return result;
}
protected static Object createCrossRate(final Object r1, final Object r2) {
if ((r1 == null) || (r2 == null)) {
// Missing input case; reported elsewhere
return null;
}
if (r1 instanceof Double) {
if (r2 instanceof Double) {
return (Double) r1 * (Double) r2;
} else if (r2 instanceof DoubleTimeSeries) {
return ((DoubleTimeSeries<?>) r2).multiply((Double) r1);
} else {
throw new IllegalArgumentException();
}
} else if (r1 instanceof DoubleTimeSeries) {
if (r2 instanceof Double) {
return ((DoubleTimeSeries<?>) r1).multiply((Double) r2);
} else if (r2 instanceof DoubleTimeSeries) {
return ((DoubleTimeSeries<?>) r1).multiply((DoubleTimeSeries<?>) r2);
} else {
throw new IllegalArgumentException();
}
} else {
throw new IllegalArgumentException();
}
}
}