/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.engine.function.resolver;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.opengamma.engine.ComputationTarget;
import com.opengamma.engine.ComputationTargetSpecification;
import com.opengamma.engine.function.CompiledFunctionDefinition;
import com.opengamma.engine.function.FunctionCompilationContext;
import com.opengamma.engine.function.ParameterizedFunction;
import com.opengamma.engine.target.ComputationTargetReference;
import com.opengamma.engine.target.ComputationTargetType;
import com.opengamma.engine.target.ComputationTargetTypeVisitor;
import com.opengamma.engine.value.ValueProperties;
import com.opengamma.engine.value.ValueRequirement;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.id.UniqueId;
import com.opengamma.id.UniqueIdentifiable;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.PublicAPI;
/**
* Advertises a function to a {@link CompiledFunctionResolver}.
*/
@PublicAPI
public class ResolutionRule {
private static final Logger s_logger = LoggerFactory.getLogger(ResolutionRule.class);
/**
* The parameterized function.
*/
private final ParameterizedFunction _parameterizedFunction;
/**
* The target filter.
*/
private final ComputationTargetFilter _computationTargetFilter;
/**
* The priority.
*/
private final int _priority;
/**
* Creates an instance.
*
* @param function the function, not null
* @param computationTargetFilter the filter, not null
* @param priority the priority
*/
public ResolutionRule(ParameterizedFunction function, ComputationTargetFilter computationTargetFilter, int priority) {
ArgumentChecker.notNull(function, "function");
ArgumentChecker.notNull(computationTargetFilter, "computationTargetFilter");
_parameterizedFunction = function;
_computationTargetFilter = computationTargetFilter;
_priority = priority;
}
/**
* Gets the parameterized function.
*
* @return the function and behavioral parameters this rule is advertising, not null
*/
public ParameterizedFunction getParameterizedFunction() {
return _parameterizedFunction;
}
/**
* Gets the filter that the rule uses.
*
* @return the filter in use, not null
*/
public ComputationTargetFilter getComputationTargetFilter() {
return _computationTargetFilter;
}
/**
* Gets the priority of the rule. If multiple rules can produce a given output, the one with the highest priority is chosen.
*
* @return the priority
*/
public int getPriority() {
return _priority;
}
/**
* The function advertised by this rule can validly produce the desired output only if:
* <ol>
* <li>The function is declared as applying to the target; and
* <li>The function can produce the output; and
* <li>This resolution rule applies to the given computation target
* </ol>
* <p>
* The implementation has been split into two accessible components to allow a resolver to cache the intermediate results. This is more efficient than repeated calls to this method.
*
* @param valueName The output value to be produced
* @param target Computation target
* @param constraints The constraints that must be satisfied on the produced value
* @param context Function compilation context
* @return Null if this the function advertised by this rule cannot produce the desired output, a valid ValueSpecification otherwise - as returned by the function. The specification is not composed
* against the requirement constraints.
*/
public ValueSpecification getResult(String valueName, ComputationTarget target, ValueProperties constraints, FunctionCompilationContext context) {
final Set<ValueSpecification> resultSpecs = getResults(target, context);
if (resultSpecs == null) {
return null;
}
return getResult(valueName, target, constraints, resultSpecs);
}
/**
* The first half of the full {@link #getResult(ValueRequirement,ComputationTarget,FunctionCompilationContext)} implementation returning the set of all function outputs for use by
* {@link #getResult(ValueRequirement,ComputationTarget,FunctionCompilationContext,Set)}.
*
* @param target the computation target
* @param context Function compilation context
* @return the set of all value specifications produced by the function, null if none can be produced
*/
public Set<ValueSpecification> getResults(final ComputationTarget target, final FunctionCompilationContext context) {
final CompiledFunctionDefinition function = _parameterizedFunction.getFunction();
// check the function can apply to the target
//DebugUtils.canApplyTo_enter();
if (!function.canApplyTo(context, target)) {
//DebugUtils.canApplyTo_leave();
return null;
}
//DebugUtils.canApplyTo_leave();
// return the maximal set of results the function can produce for the target
//DebugUtils.getResults1_enter();
final Set<ValueSpecification> results = function.getResults(context, target);
//DebugUtils.getResults1_leave();
assert isValidResultsOnTarget(target, results) : "Results " + results + " not valid for target " + target;
return results;
}
/**
* Tests whether two unique identifiers are sufficiently equal. The object identifiers of each must match. Either may omit the version, but if both specify versions then the versions must also
* match.
*
* @param a the first identifier to compare, not null
* @param b the second identifier to compare, not null
*/
private boolean isUidMatch(final UniqueId a, final UniqueId b) {
if (!a.getScheme().equals(b.getScheme()) || !a.getValue().equals(b.getValue())) {
// Object identifiers don't match
return false;
}
if ((a.getVersion() == null) || (b.getVersion() == null)) {
// Loose versioning is okay
return true;
}
// Strict versioning
return a.getVersion().equals(b.getVersion());
}
private boolean isValidResultsOnTarget(final ComputationTarget target, final Set<ValueSpecification> results) {
if (results == null) {
return true;
}
final UniqueId uid = target.getUniqueId();
if (uid != null) {
ComputationTargetSpecification targetSpec = target.toSpecification();
for (ValueSpecification result : results) {
if (!isUidMatch(uid, result.getTargetSpecification().getUniqueId())) {
s_logger.warn("Invalid UID for result {} on target {}", result, target);
return false;
}
ComputationTargetReference a = result.getTargetSpecification().getParent();
ComputationTargetReference b = targetSpec.getParent();
while ((a != null) && (b != null)) {
if (!isUidMatch(a.getSpecification().getUniqueId(), b.getSpecification().getUniqueId())) {
s_logger.warn("Parent context mismatch of result {} on target {}", result, target);
return false;
}
a = a.getParent();
b = b.getParent();
}
if ((a != null) || (b != null)) {
s_logger.warn("Invalid parent context of result {} on target {}", result, target);
return false;
}
}
} else {
for (ValueSpecification result : results) {
if (result.getTargetSpecification().getUniqueId() != null) {
s_logger.warn("Invalid result {} on null target {}", result, target);
return false;
}
}
}
return true;
}
/**
* The second half of the full {@link #getResult(ValueRequirement, ComputationTarget, FunctionCompilationContext)}) implementation taking the set of all function outputs produced by
* {@link #getResults}.
*
* @param valueName output value name to be produced, not null and interned
* @param target Computation target, not null
* @param constraints the constraints that must be satisfied, not null
* @param resultSpecs The results from {@code getResults()}, not null
* @return Null if the function advertised by this rule cannot produce the desired output, a valid ValueSpecification otherwise - as returned by the function. The specification is not composed
* against the requirement constraints.
*/
public ValueSpecification getResult(final String valueName, final ComputationTarget target, final ValueProperties constraints, final Collection<ValueSpecification> resultSpecs) {
// Of the maximal outputs, is one valid for the requirement
ValueSpecification validSpec = null;
final UniqueId targetId = target.getUniqueId();
if (targetId != null) {
for (ValueSpecification resultSpec : resultSpecs) {
//s_logger.debug("Considering {} for {}", resultSpec, output);
if ((valueName == resultSpec.getValueName())
&& isUidMatch(targetId, resultSpec.getTargetSpecification().getUniqueId()) // This is not necessary if functions are well behaved or the "isValidResultsOnTarget" check was used
&& constraints.isSatisfiedBy(resultSpec.getProperties())) {
validSpec = resultSpec;
break;
}
}
} else {
for (ValueSpecification resultSpec : resultSpecs) {
if ((valueName == resultSpec.getValueName())
&& (resultSpec.getTargetSpecification().getUniqueId() == null) // This is not necessary if functions are well behaved or the "isValidResultsOnTarget" check was used
&& constraints.isSatisfiedBy(resultSpec.getProperties())) {
validSpec = resultSpec;
break;
}
}
}
if (validSpec == null) {
return null;
}
// Apply the target filter for this rule (this is applied last because filters probably rarely exclude compared to the other tests)
if (!_computationTargetFilter.accept(target)) {
return null;
}
return validSpec;
}
@Override
public String toString() {
return "ResolutionRule[" + getParameterizedFunction() + " at priority " + getPriority() + "]";
}
private static ComputationTargetTypeVisitor<Void, List<ComputationTargetType>> s_getNestedTargetTypes = new ComputationTargetTypeVisitor<Void, List<ComputationTargetType>>() {
@Override
public List<ComputationTargetType> visitMultipleComputationTargetTypes(final Set<ComputationTargetType> types, final Void unused) {
// Multiple target types will have been removed during resolution
throw new IllegalStateException();
}
@Override
public List<ComputationTargetType> visitNestedComputationTargetTypes(final List<ComputationTargetType> targetTypes, final Void unused) {
return targetTypes;
}
@Override
public List<ComputationTargetType> visitNullComputationTargetType(final Void unused) {
// NULL should not be getting this far
throw new IllegalStateException();
}
@Override
public List<ComputationTargetType> visitClassComputationTargetType(final Class<? extends UniqueIdentifiable> type, final Void unused) {
// Single class is not compatible with the rule
return null;
}
};
private static ComputationTargetTypeVisitor<ComputationTarget, ComputationTargetType> s_getAdjustedTargetType = new ComputationTargetTypeVisitor<ComputationTarget, ComputationTargetType>() {
@Override
public ComputationTargetType visitMultipleComputationTargetTypes(final Set<ComputationTargetType> types, final ComputationTarget target) {
for (ComputationTargetType type : types) {
final ComputationTargetType adjusted = type.accept(this, target);
if (adjusted == ComputationTargetType.NULL) {
return type;
} else if (adjusted != null) {
return adjusted;
}
}
// Target not compatible
return null;
}
@Override
public ComputationTargetType visitNestedComputationTargetTypes(final List<ComputationTargetType> types, final ComputationTarget target) {
final List<ComputationTargetType> targetTypes = target.getType().accept(s_getNestedTargetTypes, null);
if (targetTypes == null) {
// Target not compatible
return null;
}
final int length = targetTypes.size();
if (length < types.size()) {
// Target does not match the type - not enough context
return null;
}
final ComputationTargetType leafType = targetTypes.get(length - 1);
final ComputationTargetType adjustedLeaf = leafType.accept(this, target);
if (adjustedLeaf == null) {
// Target not compatible at leaf type
return null;
}
if (adjustedLeaf == ComputationTargetType.NULL) {
if (length == types.size()) {
// Exact match
return ComputationTargetType.NULL;
}
}
int i = length - types.size();
ComputationTargetType type = targetTypes.get(i);
while (++i < length - 1) {
type = type.containing(targetTypes.get(i));
}
return type.containing(adjustedLeaf);
}
@Override
public ComputationTargetType visitNullComputationTargetType(final ComputationTarget target) {
return ComputationTargetType.NULL;
}
@Override
public ComputationTargetType visitClassComputationTargetType(final Class<? extends UniqueIdentifiable> type, final ComputationTarget target) {
final Class<? extends UniqueIdentifiable> clazz = target.getValue().getClass();
if (type.equals(clazz)) {
// Target type is correct
return ComputationTargetType.NULL;
} else if (type.isAssignableFrom(clazz)) {
// Target type is the target's leaf type
return target.getLeafSpecification().getType();
} else {
// Target is not compatible with the function type
return null;
}
}
};
/**
* Adjusts the type on the target to match the declared type of the function.
* <p>
* Examples:
* <ul>
* <li>The function declares SECURITY as its type and a target is of sub-class FinancialSecurity then the target type remains FinancialSecurity
* <li>The function declares POSITION|TRADE as its type and a target is of PORTFOLIO_NODE/SimplePosition then it is rewritten to SimplePosition (the matching leaf, not the full union type)
* </ul>
*
* @param type the declared type to reduce the target to, not null
* @param target the target to reduce, not null
* @return the reduced target, or null if the reduction is not possible
*/
public static ComputationTarget adjustTarget(final ComputationTargetType type, final ComputationTarget target) {
final ComputationTargetType adjusted = type.accept(s_getAdjustedTargetType, target);
if (adjusted == null) {
// Not compatible
return null;
} else if (adjusted == ComputationTargetType.NULL) {
// Exact match
return target;
} else {
// Type replacement
return new ComputationTarget(target.toSpecification().replaceType(adjusted).getSpecification(), target.getValue());
}
}
/**
* Adjusts the type on the target to match the declared type of the function.
* <p>
* Examples:
* <ul>
* <li>The function declares SECURITY as its type and a target is of sub-class FinancialSecurity then the target type remains FinancialSecurity
* <li>The function declares POSITION|TRADE as its type and a target is of PORTFOLIO_NODE/SimplePosition then it is rewritten to SimplePosition (the matching leaf, not the full union type)
* </ul>
*
* @param target the target to reduce, not null
* @return the reduced target, or null if the target is not compatible with the rule
*/
public ComputationTarget adjustTarget(final ComputationTarget target) {
return adjustTarget(getParameterizedFunction().getFunction().getTargetType(), target);
}
/**
* Adjusts the type on the target to match the declared type of the function.
* <p>
* Examples:
* <ul>
* <li>The function declares SECURITY as its type and a target is of sub-class FinancialSecurity then the target type remains FinancialSecurity
* <li>The function declares POSITION|TRADE as its type and a target is of PORTFOLIO_NODE/SimplePosition then it is rewritten to SimplePosition (the matching leaf, not the full union type)
* </ul>
*
* @param adjustmentCache a cache of targets already adjusted to certain types, not null
* @param target the target to reduce, not null
* @return the reduced target, or null if the target is not compatible with the rule
*/
public ComputationTarget adjustTarget(final Map<ComputationTargetType, ComputationTarget> adjustmentCache, final ComputationTarget target) {
final ComputationTargetType type = getParameterizedFunction().getFunction().getTargetType();
if (ComputationTargetType.NULL.equals(type)) {
// We use NULL to mark failure in the cache, and NULL will never need adjusting
return target;
}
ComputationTarget adjusted = adjustmentCache.get(type);
if (adjusted == null) {
adjusted = adjustTarget(type, target);
if (adjusted != null) {
adjustmentCache.put(type, adjusted);
} else {
// Store the failure
adjustmentCache.put(type, ComputationTarget.NULL);
}
} else {
if (ComputationTargetType.NULL.equals(adjusted.getType())) {
// Got a failure from the cache
adjusted = null;
}
}
return adjusted;
}
}