/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.engine.depgraph.impl; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Arrays; import java.util.Collection; import java.util.Map; import java.util.Set; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.opengamma.engine.ComputationTargetSpecification; import com.opengamma.engine.depgraph.DependencyNode; import com.opengamma.engine.depgraph.DependencyNodeFunction; import com.opengamma.engine.value.ValueSpecification; import com.opengamma.util.ArgumentChecker; /** * Default implementation of a {@link DependencyNode}. */ public class DependencyNodeImpl implements DependencyNode, Serializable { // TODO: Change DependencyNode from an interface to an abstract class and put the static stuff into it rather than have static methods & instanceof checks here private static final long serialVersionUID = 1L; private static final DependencyNode[] EMPTY_NODE_ARRAY = new DependencyNode[0]; private static final ValueSpecification[] EMPTY_SPECIFICATION_ARRAY = new ValueSpecification[0]; /** * The target specification. */ private final ComputationTargetSpecification _target; /** * The function and parameters. */ private DependencyNodeFunction _function; /** * The values consumed by this node. These can be considered as the labels on input edges. The length of this array and order matches {@link #_inputNodes}. */ private final ValueSpecification[] _inputValues; /** * The nodes that produce values consumed by this node. The length of this array and order matches {@link #_inputNodes}. */ private final DependencyNode[] _inputNodes; // TODO: If there are more then N inputs, then sort the array by value specification to speed up the findInputValue operation /** * The values produced by this node. These can be considered as potential labels on output edges. */ private final ValueSpecification[] _outputValues; // TODO: If there are more than N outputs, then sort the array by value specification to speed up the hasOutputValue operation // Construction operations /** * Creates a new node. * * @param function the function, not null * @param target the target specification, not null * @param outputs the outputs of the node, not null and not containing null * @param inputs the input values to the node, not null and not containing null */ public DependencyNodeImpl(final DependencyNodeFunction function, final ComputationTargetSpecification target, final Collection<ValueSpecification> outputs, final Map<ValueSpecification, DependencyNode> inputs) { ArgumentChecker.notNull(function, "function"); ArgumentChecker.notNull(target, "target"); ArgumentChecker.notNull(outputs, "outputs"); ArgumentChecker.notNull(inputs, "inputs"); _function = function; _target = target; int size = outputs.size(); _outputValues = new ValueSpecification[size]; int i = 0; for (ValueSpecification output : outputs) { ArgumentChecker.notNull(output, "output"); assert _target.equals(output.getTargetSpecification()); _outputValues[i++] = output; } assert i == size; size = inputs.size(); if (size == 0) { _inputValues = EMPTY_SPECIFICATION_ARRAY; _inputNodes = EMPTY_NODE_ARRAY; } else { _inputValues = new ValueSpecification[size]; _inputNodes = new DependencyNode[size]; i = 0; for (Map.Entry<ValueSpecification, DependencyNode> input : inputs.entrySet()) { ArgumentChecker.notNull(input, "input"); ArgumentChecker.notNull(input.getKey(), "input.key"); ArgumentChecker.notNull(input.getValue(), "input.value"); _inputValues[i] = input.getKey(); _inputNodes[i] = input.getValue(); i++; } assert i == size; } } private DependencyNodeImpl(final DependencyNodeFunction function, final ComputationTargetSpecification target, final ValueSpecification[] outputValues, final ValueSpecification[] inputValues, final DependencyNode[] inputNodes) { _function = function; _target = target; _outputValues = outputValues; _inputValues = inputValues; _inputNodes = inputNodes; } private DependencyNode addInputs(final ValueSpecification[] inputValues, final DependencyNode[] inputNodes) { int additionalInputs = 0; for (int i = 0; i < inputValues.length; i++) { if (findInputValue(inputValues[i]) < 0) { inputValues[additionalInputs] = inputValues[i]; inputNodes[additionalInputs] = inputNodes[i]; additionalInputs++; } } if (additionalInputs == 0) { // No changes; all of those inputs are present return this; } final int oldInputs = _inputValues.length; final ValueSpecification[] newInputValues = Arrays.copyOf(_inputValues, oldInputs + additionalInputs); final DependencyNode[] newInputNodes = Arrays.copyOf(_inputNodes, oldInputs + additionalInputs); System.arraycopy(inputValues, 0, newInputValues, oldInputs, additionalInputs); System.arraycopy(inputNodes, 0, newInputNodes, oldInputs, additionalInputs); return new DependencyNodeImpl(_function, _target, _outputValues, newInputValues, newInputNodes); } public static DependencyNode addInputs(final DependencyNode oldNode, final ValueSpecification[] inputValues, final DependencyNode[] inputNodes) { if (oldNode instanceof DependencyNodeImpl) { return ((DependencyNodeImpl) oldNode).addInputs(inputValues, inputNodes); } else { int additionalInputs = 0; for (int i = 0; i < inputValues.length; i++) { if (oldNode.findInputValue(inputValues[i]) < 0) { inputValues[additionalInputs] = inputValues[i]; inputNodes[additionalInputs] = inputNodes[i]; additionalInputs++; } } if (additionalInputs == 0) { // No changes; all of those inputs are present return oldNode; } final int oldInputs = oldNode.getInputCount(); final ValueSpecification[] newInputValues = new ValueSpecification[oldInputs + additionalInputs]; final DependencyNode[] newInputNodes = new DependencyNode[oldInputs + additionalInputs]; for (int i = 0; i < oldInputs; i++) { newInputValues[i] = oldNode.getInputValue(i); newInputNodes[i] = oldNode.getInputNode(i); } System.arraycopy(inputValues, 0, newInputValues, oldInputs, additionalInputs); System.arraycopy(inputNodes, 0, newInputNodes, oldInputs, additionalInputs); return new DependencyNodeImpl(oldNode.getFunction(), oldNode.getTarget(), getOutputValueArray(oldNode), inputValues, inputNodes); } } private DependencyNode replaceInput(final ValueSpecification oldInputValue, final ValueSpecification newInputValue, final DependencyNode newInputNode) { final int oldInputs = _inputValues.length; ValueSpecification[] newInputValues = new ValueSpecification[oldInputs]; DependencyNode[] newInputNodes = new DependencyNode[oldInputs]; boolean replaced = false; int j = 0; for (int i = 0; i < oldInputs; i++) { final ValueSpecification oldInput = _inputValues[i]; if (oldInputValue.equals(oldInput) || newInputValue.equals(oldInput)) { if (!replaced) { // Apply the replacement, but don't introduce a duplicate newInputValues[j] = newInputValue; newInputNodes[j++] = newInputNode; replaced = true; } } else { // Keep the existing input newInputValues[j] = oldInput; newInputNodes[j++] = _inputNodes[i]; } } if (j < oldInputs) { // A duplicate has shortened the input arrays newInputValues = Arrays.copyOf(newInputValues, j); newInputNodes = Arrays.copyOf(newInputNodes, j); } return new DependencyNodeImpl(_function, _target, _outputValues, newInputValues, newInputNodes); } public static DependencyNode replaceInput(final DependencyNode oldNode, final ValueSpecification oldInputValue, final ValueSpecification newInputValue, final DependencyNode newInputNode) { if (oldNode instanceof DependencyNodeImpl) { return ((DependencyNodeImpl) oldNode).replaceInput(oldInputValue, newInputValue, newInputNode); } else { final int oldInputs = oldNode.getInputCount(); ValueSpecification[] newInputValues = new ValueSpecification[oldInputs]; DependencyNode[] newInputNodes = new DependencyNode[oldInputs]; boolean replaced = false; int j = 0; for (int i = 0; i < oldInputs; i++) { final ValueSpecification oldInput = oldNode.getInputValue(i); if (oldInputValue.equals(oldInput) || newInputValue.equals(oldInput)) { if (!replaced) { newInputValues[j] = newInputValue; newInputNodes[j++] = newInputNode; replaced = true; } } else { newInputValues[j] = oldInput; newInputNodes[j++] = oldNode.getInputNode(i); } } if (j < oldInputs) { newInputValues = Arrays.copyOf(newInputValues, j); newInputNodes = Arrays.copyOf(newInputNodes, j); } return new DependencyNodeImpl(oldNode.getFunction(), oldNode.getTarget(), getOutputValueArray(oldNode), newInputValues, newInputNodes); } } private DependencyNode withOutputs(final ValueSpecification[] outputValues) { return new DependencyNodeImpl(_function, _target, outputValues, _inputValues, _inputNodes); } public static DependencyNode withOutputs(final DependencyNode oldNode, final ValueSpecification[] outputValues) { if (oldNode instanceof DependencyNodeImpl) { return ((DependencyNodeImpl) oldNode).withOutputs(outputValues); } else { return new DependencyNodeImpl(oldNode.getFunction(), oldNode.getTarget(), outputValues, getInputValueArray(oldNode), getInputNodeArray(oldNode)); } } private static boolean outputValueTargets(final ComputationTargetSpecification target, final ValueSpecification[] outputValues) { for (ValueSpecification outputValue : outputValues) { if (!target.equals(outputValue.getTargetSpecification())) { return false; } } return true; } public static DependencyNode of(final DependencyNodeFunction function, final ComputationTargetSpecification target, final ValueSpecification[] outputValues, final ValueSpecification[] inputValues, final DependencyNode[] inputNodes) { ArgumentChecker.notNull(function, "function"); ArgumentChecker.notNull(target, "target"); ArgumentChecker.noNulls(outputValues, "outputValues"); ArgumentChecker.noNulls(inputValues, "inputValues"); ArgumentChecker.noNulls(inputNodes, "inputNodes"); assert inputValues.length == inputNodes.length; assert outputValueTargets(target, outputValues); return new DependencyNodeImpl(function, target, outputValues, inputValues, inputNodes); } private DependencyNode removeUnnecessaryValues(final Map<ValueSpecification, DependencyNode> necessary) { ValueSpecification[] newOutputs = null; int newOutputCount = 0; for (int j = 0; j < _outputValues.length; j++) { final ValueSpecification output = _outputValues[j]; if (necessary.containsKey(output)) { if (newOutputs != null) { newOutputs[newOutputCount++] = output; } } else { if (newOutputs == null) { newOutputs = new ValueSpecification[_outputValues.length - 1]; if (j > 0) { newOutputCount = j; System.arraycopy(_outputValues, 0, newOutputs, 0, j); } } } } if ((newOutputs != null) && (newOutputCount == 0)) { // We have a node that isn't needed anymore - it produces no necessary outputs return null; } ValueSpecification[] newInputValues = null; DependencyNode[] newInputNodes = null; int newInputCount = 0; for (int j = 0; j < _inputValues.length; j++) { final ValueSpecification inputValue = _inputValues[j]; final DependencyNode oldInputNode = _inputNodes[j]; DependencyNode newInputNode = necessary.get(inputValue); if (newInputNode == null) { assert necessary.containsKey(inputValue); newInputNode = removeUnnecessaryValues(oldInputNode, necessary); assert newInputNode != null; } if (newInputNode == oldInputNode) { if (newInputValues != null) { newInputValues[newInputCount] = inputValue; newInputNodes[newInputCount++] = newInputNode; } } else { if (newInputValues == null) { newInputValues = new ValueSpecification[_inputValues.length]; newInputNodes = new DependencyNode[_inputValues.length]; newInputCount = j; System.arraycopy(_inputValues, 0, newInputValues, 0, newInputCount); System.arraycopy(_inputNodes, 0, newInputNodes, 0, newInputCount); } newInputValues[newInputCount] = inputValue; newInputNodes[newInputCount++] = newInputNode; } } final DependencyNode newNode; if (newOutputs == null) { newOutputs = _outputValues; newOutputCount = _outputValues.length; if (newInputNodes == null) { // No changes required at this node newNode = this; } else { // New inputs, previous output set if (newInputCount != newInputValues.length) { newInputValues = Arrays.copyOf(newInputValues, newInputCount); newInputNodes = Arrays.copyOf(newInputNodes, newInputCount); } newNode = new DependencyNodeImpl(_function, _target, newOutputs, newInputValues, newInputNodes); } } else { if (newOutputCount != newOutputs.length) { newOutputs = Arrays.copyOf(newOutputs, newOutputCount); } if (newInputNodes == null) { // New outputs, previous input set newInputValues = _inputValues; newInputNodes = _inputNodes; } else { if (newInputCount != newInputValues.length) { newInputValues = Arrays.copyOf(newInputValues, newInputCount); newInputNodes = Arrays.copyOf(newInputNodes, newInputCount); } } newNode = new DependencyNodeImpl(_function, _target, newOutputs, newInputValues, newInputNodes); } for (int i = 0; i < newOutputCount; i++) { necessary.put(newOutputs[i], newNode); } return newNode; } /* package */static DependencyNode removeUnnecessaryValues(final DependencyNode oldNode, final Map<ValueSpecification, DependencyNode> necessary) { if (oldNode instanceof DependencyNodeImpl) { return ((DependencyNodeImpl) oldNode).removeUnnecessaryValues(necessary); } else { int outputCount = oldNode.getOutputCount(); ValueSpecification[] newOutputs = null; int newOutputCount = 0; for (int j = 0; j < outputCount; j++) { final ValueSpecification output = oldNode.getOutputValue(j); if (necessary.containsKey(output)) { if (newOutputs != null) { newOutputs[newOutputCount++] = output; } } else { if (newOutputs == null) { newOutputs = new ValueSpecification[outputCount - 1]; for (newOutputCount = 0; newOutputCount < j; newOutputCount++) { newOutputs[newOutputCount] = oldNode.getOutputValue(newOutputCount); } } } } if ((newOutputs != null) && (newOutputCount == 0)) { // We have a node that isn't needed anymore - it produces no necessary outputs return null; } final int inputCount = oldNode.getInputCount(); ValueSpecification[] newInputValues = null; DependencyNode[] newInputNodes = null; int newInputCount = 0; for (int j = 0; j < inputCount; j++) { final ValueSpecification inputValue = oldNode.getInputValue(j); final DependencyNode oldInputNode = oldNode.getInputNode(j); DependencyNode newInputNode = necessary.get(inputValue); if (newInputNode == null) { assert necessary.containsKey(inputValue); newInputNode = removeUnnecessaryValues(oldInputNode, necessary); assert newInputNode != null; } if (newInputNode == oldInputNode) { if (newInputValues != null) { newInputValues[newInputCount] = inputValue; newInputNodes[newInputCount++] = newInputNode; } } else { if (newInputValues == null) { newInputValues = new ValueSpecification[inputCount]; newInputNodes = new DependencyNode[inputCount]; for (newInputCount = 0; newInputCount < j; newInputCount++) { newInputValues[newInputCount] = oldNode.getInputValue(newInputCount); newInputNodes[newInputCount] = oldNode.getInputNode(newInputCount); } } newInputValues[newInputCount] = inputValue; newInputNodes[newInputCount++] = newInputNode; } } final DependencyNode newNode; if (newOutputs == null) { newOutputs = DependencyNodeImpl.getOutputValueArray(oldNode); newOutputCount = newOutputs.length; if (newInputNodes == null) { // No changes required at this node newNode = oldNode; } else { // New inputs, previous output set if (newInputCount != newInputValues.length) { newInputValues = Arrays.copyOf(newInputValues, newInputCount); newInputNodes = Arrays.copyOf(newInputNodes, newInputCount); } newNode = new DependencyNodeImpl(oldNode.getFunction(), oldNode.getTarget(), newOutputs, newInputValues, newInputNodes); } } else { if (newOutputCount != newOutputs.length) { newOutputs = Arrays.copyOf(newOutputs, newOutputCount); } if (newInputNodes == null) { // New outputs, previous input set newInputValues = DependencyNodeImpl.getInputValueArray(oldNode); newInputNodes = DependencyNodeImpl.getInputNodeArray(oldNode); } else { if (newInputCount != newInputValues.length) { newInputValues = Arrays.copyOf(newInputValues, newInputCount); newInputNodes = Arrays.copyOf(newInputNodes, newInputCount); } } newNode = new DependencyNodeImpl(oldNode.getFunction(), oldNode.getTarget(), newOutputs, newInputValues, newInputNodes); } for (int i = 0; i < newOutputCount; i++) { necessary.put(newOutputs[i], newNode); } return newNode; } } // Function & Target operations @Override public DependencyNodeFunction getFunction() { return _function; } @Override public ComputationTargetSpecification getTarget() { return _target; } // Input node/value operations @Override public int getInputCount() { return _inputValues.length; } @Override public ValueSpecification getInputValue(final int index) { return _inputValues[index]; } @Override public DependencyNode getInputNode(final int index) { return _inputNodes[index]; } @Override public int findInputValue(final ValueSpecification value) { for (int i = 0; i < _inputValues.length; i++) { if (value.equals(_inputValues[i])) { return i; } } return -1; } /** * Obtains a copy of the input value specifications for a node as a set. * * @param node the node instance to query * @return the set of input value specifications to the node */ public static Set<ValueSpecification> getInputValues(final DependencyNode node) { final int count = node.getInputCount(); final Set<ValueSpecification> inputs = Sets.newHashSetWithExpectedSize(count); for (int i = 0; i < count; i++) { inputs.add(node.getInputValue(i)); } return inputs; } /** * Obtains a copy of the input value specifications and nodes as a map of value specifications to the node that produces each one * * @param node the node instance to query * @return the input values and nodes */ public static Map<ValueSpecification, DependencyNode> getInputs(final DependencyNode node) { final int count = node.getInputCount(); final Map<ValueSpecification, DependencyNode> inputs = Maps.newHashMapWithExpectedSize(count); for (int i = 0; i < count; i++) { inputs.put(node.getInputValue(i), node.getInputNode(i)); } return inputs; } /** * Obtains a copy of the input values to a node as an array. * * @param node the node instance to query, not null * @return the array of input values to the node, not null and not containing null */ public static ValueSpecification[] getInputValueArray(final DependencyNode node) { final int count = node.getInputCount(); if (count == 0) { return EMPTY_SPECIFICATION_ARRAY; } final ValueSpecification[] inputs = new ValueSpecification[count]; for (int i = 0; i < count; i++) { inputs[i] = node.getInputValue(i); } return inputs; } /** * Obtains a copy of the input nodes to a node as an array. * * @param node the node instance to query, not null * @return the array of input nodes, not null and not containing null */ public static DependencyNode[] getInputNodeArray(final DependencyNode node) { final int count = node.getInputCount(); if (count == 0) { return EMPTY_NODE_ARRAY; } final DependencyNode[] inputs = new DependencyNode[count]; for (int i = 0; i < count; i++) { inputs[i] = node.getInputNode(i); } return inputs; } // Output node/value operations @Override public int getOutputCount() { return _outputValues.length; } @Override public ValueSpecification getOutputValue(final int index) { return _outputValues[index]; } @Override public boolean hasOutputValue(final ValueSpecification value) { for (int i = 0; i < _outputValues.length; i++) { if (value.equals(_outputValues[i])) { return true; } } return false; } /** * Obtains a copy of the output value specifications for a node as a set. * * @param node the node instance to query, not null * @return the set of output value specifications to the node, not null */ public static Set<ValueSpecification> getOutputValues(final DependencyNode node) { final int count = node.getOutputCount(); final Set<ValueSpecification> inputs = Sets.newHashSetWithExpectedSize(count); for (int i = 0; i < count; i++) { inputs.add(node.getOutputValue(i)); } return inputs; } /** * Obtains a copy of the output value specifications for a node as an array. * * @param node the node instance to query, not null * @return the array of output value specifications to the node, not null */ public static ValueSpecification[] getOutputValueArray(final DependencyNode node) { final int count = node.getOutputCount(); final ValueSpecification[] outputs = new ValueSpecification[count]; for (int i = 0; i < count; i++) { outputs[i] = node.getOutputValue(i); } return outputs; } /* package */static void gatherOutputValues(final DependencyNode node, final Map<ValueSpecification, DependencyNode> outputs) { int count = node.getOutputCount(); for (int i = 0; i < count; i++) { final ValueSpecification output = node.getOutputValue(i); DependencyNode existing = outputs.put(output, node); if (existing != null) { assert existing == node; return; } } count = node.getInputCount(); for (int i = 0; i < count; i++) { gatherOutputValues(node.getInputNode(i), outputs); } } // Misc @Override public String toString() { return "Node" + Integer.toHexString(System.identityHashCode(this)) + "[" + getFunction() + " on " + getTarget() + ", " + _inputValues.length + " input(s), " + _outputValues.length + " output(s)]"; } private void writeObject(final ObjectOutputStream out) throws IOException { if (!(_function instanceof Serializable)) { _function = DependencyNodeFunctionImpl.of(_function.getFunctionId(), _function.getParameters()); } out.defaultWriteObject(); } }