/** * Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.calc.marketdata; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import com.google.common.collect.ImmutableList; import com.opengamma.strata.collect.ArgChecker; import com.opengamma.strata.collect.tuple.Pair; import com.opengamma.strata.data.MarketDataId; import com.opengamma.strata.data.ObservableId; import com.opengamma.strata.data.scenario.ScenarioMarketData; /** * A node in a tree of dependencies of market data required by a set of calculations. * <p> * The immediate children of the root node are the market data values required by the calculations. Their child * nodes are the market data they depend on, and so on. * <p> * This tree is used to determine the order in which market data is built. The leaves of the tree * represent market data with no dependencies. This includes: * <ul> * <li>Market data that is already available</li> * <li>Observable data whose value can be obtained from a market data provider</li> * <li>Market data that can be built from data that is already available</li> * </ul> * For example, if a function requests a curve, there will be a node below the root representing the curve. * The curve's node will have a child node representing the curve group containing the curve. The curve group node * has child nodes representing the market data values at each of the curve points. It might also have a child node * representing another curve, or possibly an FX rate, and the curve and FX rate nodes would themselves * depend on market data values. */ final class MarketDataNode { /** The type of market data represented by the node, either a single value or a time series of values. */ enum DataType { /** The node represents a single market data value. */ SINGLE_VALUE, /** The node represents a time series of market data values. */ TIME_SERIES } /** The ID of the required market data. */ private final MarketDataId<?> id; /** The type of the required market data. */ private final DataType dataType; /** Child nodes identifying the market data required to build the market data in this node. */ private final List<MarketDataNode> dependencies; /** * Builds a tree representing the dependencies between items of market data and returns the root node. * * @param requirements IDs of the market data that must be provided * @param suppliedData data supplied by the user * @param marketDataConfig configuration specifying how market data values should be built * @param functions functions for market data, keyed by the type of market data ID they handle * @return the root node of the market data dependency tree */ static MarketDataNode buildDependencyTree( MarketDataRequirements requirements, ScenarioMarketData suppliedData, MarketDataConfig marketDataConfig, Map<Class<? extends MarketDataId<?>>, MarketDataFunction<?, ?>> functions) { DependencyTreeBuilder treeBuilder = DependencyTreeBuilder.of(suppliedData, requirements, marketDataConfig, functions); return MarketDataNode.root(treeBuilder.dependencyNodes()); } /** * Returns a root node which doesn't have a market data ID or data type. * * @param children the child nodes representing the market data dependencies of the root node * @return a root node which doesn't have a market data ID or data type */ static MarketDataNode root(List<MarketDataNode> children) { ArgChecker.notNull(children, "children"); return new MarketDataNode(null, null, children); } /** * Returns a child node representing an item of market data. * * @param id an ID identifying the market data represented by the node * @param dataType the type of market data represented by the node, either a single value or a time series of values * @param children the child nodes representing the market data dependencies of the node * @return a child node representing an item of market data */ static MarketDataNode child(MarketDataId<?> id, DataType dataType, List<MarketDataNode> children) { ArgChecker.notNull(id, "id"); ArgChecker.notNull(dataType, "dataType"); ArgChecker.notNull(children, "children"); return new MarketDataNode(id, dataType, children); } /** * Returns a leaf node representing an item of market data with no dependencies on other market data. * * @param id an ID identifying the market data represented by the node * @param dataType the type of market data represented by the node, either a single value or a time series of values * @return a leaf node representing an item of market data with no dependencies on other market data */ static MarketDataNode leaf(MarketDataId<?> id, DataType dataType) { ArgChecker.notNull(id, "id"); ArgChecker.notNull(dataType, "dataType"); return new MarketDataNode(id, dataType, ImmutableList.of()); } private MarketDataNode(MarketDataId<?> id, DataType dataType, List<MarketDataNode> dependencies) { this.dataType = dataType; this.id = id; this.dependencies = ImmutableList.copyOf(dependencies); } /** * Returns a copy of the dependency tree without the leaf nodes. It also returns the market data requirements * represented by the leaf nodes. * <p> * The leaf nodes represent market data with no missing requirements for market data. This includes: * <ul> * <li>Market data that is already available</li> * <li>Observable data whose value can be obtained from a market data provider</li> * <li>Market data that can be built from data that is already available</li> * </ul> * Therefore the market data represented by the leaf nodes can be built immediately. * * @return a copy of the dependency tree without the leaf nodes and the market data requirements * represented by the leaf nodes */ Pair<MarketDataNode, MarketDataRequirements> withLeavesRemoved() { ImmutableList.Builder<MarketDataNode> childNodesBuilder = ImmutableList.builder(); MarketDataRequirementsBuilder requirementsBuilder = MarketDataRequirements.builder(); for (MarketDataNode child : dependencies) { if (child.isLeaf()) { switch (child.dataType) { case SINGLE_VALUE: requirementsBuilder.addValues(child.id); break; case TIME_SERIES: requirementsBuilder.addTimeSeries(((ObservableId) child.id)); break; } } else { Pair<MarketDataNode, MarketDataRequirements> childResult = child.withLeavesRemoved(); childNodesBuilder.add(childResult.getFirst()); requirementsBuilder.addRequirements(childResult.getSecond()); } } MarketDataNode node = new MarketDataNode(id, dataType, childNodesBuilder.build()); MarketDataRequirements requirements = requirementsBuilder.build(); return Pair.of(node, requirements); } /** * Returns true if this node has no children. * * @return true if this node has no children */ boolean isLeaf() { return dependencies.isEmpty(); } /** * Returns the ID of the market data value represented by this node. * * @return the ID of the market data value represented by this node */ public MarketDataId<?> getId() { return id; } /** * Prints this node and its tree of dependencies to an ASCII tree. * * @param builder a string builder into which the result will be written * @param indent the indent printed at the start of the line before the node * @param childIndent the indent printed at the start of the line before the node's children * @return the string builder containing the pretty-printed tree */ private StringBuilder prettyPrint(StringBuilder builder, String indent, String childIndent) { String nodeDescription = (id == null) ? "Root" : (id + " " + dataType); builder.append('\n').append(indent).append(nodeDescription); for (Iterator<MarketDataNode> it = dependencies.iterator(); it.hasNext();) { MarketDataNode child = it.next(); String newIndent; String newChildIndent; boolean isFinalChild = !it.hasNext(); if (!isFinalChild) { newIndent = childIndent + " |--"; // Unicode boxes: \u251c\u2500\u2500 newChildIndent = childIndent + " | "; // Unicode boxes: \u2502 } else { newIndent = childIndent + " `--"; // Unicode boxes: \u2514\u2500\u2500 newChildIndent = childIndent + " "; } child.prettyPrint(builder, newIndent, newChildIndent); } return builder; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } MarketDataNode that = (MarketDataNode) o; return Objects.equals(id, that.id) && Objects.equals(dataType, that.dataType) && Objects.equals(dependencies, that.dependencies); } @Override public int hashCode() { return Objects.hash(id, dataType, dependencies); } @Override public String toString() { return prettyPrint(new StringBuilder(), "", "").toString(); } }