/**
* 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.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.MapMaker;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.engine.ComputationTarget;
import com.opengamma.engine.ComputationTargetResolver;
import com.opengamma.engine.ComputationTargetSpecification;
import com.opengamma.engine.MemoryUtils;
import com.opengamma.engine.function.FunctionCompilationContext;
import com.opengamma.engine.function.ParameterizedFunction;
import com.opengamma.engine.function.blacklist.FunctionBlacklistQuery;
import com.opengamma.engine.target.ComputationTargetResolverUtils;
import com.opengamma.engine.target.ComputationTargetType;
import com.opengamma.engine.target.ComputationTargetTypeMap;
import com.opengamma.engine.target.ComputationTargetTypeVisitor;
import com.opengamma.engine.value.ValueProperties;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.id.UniqueIdentifiable;
import com.opengamma.lambdava.functions.Function2;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.tuple.Pair;
import com.opengamma.util.tuple.Triple;
/**
* Default implementation of the compiled function resolver.
* <p>
* The aim of the resolution is to find functions that are capable of satisfying a requirement. In addition, a priority mechanism is used to return functions in priority order from highest to lowest.
* <p>
* This class is not thread-safe. It is possible to call {@link #resolveFunction} concurrently from multiple threads, the rule manipulation methods require external locking.
*/
public class DefaultCompiledFunctionResolver implements CompiledFunctionResolver {
private static final Logger s_logger = LoggerFactory.getLogger(DefaultCompiledFunctionResolver.class);
/**
* Holds an arbitrary bundle of rules with mixed priorities. Instances can attach to a "parent" bundle to receive copies of those rules. For example a rule that applies to objects of type A must be
* present in the bundles for objects of sub-types of A.
*/
private interface ChainedRuleBundle extends Iterable<Collection<ResolutionRule>> {
/**
* Groups the rules into priority levels, sorts these and returns a structure that can iterate over them in descending priority order.
*
* @returns the prioritized rules or null if there are none
*/
Iterable<Collection<ResolutionRule>> prioritize();
/**
* Registers a listening {@link ChainedRuleBundle} with this one. The listener will be immediately notified of any existing rules. When new rules are added, the listener will also be notified.
*/
void addListener(ChainedRuleBundle listener);
/**
* Adds a rule to this bundle and notifies any registered listeners of the rule.
*/
void addRule(ResolutionRule rule);
}
private static final class SimpleChainedRuleBundle implements ChainedRuleBundle {
/**
* All of the rules, in an arbitrary order.
*/
private final LinkedList<ResolutionRule> _rules = new LinkedList<ResolutionRule>();
/**
* All of the listeners to be notified when a rule is added.
*/
private ArrayList<ChainedRuleBundle> _listeners;
@Override
public Iterable<Collection<ResolutionRule>> prioritize() {
if (_rules.isEmpty()) {
return null;
} else {
final Map<Integer, Collection<ResolutionRule>> priorityToRules = new HashMap<Integer, Collection<ResolutionRule>>();
for (ResolutionRule rule : _rules) {
Collection<ResolutionRule> rules = priorityToRules.get(rule.getPriority());
if (rules == null) {
rules = new LinkedList<ResolutionRule>();
priorityToRules.put(rule.getPriority(), rules);
}
rules.add(rule);
}
final Integer[] priorities = priorityToRules.keySet().toArray(new Integer[priorityToRules.size()]);
Arrays.sort(priorities);
final Collection<Collection<ResolutionRule>> rules = new ArrayList<Collection<ResolutionRule>>(priorities.length);
for (int i = priorities.length; --i >= 0;) {
rules.add(new ArrayList<ResolutionRule>(priorityToRules.get(priorities[i])));
}
return rules;
}
}
/**
* Returns an iterator over the rules. The rules are not prioritized and returned as a single chunk.
*/
@Override
public Iterator<Collection<ResolutionRule>> iterator() {
return Collections.<Collection<ResolutionRule>>singleton(_rules).iterator();
}
@Override
public void addListener(final ChainedRuleBundle listener) {
if (_listeners == null) {
_listeners = new ArrayList<ChainedRuleBundle>();
}
_listeners.add(listener);
for (ResolutionRule rule : _rules) {
listener.addRule(rule);
}
}
@Override
public void addRule(final ResolutionRule rule) {
_rules.add(rule);
if (_listeners != null) {
for (ChainedRuleBundle listener : _listeners) {
listener.addRule(rule);
}
}
}
}
private static final class FoldedChainedRuleBundle implements ChainedRuleBundle {
private final Collection<ChainedRuleBundle> _bundles;
private FoldedChainedRuleBundle(final Collection<ChainedRuleBundle> bundles) {
_bundles = bundles;
}
public static ChainedRuleBundle of(final ChainedRuleBundle a, final ChainedRuleBundle b) {
if (a instanceof FoldedChainedRuleBundle) {
final Collection<ChainedRuleBundle> bundles = new ArrayList<ChainedRuleBundle>(((FoldedChainedRuleBundle) a)._bundles);
if (b instanceof FoldedChainedRuleBundle) {
bundles.addAll(((FoldedChainedRuleBundle) b)._bundles);
} else {
bundles.add(b);
}
return new FoldedChainedRuleBundle(bundles);
} else {
return new FoldedChainedRuleBundle(Arrays.asList(a, b));
}
}
@Override
public Iterator<Collection<ResolutionRule>> iterator() {
// Folding should only occur at a "fetch" so we will not be requesting an iterator
throw new UnsupportedOperationException();
}
@Override
public Iterable<Collection<ResolutionRule>> prioritize() {
// Folding should only occur at a "fetch" so we will not be requesting a prioritization
throw new UnsupportedOperationException();
}
@Override
public void addListener(final ChainedRuleBundle listener) {
for (ChainedRuleBundle bundle : _bundles) {
bundle.addListener(listener);
}
}
@Override
public void addRule(final ResolutionRule rule) {
for (ChainedRuleBundle bundle : _bundles) {
bundle.addRule(rule);
}
}
}
private static class FoldedCompiledRuleBundle extends ArrayList<Collection<ResolutionRule>> implements Iterable<Collection<ResolutionRule>> {
private static final long serialVersionUID = 1L;
public FoldedCompiledRuleBundle(final Iterable<Collection<ResolutionRule>> a, final Iterable<Collection<ResolutionRule>> b) {
final Iterator<Collection<ResolutionRule>> aItr = a.iterator();
Collection<ResolutionRule> aRules = aItr.next();
int aPriority = aRules.iterator().next().getPriority();
final Iterator<Collection<ResolutionRule>> bItr = b.iterator();
Collection<ResolutionRule> bRules = bItr.next();
int bPriority = bRules.iterator().next().getPriority();
do {
if (aPriority == bPriority) {
final Set<ResolutionRule> rules = new HashSet<ResolutionRule>(aRules);
rules.addAll(bRules);
add(rules);
aRules = null;
bRules = null;
} else if (aPriority > bPriority) {
add(aRules);
aRules = null;
} else {
add(bRules);
bRules = null;
}
if (aRules == null) {
if (aItr.hasNext()) {
aRules = aItr.next();
}
}
if (bRules == null) {
if (bItr.hasNext()) {
bRules = bItr.next();
}
}
} while ((aRules != null) && (bRules != null));
if (aRules != null) {
do {
add(aRules);
if (aItr.hasNext()) {
aRules = aItr.next();
} else {
break;
}
} while (true);
}
if (bRules != null) {
do {
add(bRules);
if (bItr.hasNext()) {
bRules = bItr.next();
} else {
break;
}
} while (true);
}
}
}
private static final Function2<Iterable<Collection<ResolutionRule>>, Iterable<Collection<ResolutionRule>>, Iterable<Collection<ResolutionRule>>> s_foldRules =
new Function2<Iterable<Collection<ResolutionRule>>, Iterable<Collection<ResolutionRule>>, Iterable<Collection<ResolutionRule>>>() {
@Override
public Iterable<Collection<ResolutionRule>> execute(final Iterable<Collection<ResolutionRule>> a, final Iterable<Collection<ResolutionRule>> b) {
if (a instanceof ChainedRuleBundle) {
if (b instanceof ChainedRuleBundle) {
return FoldedChainedRuleBundle.of((ChainedRuleBundle) a, (ChainedRuleBundle) b);
} else {
throw new IllegalStateException("Rules have been partially compiled");
}
} else {
if (b instanceof ChainedRuleBundle) {
throw new IllegalStateException("Rules have been partially compiled");
} else {
return new FoldedCompiledRuleBundle(a, b);
}
}
}
};
/**
* The rules by target type. The map values are {@link ChainedRuleBundle} instances during construction, after which return an iterator giving the rules in blocks of descending priority order.
*/
private final ComputationTargetTypeMap<Iterable<Collection<ResolutionRule>>> _type2Rules = new ComputationTargetTypeMap<Iterable<Collection<ResolutionRule>>>(s_foldRules);
/**
* The compilation context.
*/
private final FunctionCompilationContext _functionCompilationContext;
/**
* Cache of targets. The values are weak so that when the function iterators drop out of scope as the requirements on the target are resolved the entry can be dropped.
*/
private final ConcurrentMap<ComputationTargetSpecification, Pair<ResolutionRule[], Collection<ValueSpecification>[]>> _targetCache = new MapMaker().weakValues().makeMap();
/**
* Creates a resolver.
*
* @param functionCompilationContext the context, not null
*/
public DefaultCompiledFunctionResolver(final FunctionCompilationContext functionCompilationContext) {
this(functionCompilationContext, Collections.<ResolutionRule>emptyList());
}
/**
* Creates a resolver.
*
* @param functionCompilationContext the context, not null
* @param resolutionRules the resolution rules, not null
*/
public DefaultCompiledFunctionResolver(final FunctionCompilationContext functionCompilationContext, Collection<ResolutionRule> resolutionRules) {
ArgumentChecker.notNull(functionCompilationContext, "functionCompilationContext");
ArgumentChecker.notNull(resolutionRules, "resolutionRules");
_functionCompilationContext = functionCompilationContext;
addRules(resolutionRules);
}
private static final Function2<Iterable<Collection<ResolutionRule>>, Iterable<Collection<ResolutionRule>>, Iterable<Collection<ResolutionRule>>> s_combineChainedRuleBundle =
new Function2<Iterable<Collection<ResolutionRule>>, Iterable<Collection<ResolutionRule>>, Iterable<Collection<ResolutionRule>>>() {
@Override
public Iterable<Collection<ResolutionRule>> execute(final Iterable<Collection<ResolutionRule>> a, final Iterable<Collection<ResolutionRule>> b) {
if (!(a instanceof ChainedRuleBundle)) {
throw new IllegalStateException("Rules have already been compiled - can't add new ones");
}
((ChainedRuleBundle) a).addListener((ChainedRuleBundle) b);
return b;
}
};
private static final ComputationTargetTypeVisitor<DefaultCompiledFunctionResolver, Void> s_createChainedRuleBundle =
new ComputationTargetTypeVisitor<DefaultCompiledFunctionResolver, Void>() {
@Override
public Void visitMultipleComputationTargetTypes(final Set<ComputationTargetType> types, final DefaultCompiledFunctionResolver self) {
for (ComputationTargetType type : types) {
type.accept(this, self);
}
return null;
}
@Override
public Void visitNestedComputationTargetTypes(final List<ComputationTargetType> types, final DefaultCompiledFunctionResolver self) {
return types.get(types.size() - 1).accept(this, self);
}
@Override
public Void visitNullComputationTargetType(final DefaultCompiledFunctionResolver self) {
if (self._type2Rules.getDirect(ComputationTargetType.NULL) == null) {
self._type2Rules.put(ComputationTargetType.NULL, new SimpleChainedRuleBundle());
}
return null;
}
private void insertBundle(final Class<?> clazz, final DefaultCompiledFunctionResolver self) {
if ((clazz != null) && UniqueIdentifiable.class.isAssignableFrom(clazz)) {
@SuppressWarnings("unchecked")
final ComputationTargetType type = ComputationTargetType.of((Class<? extends UniqueIdentifiable>) clazz);
if (self._type2Rules.getDirect(type) == null) {
for (Class<?> superClazz : clazz.getInterfaces()) {
insertBundle(superClazz, self);
}
insertBundle(clazz.getSuperclass(), self);
self._type2Rules.put(type, new SimpleChainedRuleBundle(), s_combineChainedRuleBundle);
}
}
}
@Override
public Void visitClassComputationTargetType(final Class<? extends UniqueIdentifiable> type, final DefaultCompiledFunctionResolver self) {
insertBundle(type, self);
return null;
}
};
/**
* Adds a single rule to the resolver. Rules must be added before calling {@link #compileRules} to pre-process them into the data structures used for resolution.
*
* @param resolutionRule the rule to add, not null
*/
public void addRule(ResolutionRule resolutionRule) {
final ComputationTargetType type = resolutionRule.getParameterizedFunction().getFunction().getTargetType();
type.accept(s_createChainedRuleBundle, this);
final Iterable<Collection<ResolutionRule>> rules = _type2Rules.getDirect(type);
assert rules != null; // s_createChainedRuleBundle should have done this
final ChainedRuleBundle bundle;
if (rules instanceof ChainedRuleBundle) {
bundle = (ChainedRuleBundle) rules;
} else {
throw new IllegalStateException("Rules have already been compiled");
}
bundle.addRule(resolutionRule);
}
/**
* Adds rules to the resolver. Rules must be added before calling {@link #compileRules} to pre-process them into the data structures used for resolution.
*
* @param resolutionRules the rules to add, no nulls, not null
*/
public void addRules(Iterable<ResolutionRule> resolutionRules) {
for (ResolutionRule resolutionRule : resolutionRules) {
addRule(resolutionRule);
}
}
/**
* Processes the rules into data structures used for resolution. After calling this method, no further rules must be added using {@link #addRule} or {@link #addRules}.
*/
public void compileRules() {
final Iterator<Map.Entry<ComputationTargetType, Iterable<Collection<ResolutionRule>>>> itr = _type2Rules.entries().iterator();
while (itr.hasNext()) {
final Map.Entry<ComputationTargetType, Iterable<Collection<ResolutionRule>>> e = itr.next();
final Iterable<Collection<ResolutionRule>> v = e.getValue();
if (v instanceof ChainedRuleBundle) {
final Iterable<Collection<ResolutionRule>> rules = ((ChainedRuleBundle) v).prioritize();
if (rules != null) {
e.setValue(rules);
} else {
itr.remove();
}
}
}
}
@Override
public Collection<ResolutionRule> getAllResolutionRules() {
final Set<ResolutionRule> rules = new HashSet<ResolutionRule>();
for (Iterable<Collection<ResolutionRule>> typeRules : _type2Rules.values()) {
for (Collection<ResolutionRule> priorityRules : typeRules) {
rules.addAll(priorityRules);
}
}
return rules;
}
/**
* Gets the function compilation context.
*
* @return the context, not null
*/
protected FunctionCompilationContext getFunctionCompilationContext() {
return _functionCompilationContext;
}
/**
* Comparator to give a fixed ordering of functions at the same priority so that we at least have deterministic behavior between runs.
*/
private static final Comparator<Pair<ResolutionRule, Collection<ValueSpecification>>> RULE_COMPARATOR = new Comparator<Pair<ResolutionRule, Collection<ValueSpecification>>>() {
@Override
public int compare(Pair<ResolutionRule, Collection<ValueSpecification>> o1, Pair<ResolutionRule, Collection<ValueSpecification>> o2) {
int c = o1.getFirst().getParameterizedFunction().getFunction().getFunctionDefinition().getUniqueId()
.compareTo(o2.getFirst().getParameterizedFunction().getFunction().getFunctionDefinition().getUniqueId());
if (c != 0) {
return c;
}
// Have the same function, can try and order the "FunctionParameters" as we know it implements a hash code
c = o1.getFirst().getParameterizedFunction().getParameters().hashCode() - o2.getFirst().getParameterizedFunction().getParameters().hashCode();
if (c != 0) {
return c;
}
throw new OpenGammaRuntimeException("Rule priority conflict - cannot order " + o1 + " against " + o2);
}
};
private static ValueSpecification reduceMemory(final ValueSpecification valueSpec, final ComputationTargetResolver.AtVersionCorrection resolver) {
final ComputationTargetSpecification oldTargetSpec = valueSpec.getTargetSpecification();
final ComputationTargetSpecification newTargetSpec = ComputationTargetResolverUtils.simplifyType(oldTargetSpec, resolver);
if (newTargetSpec == oldTargetSpec) {
return MemoryUtils.instance(valueSpec);
} else {
return MemoryUtils.instance(new ValueSpecification(valueSpec.getValueName(), newTargetSpec, valueSpec.getProperties()));
}
}
private static Collection<ValueSpecification> reduceMemory(final Set<ValueSpecification> specifications, final ComputationTargetResolver.AtVersionCorrection resolver) {
if (specifications.size() == 1) {
final ValueSpecification specification = specifications.iterator().next();
final ValueSpecification reducedSpecification = reduceMemory(specification, resolver);
if (specification == reducedSpecification) {
return specifications;
} else {
return Collections.singleton(reducedSpecification);
}
} else {
final Collection<ValueSpecification> result = new ArrayList<ValueSpecification>(specifications.size());
for (ValueSpecification specification : specifications) {
result.add(reduceMemory(specification, resolver));
}
return result;
}
}
@SuppressWarnings("unchecked")
@Override
public Iterator<Triple<ParameterizedFunction, ValueSpecification, Collection<ValueSpecification>>> resolveFunction(
final String valueName, final ComputationTarget target, final ValueProperties constraints) {
final ComputationTargetResolver.AtVersionCorrection resolver = getFunctionCompilationContext().getComputationTargetResolver();
// TODO [PLAT-2286] Don't key the cache by target specification as the contexts may vary. E.g. the (PORTFOLIO_NODE/POSITION, node0, pos0) target
// will have considered all the rules for (POSITION, pos0). We want to share this, not duplicate the effort (and the storage)
final ComputationTargetSpecification targetSpecification = MemoryUtils.instance(ComputationTargetResolverUtils.simplifyType(target.toSpecification(), resolver));
Pair<ResolutionRule[], Collection<ValueSpecification>[]> cached = _targetCache.get(targetSpecification);
if (cached == null) {
final LinkedList<ResolutionRule> resolutionRules = new LinkedList<ResolutionRule>();
final LinkedList<Collection<ValueSpecification>> resolutionResults = new LinkedList<Collection<ValueSpecification>>();
final Iterable<Collection<ResolutionRule>> typeRules = _type2Rules.get(target.getType());
if (typeRules != null) {
final Map<ComputationTargetType, ComputationTarget> adjusted = new HashMap<ComputationTargetType, ComputationTarget>();
for (Collection<ResolutionRule> rules : typeRules) {
int rulesFound = 0;
for (ResolutionRule rule : rules) {
final ComputationTarget adjustedTarget = rule.adjustTarget(adjusted, target);
if (adjustedTarget != null) {
final Set<ValueSpecification> results = rule.getResults(adjustedTarget, getFunctionCompilationContext());
if ((results != null) && !results.isEmpty()) {
resolutionRules.add(rule);
resolutionResults.add(reduceMemory(results, resolver));
rulesFound++;
}
}
}
if (rulesFound > 1) {
// sort only the sub-list of rules associated with the priority
final Iterator<ResolutionRule> rulesIterator = resolutionRules.descendingIterator();
final Iterator<Collection<ValueSpecification>> resultsIterator = resolutionResults.descendingIterator();
final Pair<ResolutionRule, Collection<ValueSpecification>>[] found = new Pair[rulesFound];
for (int i = 0; i < rulesFound; i++) {
found[i] = Pair.of(rulesIterator.next(), resultsIterator.next());
rulesIterator.remove();
resultsIterator.remove();
}
// TODO [ENG-260] re-order the last "rulesFound" rules in the list with a cost-based heuristic (cheapest first)
// TODO [ENG-260] throw an exception if there are two rules which can't be re-ordered
// REVIEW 2010-10-27 Andrew -- Could the above be done with a Comparator<Pair<ParameterizedFunction, ValueSpecification>>
// provided in the compilation context? This could do away with the need for our "priority" levels as that can do ALL ordering.
// We should wrap it at construction in something that will detect the equality case and trigger an exception.
Arrays.sort(found, RULE_COMPARATOR);
for (int i = 0; i < rulesFound; i++) {
resolutionRules.add(found[i].getFirst());
resolutionResults.add(found[i].getSecond());
}
}
}
} else {
s_logger.warn("No rules for target type {}", target);
}
// TODO: the array of rules is probably getting duplicated for each similar target (e.g. all swaps probably use the same rules)
cached = (Pair<ResolutionRule[], Collection<ValueSpecification>[]>) (Pair<?, ?>) Pair.of(resolutionRules.toArray(new ResolutionRule[resolutionRules.size()]),
resolutionResults.toArray(new Collection[resolutionResults.size()]));
final Pair<ResolutionRule[], Collection<ValueSpecification>[]> existing = _targetCache.putIfAbsent(targetSpecification, cached);
if (existing != null) {
cached = existing;
}
}
return new It(valueName, targetSpecification, constraints, target, getFunctionCompilationContext(), cached);
}
/**
* Iterator of functions and specifications from a dependency node.
*/
private static final class It implements Iterator<Triple<ParameterizedFunction, ValueSpecification, Collection<ValueSpecification>>> {
private final FunctionCompilationContext _context;
private final ComputationTargetSpecification _target;
private final String _valueName;
private final ValueProperties _constraints;
private final Pair<ResolutionRule[], Collection<ValueSpecification>[]> _values;
private int _itr;
private Triple<ParameterizedFunction, ValueSpecification, Collection<ValueSpecification>> _next;
private It(final String valueName, final ComputationTargetSpecification targetSpecification, final ValueProperties constraints, final ComputationTarget target,
final FunctionCompilationContext context, final Pair<ResolutionRule[], Collection<ValueSpecification>[]> values) {
_context = context;
_target = targetSpecification;
_valueName = valueName;
_constraints = constraints;
_values = values;
findNext(target);
}
private void findNext(final ComputationTarget target) {
final ResolutionRule[] rules = _values.getFirst();
final Collection<ValueSpecification>[] resultSets = _values.getSecond();
final FunctionBlacklistQuery blacklist = _context.getGraphBuildingBlacklist();
while (_itr < rules.length) {
final ResolutionRule rule = rules[_itr];
if (!blacklist.isBlacklisted(rule.getParameterizedFunction(), _target)) {
final ComputationTarget adjustedTarget = rule.adjustTarget(target);
if (adjustedTarget != null) {
final Collection<ValueSpecification> resultSet = resultSets[_itr];
final ValueSpecification result = rule.getResult(_valueName, adjustedTarget, _constraints, resultSet);
if (result != null) {
_next = Triple.of(rule.getParameterizedFunction(), result, resultSet);
_itr++;
return;
}
}
}
_itr++;
}
_next = null;
}
@Override
public boolean hasNext() {
if (_next == null) {
findNext(_context.getComputationTargetResolver().resolve(_target));
}
return _next != null;
}
@Override
public Triple<ParameterizedFunction, ValueSpecification, Collection<ValueSpecification>> next() {
if (_next == null) {
findNext(_context.getComputationTargetResolver().resolve(_target));
}
Triple<ParameterizedFunction, ValueSpecification, Collection<ValueSpecification>> next = _next;
_next = null;
return next;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
}