/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.engine.depgraph.ambiguity;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.threeten.bp.Instant;
import com.google.common.collect.MapMaker;
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.MarketDataSourcingFunction;
import com.opengamma.engine.function.ParameterizedFunction;
import com.opengamma.engine.function.exclusion.FunctionExclusionGroup;
import com.opengamma.engine.function.exclusion.FunctionExclusionGroups;
import com.opengamma.engine.function.resolver.ComputationTargetResults;
import com.opengamma.engine.function.resolver.ResolutionRule;
import com.opengamma.engine.marketdata.availability.MarketDataAvailabilityProvider;
import com.opengamma.engine.target.ComputationTargetType;
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.engine.view.ViewCalculationConfiguration;
import com.opengamma.id.UniqueId;
import com.opengamma.id.VersionCorrection;
import com.opengamma.util.ArgumentChecker;
/**
* Basic implementation of {@link RequirementAmbiguityChecker} that operates in a single (the caller's) thread.
*/
public class SimpleRequirementAmbiguityChecker implements RequirementAmbiguityChecker {
private static final Logger s_logger = LoggerFactory.getLogger(SimpleRequirementAmbiguityChecker.class);
private static final ParameterizedFunction MARKET_DATA_SOURCING_FUNCTION = createParameterizedFunction(MarketDataSourcingFunction.INSTANCE);
private static ParameterizedFunction createParameterizedFunction(final CompiledFunctionDefinition function) {
return new ParameterizedFunction(function, function.getFunctionDefinition().getDefaultParameters());
}
private final MarketDataAvailabilityProvider _mdap;
private final FunctionExclusionGroups _exclusions;
private final FunctionCompilationContext _compilationContext;
private final ResolutionRule[][] _rules;
private final ConcurrentMap<ComputationTargetType, ResolutionRule[][]> _rulesByType = new ConcurrentHashMap<ComputationTargetType, ResolutionRule[][]>();
private boolean _greedyCaching;
private ConcurrentMap<?, FullRequirementResolution> _sharedCaching;
public SimpleRequirementAmbiguityChecker(final AmbiguityCheckerContext context, final Instant valuationTime, VersionCorrection resolverVersionCorrection) {
ArgumentChecker.notNull(context, "context");
ArgumentChecker.notNull(valuationTime, "valuationTime");
resolverVersionCorrection = ArgumentChecker.notNull(resolverVersionCorrection, "resolverVersionCorrection").withLatestFixed(Instant.now());
_mdap = context.getMarketDataAvailabilityProvider();
_exclusions = context.getFunctionExclusionGroups();
_compilationContext = context.getFunctionCompilationContext().clone();
_compilationContext.setComputationTargetResolver(_compilationContext.getRawComputationTargetResolver().atVersionCorrection(resolverVersionCorrection));
Collection<ResolutionRule> rules = context.getFunctionResolver().compile(valuationTime).getAllResolutionRules();
_compilationContext.setComputationTargetResults(new ComputationTargetResults(rules));
_rules = buildRules(rules);
}
public SimpleRequirementAmbiguityChecker(final AmbiguityCheckerContext context, final Instant valuationTime, VersionCorrection resolverVersionCorrection,
final ViewCalculationConfiguration calcConfig) {
ArgumentChecker.notNull(context, "context");
ArgumentChecker.notNull(valuationTime, "valuationTime");
resolverVersionCorrection = ArgumentChecker.notNull(resolverVersionCorrection, "resolverVersionCorrection").withLatestFixed(Instant.now());
ArgumentChecker.notNull(calcConfig, "calcConfig");
_mdap = context.getMarketDataAvailabilityProvider();
_exclusions = context.getFunctionExclusionGroups();
_compilationContext = context.getFunctionCompilationContext().clone();
_compilationContext.setComputationTargetResolver(_compilationContext.getRawComputationTargetResolver().atVersionCorrection(resolverVersionCorrection));
Collection<ResolutionRule> rules = context.getFunctionResolver().compile(valuationTime).getAllResolutionRules();
rules = calcConfig.getResolutionRuleTransform().transform(rules);
_compilationContext.setComputationTargetResults(new ComputationTargetResults(rules));
_compilationContext.setViewCalculationConfiguration(calcConfig);
_rules = buildRules(rules);
final UniqueId portfolioId = calcConfig.getViewDefinition().getPortfolioId();
if (portfolioId != null) {
s_logger.info("Resolving portflio {} for view definition", portfolioId);
ComputationTargetSpecification portfolioSpec = new ComputationTargetSpecification(ComputationTargetType.PORTFOLIO, portfolioId);
if (portfolioId.isLatest()) {
_compilationContext.getComputationTargetResolver().getSpecificationResolver().getTargetSpecification(portfolioSpec);
}
final ComputationTarget target = _compilationContext.getComputationTargetResolver().resolve(portfolioSpec);
if (target != null) {
_compilationContext.setPortfolio(target.getValue(ComputationTargetType.PORTFOLIO));
} else {
s_logger.error("Couldn't resolve portfolio {}", portfolioId);
}
}
}
private ResolutionRule[][] buildRules(final Collection<ResolutionRule> rules) {
final Map<Integer, Collection<ResolutionRule>> byPriority = new HashMap<Integer, Collection<ResolutionRule>>();
for (ResolutionRule rule : rules) {
Collection<ResolutionRule> priorityGroup = byPriority.get(rule.getPriority());
if (priorityGroup == null) {
priorityGroup = new ArrayList<ResolutionRule>();
byPriority.put(rule.getPriority(), priorityGroup);
}
priorityGroup.add(rule);
}
final List<Integer> priorities = new ArrayList<Integer>(byPriority.keySet());
Collections.sort(priorities);
final ResolutionRule[][] result = new ResolutionRule[priorities.size()][];
int i = result.length;
for (Integer priority : priorities) {
final Collection<ResolutionRule> priorityGroup = byPriority.get(priority);
result[--i] = priorityGroup.toArray(new ResolutionRule[priorityGroup.size()]);
}
return result;
}
public MarketDataAvailabilityProvider getMarketDataAvailabilityProvider() {
return _mdap;
}
public FunctionExclusionGroups getExclusions() {
return _exclusions;
}
public FunctionCompilationContext getCompilationContext() {
return _compilationContext;
}
private ResolutionRule[][] getRules() {
return _rules;
}
private ResolutionRule[][] getRules(final ComputationTargetType type) {
ResolutionRule[][] rules = _rulesByType.get(type);
if (rules == null) {
final List<ResolutionRule[]> rulesList = new ArrayList<ResolutionRule[]>(getRules().length);
for (ResolutionRule[] originalRules : getRules()) {
final List<ResolutionRule> filteredRules = new ArrayList<ResolutionRule>(originalRules.length);
for (ResolutionRule originalRule : originalRules) {
if (originalRule.getParameterizedFunction().getFunction().getTargetType().isCompatible(type)) {
filteredRules.add(originalRule);
}
}
if (!filteredRules.isEmpty()) {
rulesList.add(filteredRules.toArray(new ResolutionRule[filteredRules.size()]));
}
}
rules = rulesList.toArray(new ResolutionRule[rulesList.size()][]);
final ResolutionRule[][] existing = _rulesByType.putIfAbsent(type, rules);
if (existing != null) {
rules = existing;
}
}
return rules;
}
public void setGreedyCaching(final boolean greedyCaching) {
_greedyCaching = greedyCaching;
}
public boolean isGreedyCaching() {
return _greedyCaching;
}
public void setSharedCaching(final boolean sharedCaching) {
//_sharedCaching = sharedCaching ? new ConcurrentHashMap<Object, FullRequirementResolution>() : null;
_sharedCaching = sharedCaching ? new MapMaker().softValues().<Object, FullRequirementResolution>makeMap() : null;
}
public boolean isSharedCaching() {
return _sharedCaching != null;
}
private ValueSpecification alias(ValueSpecification marketDataSpec, final ComputationTargetSpecification targetSpec, final ValueRequirement requirement) {
if (!marketDataSpec.getValueName().equals(requirement.getValueName())) {
marketDataSpec = new ValueSpecification(requirement.getValueName(), marketDataSpec.getTargetSpecification(), marketDataSpec.getProperties());
}
if (!marketDataSpec.getTargetSpecification().equals(targetSpec)) {
marketDataSpec = new ValueSpecification(marketDataSpec.getValueName(), targetSpec, marketDataSpec.getProperties());
}
if (!requirement.getConstraints().isSatisfiedBy(marketDataSpec.getProperties())) {
final String function = requirement.getConstraints().getSingleValue(ValuePropertyNames.FUNCTION);
final ValueProperties a, b;
if (function != null) {
a = marketDataSpec.getProperties().copy().withoutAny(ValuePropertyNames.FUNCTION).with(ValuePropertyNames.FUNCTION, function).get();
b = requirement.getConstraints().withoutAny(ValuePropertyNames.FUNCTION);
} else {
a = marketDataSpec.getProperties();
b = requirement.getConstraints();
}
marketDataSpec = new ValueSpecification(marketDataSpec.getValueName(), marketDataSpec.getTargetSpecification(), a.union(b));
}
return marketDataSpec;
}
private boolean isExcluded(final Collection<FunctionExclusionGroup> exclusions, final ResolutionRule rule) {
if (exclusions != null) {
final FunctionExclusionGroups util = getExclusions();
final FunctionExclusionGroup exclusion = util.getExclusionGroup(rule.getParameterizedFunction().getFunction().getFunctionDefinition());
if ((exclusion != null) && util.isExcluded(exclusion, exclusions)) {
s_logger.debug("Ignoring {} from exclusion group {}", rule, exclusion);
return true;
}
}
return false;
}
private Collection<FunctionExclusionGroup> getFunctionExclusion(final Collection<FunctionExclusionGroup> parentExclusion, final ResolutionRule rule) {
final FunctionExclusionGroups groups = getExclusions();
if (groups == null) {
return null;
}
final FunctionExclusionGroup functionExclusion = groups.getExclusionGroup(rule.getParameterizedFunction().getFunction().getFunctionDefinition());
if (functionExclusion == null) {
return parentExclusion;
}
if (parentExclusion != null) {
return groups.withExclusion(parentExclusion, functionExclusion);
} else {
return Collections.singleton(functionExclusion);
}
}
private Collection<FullRequirementResolution> resolve(final CheckingCache cache, final Collection<FunctionExclusionGroup> parentExclusion, final ComputationTarget target,
final ValueRequirement desiredValue, final ResolutionRule rule, final Set<ValueRequirement> inputs) {
String functionExclusionValueName = desiredValue.getValueName();
Collection<FunctionExclusionGroup> functionExclusion = null;
final Collection<FullRequirementResolution> resolvedInputs = new ArrayList<FullRequirementResolution>(inputs.size());
for (ValueRequirement input : inputs) {
final FullRequirementResolution resolvedInput;
if ((input.getValueName() == functionExclusionValueName) && input.getTargetReference().equals(target.toSpecification())) {
if (functionExclusion == null) {
functionExclusion = getFunctionExclusion(parentExclusion, rule);
if (functionExclusion == null) {
functionExclusionValueName = null;
}
}
resolvedInput = resolve(cache, functionExclusion, input);
} else {
resolvedInput = resolve(cache, null, input);
}
if ((resolvedInput != null) && resolvedInput.isResolved()) {
resolvedInputs.add(resolvedInput);
}
}
return resolvedInputs;
}
private void getResolvedInputs(final Collection<FullRequirementResolution> resolvedInputs, final ValueRequirement[] inputArray, final Iterator<Collection<RequirementResolution>>[] itrResolvedInputs) {
int i = 0;
for (FullRequirementResolution resolvedInput : resolvedInputs) {
inputArray[i] = resolvedInput.getRequirement();
itrResolvedInputs[i++] = resolvedInput.getResolutions().iterator();
}
}
private boolean getResolvedInputs(final int j, final ValueRequirement[] inputArray, final RequirementResolution[][] resolvedInputsSlice, final Map<ValueSpecification, ValueRequirement> inputMap) {
inputMap.clear();
int base = 1;
boolean success = true;
for (int i = 0; i < resolvedInputsSlice.length; i++) {
final int size = resolvedInputsSlice[i].length;
final RequirementResolution resolvedInput = resolvedInputsSlice[i][(j / base) % size];
base *= size;
if (resolvedInput == null) {
success = false;
} else {
inputMap.put(resolvedInput.getSpecification(), inputArray[i]);
}
}
return success;
}
protected FullRequirementResolution resolve(final CheckingCache cache, final Collection<FunctionExclusionGroup> exclusions, final ValueRequirement requirement) {
if (!cache.begin(requirement)) {
// Recursive requirement; abort
s_logger.debug("Recursive requirement on {}", requirement);
return null;
}
FullRequirementResolution resolved = cache.get(requirement);
if (resolved != null) {
s_logger.debug("Cached resolution {}", resolved);
cache.end(requirement);
return resolved;
}
s_logger.debug("Resolving {}", requirement);
resolved = new FullRequirementResolution(requirement);
final ComputationTargetResolver.AtVersionCorrection resolver = getCompilationContext().getComputationTargetResolver();
final ComputationTargetSpecification targetSpec = resolver.getSpecificationResolver().getTargetSpecification(requirement.getTargetReference());
if (targetSpec != null) {
final ComputationTarget target = resolver.resolve(targetSpec);
ValueSpecification marketData = getMarketDataAvailabilityProvider().getAvailability(targetSpec, (target != null) ? target.getValue() : null, requirement);
if (marketData != null) {
s_logger.debug("Market data satisfies {} with {}", requirement, marketData);
marketData = alias(marketData, targetSpec, requirement);
resolved.addResolutions(Collections.singleton(new RequirementResolution(marketData, MARKET_DATA_SOURCING_FUNCTION, Collections.<FullRequirementResolution>emptySet())));
} else {
if (target != null) {
final List<Collection<RequirementResolution>> resolutions = new ArrayList<Collection<RequirementResolution>>();
final Map<ComputationTargetType, ComputationTarget> targetCache = new HashMap<ComputationTargetType, ComputationTarget>();
final Map<ValueSpecification, ValueRequirement> inputMap = new HashMap<ValueSpecification, ValueRequirement>();
for (ResolutionRule[] rules : getRules(target.toSpecification().getType())) {
for (ResolutionRule rule : rules) {
try {
if (isExcluded(exclusions, rule)) {
continue;
}
final ComputationTarget adjustedTarget = rule.adjustTarget(targetCache, target);
final ValueSpecification nominalResult = rule.getResult(requirement.getValueName(), adjustedTarget, requirement.getConstraints(), getCompilationContext());
if (nominalResult != null) {
s_logger.debug("Possible resolution of {} to {}", requirement, nominalResult);
Set<ValueRequirement> inputs = null;
try {
inputs = rule.getParameterizedFunction().getFunction().getRequirements(getCompilationContext(), adjustedTarget, requirement);
} catch (Throwable t) {
s_logger.debug("Exception thrown by getRequirements", t);
}
if (inputs != null) {
final Collection<FullRequirementResolution> resolvedInputs = resolve(cache, exclusions, target, requirement, rule, inputs);
if (resolvedInputs.size() != inputs.size()) {
if (!rule.getParameterizedFunction().getFunction().canHandleMissingRequirements()) {
s_logger.debug("Couldn't resolve inputs for {}", rule);
continue;
}
}
final ValueRequirement[] inputArray = new ValueRequirement[resolvedInputs.size()];
@SuppressWarnings("unchecked")
final Iterator<Collection<RequirementResolution>>[] itrResolvedInputs = new Iterator[resolvedInputs.size()];
getResolvedInputs(resolvedInputs, inputArray, itrResolvedInputs);
final RequirementResolution[][] resolvedInputsSlice = new RequirementResolution[resolvedInputs.size()][];
int resolutionIndex = 0;
do {
int ambiguous = 1;
boolean hasNext = false;
for (int i = 0; i < resolvedInputsSlice.length; i++) {
if (itrResolvedInputs[i].hasNext()) {
final Collection<RequirementResolution> value = itrResolvedInputs[i].next();
resolvedInputsSlice[i] = value.toArray(new RequirementResolution[value.size()]);
hasNext = true;
}
ambiguous *= resolvedInputsSlice[i].length;
if (ambiguous <= 0) {
// The cross product can be bad enough, but this is *really* bad
throw new IllegalStateException("Overflow");
}
}
if (!hasNext) {
break;
}
if (ambiguous > 1) {
s_logger.info("{} ambiguous input states discovered for {}", ambiguous, requirement);
}
boolean failed = false;
boolean succeeded = false;
for (int j = 0; j < ambiguous; j++) {
if (!getResolvedInputs(j, inputArray, resolvedInputsSlice, inputMap)) {
if (!rule.getParameterizedFunction().getFunction().canHandleMissingRequirements()) {
failed = true;
continue;
}
}
Set<ValueSpecification> results = null;
try {
results = rule.getParameterizedFunction().getFunction().getResults(getCompilationContext(), adjustedTarget, inputMap);
} catch (Throwable t) {
s_logger.debug("Exception thrown by getResults", t);
}
if (results != null) {
ValueSpecification finalResult = null;
for (ValueSpecification result : results) {
if (requirement.getValueName().equals(result.getValueName())) {
if (requirement.getConstraints().isSatisfiedBy(result.getProperties())) {
finalResult = result;
break;
}
}
}
if (finalResult != null) {
final Set<ValueRequirement> additionalRequirements = rule.getParameterizedFunction().getFunction()
.getAdditionalRequirements(getCompilationContext(), adjustedTarget, inputMap.keySet(), results);
if (additionalRequirements != null) {
if (additionalRequirements.isEmpty()) {
s_logger.debug("Resolved {} to {}", requirement, finalResult);
if (resolutionIndex >= resolutions.size()) {
resolutions.add(new HashSet<RequirementResolution>());
}
resolutions.get(resolutionIndex).add(new RequirementResolution(finalResult, rule.getParameterizedFunction(), resolvedInputs));
succeeded = true;
} else {
final Collection<FullRequirementResolution> additionalResolvedRequirements = resolve(cache, exclusions, target, requirement, rule, additionalRequirements);
if ((additionalResolvedRequirements.size() == additionalRequirements.size()) || rule.getParameterizedFunction().getFunction().canHandleMissingRequirements()) {
resolvedInputs.addAll(additionalResolvedRequirements);
s_logger.debug("Resolved {} to {}", requirement, finalResult);
if (resolutionIndex >= resolutions.size()) {
resolutions.add(new HashSet<RequirementResolution>());
}
resolutions.get(resolutionIndex).add(new RequirementResolution(finalResult, rule.getParameterizedFunction(), resolvedInputs));
succeeded = true;
} else {
failed = true;
}
}
} else {
failed = true;
}
} else {
failed = true;
}
} else {
failed = true;
}
}
if (succeeded) {
if (failed) {
// Not all combinations are successful; treat as ambiguous
resolutions.get(resolutionIndex).add(null);
}
resolutionIndex++;
}
} while (true);
}
}
} catch (Throwable t) {
s_logger.error("Exception thrown by {} when handling {}", rule, requirement);
s_logger.warn("Exception", t);
}
}
if (!resolutions.isEmpty()) {
for (Collection<RequirementResolution> resolution : resolutions) {
if (resolution.size() > 1) {
s_logger.info("Got ambiguous resolution of {} to {}", requirement, resolutions);
} else {
s_logger.debug("Unambiguous resolution of {} to {}", requirement, resolutions);
}
resolved.addResolutions(resolution);
}
resolutions.clear();
}
}
if (resolved.isResolved()) {
s_logger.info("Resolved {}", requirement);
} else {
s_logger.debug("No resolutions found for {}", requirement);
}
} else {
s_logger.warn("Couldn't resolve target for {}", requirement);
}
}
} else {
s_logger.warn("Couldn't resolve target specification for {}", requirement);
}
cache.end(requirement);
return cache.put(resolved);
}
// RequirementAmbiguityChecker
@Override
public FullRequirementResolution resolve(final ValueRequirement requirement) {
return resolve(new CheckingCache(isGreedyCaching(), _sharedCaching), null, requirement);
}
}