/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.financial.analytics; import java.util.ArrayList; import java.util.Collection; import java.util.Set; import com.google.common.collect.Sets; import com.opengamma.engine.function.AbstractFunction; import com.opengamma.engine.value.ComputedValue; import com.opengamma.engine.value.ValueProperties; import com.opengamma.engine.value.ValueProperties.Builder; import com.opengamma.engine.value.ValuePropertyNames; import com.opengamma.engine.value.ValueRequirement; import com.opengamma.engine.value.ValueSpecification; /** * A base class for functions which need to preserve a set of properties from their inputs to their outputs, and also therefore need to request the appropriate inputs. * * @deprecated This is not a good way to translate constraints from the desired result to the requirements and properties from the satisfied requirements to the final results. Refer to * {@link PositionOrTradeScalingFunction} or {@link SummingFunction} for a simpler method */ @Deprecated public abstract class PropertyPreservingFunction extends AbstractFunction.NonCompiledInvoker { /** * Gets the properties which the function must preserve. If a property in this category occurs on any input then all * inputs must declare the same property value, and this will be propagated to the outputs, or the function will * fail. If no inputs declare a property then it will not appear on the outputs. * * @return the properties which the function is required to preserve, not null */ protected abstract Collection<String> getPreservedProperties(); /** * Gets the properties which the function will attempt to preserve. A property in this category will appear on the * function outputs if the property is present and has the same value across every input, otherwise it will be dropped. * * @return the properties which the function will attempt to preserve, not null */ protected abstract Collection<String> getOptionalPreservedProperties(); private ValueProperties createInputConstraints(final Collection<String> preserve) { final ValueProperties.Builder builder = ValueProperties.builder(); for (final String value : preserve) { builder.withOptional(value); } return builder.get(); } private ValueProperties createResultProperties(final Collection<String> preserve) { final ValueProperties.Builder builder = ValueProperties.builder(); for (final String value : preserve) { builder.withAny(value); } applyAdditionalResultProperties(builder); return builder.get(); } /** * Add additional properties to the results. The default here adds the function identifier; override * this to add further information, but call the superclass method if the function identifier is not * added. * * @param builder to add properties to */ protected void applyAdditionalResultProperties(final ValueProperties.Builder builder) { builder.with(ValuePropertyNames.FUNCTION, getUniqueId()); } private ValueProperties _inputConstraints; private ValueProperties _resultProperties; private ValueProperties _requiredProperties; @Override public void setUniqueId(final String identifier) { super.setUniqueId(identifier); final Collection<String> optionalProperties = getOptionalPreservedProperties(); final Collection<String> requiredProperties = getPreservedProperties(); final Collection<String> preservationCandidates = new ArrayList<String>(optionalProperties.size() + requiredProperties.size()); preservationCandidates.addAll(optionalProperties); preservationCandidates.addAll(requiredProperties); _resultProperties = createResultProperties(preservationCandidates); _requiredProperties = createInputConstraints(requiredProperties); _inputConstraints = createInputConstraints(preservationCandidates); } protected ValueProperties getInputConstraint(final ValueRequirement desiredValue) { return getInputConstraints().compose(desiredValue.getConstraints()); } protected ValueProperties getInputConstraints() { return _inputConstraints; } protected ValueProperties getResultProperties() { return _resultProperties; } private ValueProperties getResultProperties(final ValueProperties properties) { final ValueProperties.Builder builder = properties.copy().withoutAny(ValuePropertyNames.FUNCTION); applyAdditionalResultProperties(builder); return builder.get(); } /** * Produces the properties of the input value composed against the input constraints. * * @param inputSpec an input value specification * @return the composed properties */ protected ValueProperties getResultProperties(final ValueSpecification inputSpec) { return getResultProperties(inputSpec.getProperties().compose(getInputConstraints())); } /** * Produces the input constraints composed against the properties of the inputs, ensuring that any required preserved * properties are identical if present. * * @param inputs a set of input value specifications * @return the composed properties */ protected ValueProperties getResultProperties(final Collection<ValueSpecification> inputs) { //NOTE: this function must be invariant under inputs ordering if (inputs.isEmpty()) { return null; } ValueProperties compositeProperties = null; ValueProperties referenceRequiredProperties = null; for (final ValueSpecification input : inputs) { if (compositeProperties == null) { compositeProperties = composeStrict(getInputConstraints(), input.getProperties()); referenceRequiredProperties = _requiredProperties.compose(input.getProperties()); } else { final ValueProperties requiredPropertyComposition = _requiredProperties.compose(input.getProperties()); if (!requiredPropertyComposition.equals(referenceRequiredProperties)) { // Required property composition 'requiredPropertyComposition' produced from input 'input' differs from current required // property composition 'referenceRequiredProperties' implying incompatible property values among the inputs return null; } // Know that the required properties are preserved correctly, so now compose everything compositeProperties = composeStrict(compositeProperties, input.getProperties()); } } return getResultProperties(compositeProperties); } /** * Creates the intersection of two valueProperties. * Note: this behaves as ValueProperties.compose except for the clause * "Any properties defined in this set but not in the other remain untouched." * @param a some properties * @param b some properties * @return the intersection of a and b */ private ValueProperties composeStrict(final ValueProperties a, final ValueProperties b) { final ValueProperties none = ValueProperties.none(); if (none.equals(a) || none.equals(b)) { //NOTE: none has null properties return none; } //NOTE: infinite properties behave as empty final ValueProperties compose = a.compose(b); final Set<String> mismatchedProperties = Sets.symmetricDifference(a.getProperties(), b.getProperties()); return without(compose, mismatchedProperties); } private ValueProperties without(final ValueProperties compose, final Collection<String> symmetricDiff) { if (symmetricDiff.isEmpty()) { return compose; } final Builder filtered = compose.copy(); for (final String propertyName : symmetricDiff) { filtered.withoutAny(propertyName); } return filtered.get(); } protected ValueProperties getResultPropertiesFromInputs(final Collection<ComputedValue> inputs) { final Collection<ValueSpecification> specs = new ArrayList<ValueSpecification>(inputs.size()); for (final ComputedValue input : inputs) { specs.add(input.getSpecification()); } return getResultProperties(specs); } }