/** * 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(); } } }