/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.engine.depgraph;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.opengamma.engine.function.CompiledFunctionDefinition;
import com.opengamma.engine.function.ParameterizedFunction;
import com.opengamma.engine.function.exclusion.FunctionExclusionGroup;
import com.opengamma.engine.function.exclusion.FunctionExclusionGroups;
import com.opengamma.engine.value.ValueRequirement;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.util.tuple.Pair;
import com.opengamma.util.tuple.Triple;
/**
* Base class for steps that are based on iterating through a collection of candidate functions.
*
* @param <T> the root iteration type
*/
/* package */abstract class FunctionIterationStep extends ResolveTask.State {
private static final Logger s_logger = LoggerFactory.getLogger(FunctionIterationStep.class);
public abstract static class IterationBaseStep extends ResolveTask.State {
public IterationBaseStep(ResolveTask task) {
super(task);
}
/**
* Attempts a function application.
* <p>
* The {@code resolvedOutput} value must be normalized.
*
* @param context the graph building context, not null
* @param resolvedOutput the provisional resolved value specification, not null
* @param resolvedFunction the function to apply, containing the definition, satisfying maximal specification, and all maximal output specifications
*/
protected void functionApplication(final GraphBuildingContext context, final ValueSpecification resolvedOutput,
final Triple<ParameterizedFunction, ValueSpecification, Collection<ValueSpecification>> resolvedFunction) {
final Pair<ResolveTask[], ResolvedValueProducer[]> existing = context.getTasksProducing(resolvedOutput);
if (existing == null) {
final ResolvedValue existingValue = context.getProduction(resolvedOutput);
if (existingValue == null) {
// We're going to work on producing
s_logger.debug("Creating producer for {}", resolvedOutput);
setRunnableTaskState(new FunctionApplicationStep(getTask(), this, resolvedFunction, resolvedOutput), context);
} else {
// Value has already been produced
s_logger.debug("Using existing production of {}", resolvedOutput);
final ResolveTask.State state = new ExistingProductionStep(getTask(), this, resolvedFunction, resolvedOutput);
if (setTaskState(state)) {
if (!pushResult(context, existingValue, false)) {
s_logger.debug("Production not accepted - rescheduling");
state.setRunnableTaskState(this, context);
}
}
}
} else {
// Other tasks are working on it, or have already worked on it
s_logger.debug("Delegating to existing producers for {}", resolvedOutput);
final ExistingResolutionsStep state = new ExistingResolutionsStep(getTask(), this, resolvedFunction, resolvedOutput);
if (setTaskState(state)) {
ResolvedValueProducer singleTask = null;
AggregateResolvedValueProducer aggregate = null;
// Must not introduce a loop (checking parent resolve tasks isn't sufficient) so only use "finished" tasks.
final ResolveTask[] existingTasks = existing.getFirst();
final ResolvedValueProducer[] existingProducers = existing.getSecond();
for (int i = 0; i < existingTasks.length; i++) {
if (existingTasks[i].isFinished()) {
// Can use this task without creating a loop
if (singleTask == null) {
singleTask = existingProducers[i];
singleTask.addRef(); // There is already an open count from when we got the producers
} else {
if (aggregate == null) {
aggregate = new AggregateResolvedValueProducer(getValueRequirement());
aggregate.addProducer(context, singleTask);
singleTask.release(context);
}
aggregate.addProducer(context, existingProducers[i]);
}
}
// Only the producers are ref-counted
existingProducers[i].release(context);
}
if (aggregate != null) {
aggregate.addCallback(context, state);
aggregate.start(context);
aggregate.release(context);
} else {
if (singleTask != null) {
singleTask.addCallback(context, state);
singleTask.release(context);
} else {
// Other threads haven't progressed to completion - try and produce the value ourselves
s_logger.debug("No suitable delegate found - creating producer");
state.setRunnableTaskState(new FunctionApplicationStep(getTask(), this, resolvedFunction, resolvedOutput), context);
}
}
}
}
}
/**
* Returns the desired value for the production. This might be a more constrained form than the value requirement being satisfied for graph construction.
*
* @return the desired value, not null
*/
protected abstract ValueRequirement getDesiredValue();
/**
* Resolves the output values declared by a function at late resolution against the current requirement. The one that satisfies the requirement is composed, added to the set, and returned. All
* other output specifications are added to the output set unchanged.
* <p>
* The returned specification must be normalized.
*
* @param context the graph building context, not null
* @param newOutputValues the output values returned by the function, not null
* @param resolvedOutputValues the composed output values, not null
* @return the satisfying resolved output, or null if none satisfy
*/
protected abstract ValueSpecification getResolvedOutputs(GraphBuildingContext context, Set<ValueSpecification> newOutputValues, Set<ValueSpecification> resolvedOutputValues);
/**
* For debugging/diagnostic reporting.
*/
protected abstract void reportResult();
@Override
protected void pump(final GraphBuildingContext context) {
// No-op; happens if a worker "finishes" a function application PumpingState and it progresses to the next natural
// state in advance of the pump from the abstract value producer. See PumpingState.finished for an explanation
}
}
private final IterationBaseStep _base;
public FunctionIterationStep(final ResolveTask task, final IterationBaseStep base) {
super(task);
_base = base;
}
protected IterationBaseStep getIterationBase() {
return _base;
}
protected Collection<FunctionExclusionGroup> getFunctionExclusion(final GraphBuildingContext context, final CompiledFunctionDefinition function) {
final FunctionExclusionGroups groups = context.getFunctionExclusionGroups();
if (groups == null) {
return null;
}
final FunctionExclusionGroup functionExclusion = groups.getExclusionGroup(function.getFunctionDefinition());
if (functionExclusion == null) {
return getTask().getFunctionExclusion();
}
final Collection<FunctionExclusionGroup> parentExclusion = getTask().getFunctionExclusion();
if (parentExclusion != null) {
return groups.withExclusion(parentExclusion, functionExclusion);
} else {
return Collections.singleton(functionExclusion);
}
}
@Override
protected boolean run(final GraphBuildingContext context) {
if (setTaskState(getIterationBase())) {
return getIterationBase().run(context);
} else {
return true;
}
}
}