/**
* Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.engine.function.resolver;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Maps;
import com.opengamma.engine.ComputationTarget;
import com.opengamma.engine.ComputationTargetResolver;
import com.opengamma.engine.ComputationTargetSpecification;
import com.opengamma.engine.function.CompiledFunctionDefinition;
import com.opengamma.engine.function.FunctionCompilationContext;
import com.opengamma.engine.function.FunctionCompilationContextAware;
import com.opengamma.engine.target.ComputationTargetResolverUtils;
import com.opengamma.engine.target.ComputationTargetSpecificationResolver;
import com.opengamma.engine.target.ComputationTargetType;
import com.opengamma.engine.value.ValueProperties;
import com.opengamma.engine.value.ValueRequirement;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.PublicAPI;
/**
* Service to interrogate the results available on a computation target.
*/
@PublicAPI
public class ComputationTargetResults implements FunctionCompilationContextAware {
private static final Logger s_logger = LoggerFactory.getLogger(ComputationTargetResults.class);
/**
* The resolution rules to use, in descending priority order.
*/
private final List<ResolutionRule> _rules;
/**
* The compilation context to use for recursive query. Typically this should be
* the context this service is part of. On construction, that context is cloned
* so that the service can be removed from the copy. This prevents functions
* that are defined in terms of the available outputs of others from being caught
* in an infinite loop of recursive calls.
*/
private FunctionCompilationContext _context;
/**
* Creates a new instance.
*
* @param rules the resolution rules to use, not null
*/
public ComputationTargetResults(final Collection<ResolutionRule> rules) {
ArgumentChecker.notNull(rules, "rules");
_rules = new ArrayList<ResolutionRule>(rules);
Collections.sort(_rules, new Comparator<ResolutionRule>() {
@Override
public int compare(final ResolutionRule o1, final ResolutionRule o2) {
if (o1.getPriority() > o2.getPriority()) {
return -1;
} else if (o1.getPriority() < o2.getPriority()) {
return 1;
} else {
return 0;
}
}
});
}
@Override
public void setFunctionCompilationContext(final FunctionCompilationContext context) {
if (_context == null) {
_context = context.clone();
_context.setComputationTargetResults(null);
}
}
/**
* Gets the list of resolution rules, in descending priority order.
*
* @return the rules, not null
*/
protected List<ResolutionRule> getRules() {
return _rules;
}
/**
* Gets the function compilation context (lacking this service).
*
* @return the context, not null
*/
protected FunctionCompilationContext getContext() {
return _context;
}
/**
* Gets the target resolver to use for partial result resolution.
*
* @return the target resolver, not null
*/
protected ComputationTargetResolver.AtVersionCorrection getTargetResolver() {
return getContext().getComputationTargetResolver();
}
/**
* Gets the specification resolver to use for targets specified by external identifier only.
*
* @return the
*/
protected ComputationTargetSpecificationResolver.AtVersionCorrection getTargetSpecificationResolver() {
return getContext().getComputationTargetResolver().getSpecificationResolver();
}
/**
* Returns the maximal result sets from all functions on the given target. The results
* are presented in the descending priority order of the rules that produced them.
*
* @param target the target to get results for, not null
* @return the list of maximal results, not null
*/
public List<ValueSpecification> getMaximalResults(final ComputationTarget target) {
final Set<ValueSpecification> result = new LinkedHashSet<ValueSpecification>();
for (final ResolutionRule rule : getRules()) {
if (rule.getParameterizedFunction().getFunction().getTargetType().isCompatible(target.getType())) {
final Set<ValueSpecification> results = rule.getResults(target, getContext());
if (results != null) {
result.addAll(results);
}
}
}
s_logger.info("Maximal results for {} = {}", target, result);
return new ArrayList<ValueSpecification>(result);
}
/**
* Returns the partially resolved result sets from all functions on the given target.
* The results are presented in the descending priority order of the rules that produced
* them.
* <p>
* This differs from {@link #getMaximalResults} by following the requirements of any
* functions that produce non-finite properties on their maximal outputs.
*
* @param target the target to get results for, not null
* @return the list of partially resolved results, not null
*/
public List<ValueSpecification> getPartialResults(final ComputationTarget target) {
final Map<ComputationTargetType, ComputationTarget> adjustedTargetCache = new HashMap<ComputationTargetType, ComputationTarget>();
final Set<ValueSpecification> result = new LinkedHashSet<ValueSpecification>();
for (final ResolutionRule rule : getRules()) {
final CompiledFunctionDefinition function = rule.getParameterizedFunction().getFunction();
if (!function.getTargetType().isCompatible(target.getType())) {
continue;
}
final ComputationTarget adjustedTarget = rule.adjustTarget(adjustedTargetCache, target);
final Set<ValueSpecification> results;
try {
results = rule.getResults(adjustedTarget, getContext());
if (results == null) {
continue;
}
} catch (final Throwable t) {
s_logger.warn("Couldn't call getResults on {} - {}", rule, t);
s_logger.debug("Caught exception", t);
continue;
}
//CSOFF
resultsLoop:
//CSON
for (final ValueSpecification spec : results) {
if (!spec.getProperties().getProperties().isEmpty()) {
result.add(spec);
continue resultsLoop;
}
final ValueSpecification resolvedSpec = resolvePartialSpecification(spec, adjustedTarget, function, new HashSet<ValueRequirement>(), adjustedTargetCache, ValueProperties.none());
if (resolvedSpec != null) {
result.add(resolvedSpec);
}
}
}
s_logger.info("Maximal results for {} = {}", target, result);
return new ArrayList<ValueSpecification>(result);
}
/**
* Attempts partial resolution of a requirement. Requirement chains are followed until a specification is found with finite properties.
*
* @param requirement requirement to resolve, not null
* @param visited requirements visited so far, to detect recursion, not null
* @param adjustedTargetCache cache of adjusted targets, keyed by the target function type, not null
* @return the resolved specification, or null if it couldn't be resolved
*/
protected ValueSpecification resolvePartialRequirement(final ValueRequirement requirement, final Set<ValueRequirement> visited,
final Map<ComputationTargetType, ComputationTarget> adjustedTargetCache) {
if (!visited.add(requirement)) {
s_logger.debug("Recursive request for {}", requirement);
return null;
}
final ComputationTargetSpecification targetSpec = getTargetSpecificationResolver().getTargetSpecification(
ComputationTargetResolverUtils.simplifyType(requirement.getTargetReference(), getTargetResolver()));
final ComputationTarget target = getTargetResolver().resolve(targetSpec);
if (target == null) {
s_logger.debug("Couldn't resolve target for {}", requirement);
visited.remove(requirement);
return null;
}
s_logger.debug("Partially resolving {}", requirement);
for (final ResolutionRule rule : getRules()) {
final CompiledFunctionDefinition function = rule.getParameterizedFunction().getFunction();
if (!function.getTargetType().isCompatible(target.getType())) {
continue;
}
final ComputationTarget adjustedTarget = rule.adjustTarget(adjustedTargetCache, target);
final ValueSpecification result;
try {
result = rule.getResult(requirement.getValueName(), adjustedTarget, requirement.getConstraints(), getContext());
if (result == null) {
continue;
}
} catch (final Throwable t) {
s_logger.warn("Couldn't call getResult on {} - {}", rule, t);
s_logger.debug("Caught exception", t);
continue;
}
if (!result.getProperties().getProperties().isEmpty()) {
s_logger.debug("Partial resolution of {} to {}", requirement, result);
visited.remove(requirement);
return result;
}
final ValueSpecification resolvedResult = resolvePartialSpecification(result, adjustedTarget, function, visited, adjustedTargetCache, requirement.getConstraints());
if (resolvedResult != null) {
s_logger.debug("Partial resolution of {} to {}", requirement, resolvedResult);
visited.remove(requirement);
return resolvedResult;
}
}
s_logger.debug("Couldn't resolve {}", requirement);
visited.remove(requirement);
return null;
}
/**
* Attempts partial resolution of a non-finite specification produced as part of a function's maximal outputs. Requirement chains are followed until a specification is found with finite properties.
*
* @param specification maximal output specification, non-finite properties, not null
* @param target computation target the function is to operate on, not null
* @param function function to apply, not null
* @param visited requirements visited so far, to detect recursion, not null
* @param adjustedTarget cache of adjusted targets, keyed by the target function type, not null
* @param constraints requirement constraints, not null
* @return the partially resolved specification, or null if resolution is not possible
*/
protected ValueSpecification resolvePartialSpecification(final ValueSpecification specification, final ComputationTarget target, final CompiledFunctionDefinition function,
final Set<ValueRequirement> visited, final Map<ComputationTargetType, ComputationTarget> adjustedTarget, final ValueProperties constraints) {
final Set<ValueRequirement> reqs;
try {
reqs = function.getRequirements(getContext(), target, new ValueRequirement(specification.getValueName(), specification.getTargetSpecification(), constraints));
if (reqs == null) {
return null;
}
} catch (final Throwable t) {
s_logger.warn("Couldn't call getRequirements on {} - {}", function, t);
s_logger.debug("Caught exception", t);
return null;
}
s_logger.debug("Need partial resolution of {} to continue", reqs);
final Map<ValueSpecification, ValueRequirement> resolved = Maps.newHashMapWithExpectedSize(reqs.size());
for (final ValueRequirement req : reqs) {
// TODO: need to call "simplify type" on requirement
final ValueSpecification resolvedReq = resolvePartialRequirement(req, visited, adjustedTarget);
if (resolvedReq == null) {
return null;
}
resolved.put(resolvedReq, req);
}
final Set<ValueSpecification> lateResults;
try {
lateResults = function.getResults(getContext(), target, resolved);
if ((lateResults == null) || lateResults.isEmpty()) {
return null;
}
} catch (final Throwable t) {
s_logger.warn("Couldn't call getResults on {} - {}", function, t);
s_logger.debug("Caught exception", t);
return null;
}
for (final ValueSpecification lateSpec : lateResults) {
if (!lateSpec.getProperties().getProperties().isEmpty()) {
s_logger.debug("Deep resolution of {} to {}", specification, lateSpec);
return lateSpec;
}
}
return null;
}
}