/** * 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 static com.opengamma.strata.collect.Guavate.toImmutableList; import java.util.List; import java.util.Map; import java.util.Set; import com.google.common.collect.ImmutableList; import com.opengamma.strata.data.MarketDataId; import com.opengamma.strata.data.ObservableId; import com.opengamma.strata.data.scenario.ScenarioMarketData; /** * Builds a dependency tree for the items of market used in a set of calculations. * <p> * The root of the tree represents the calculations and the child nodes represent items of market data on which * the calculations depend. Market data can depend on other market data, creating a tree structure of unlimited * depth. * <p> * Edges between nodes represent dependencies on items of market data. Leaf nodes represent market data * with no unsatisfied dependencies which can be built immediately. * <p> * See {@link MarketDataNode} for more detailed documentation. * * @see MarketDataNode */ final class DependencyTreeBuilder { /** The market data supplied by the user. */ private final ScenarioMarketData suppliedData; /** The functions that create items of market data. */ private final Map<Class<? extends MarketDataId<?>>, MarketDataFunction<?, ?>> functions; /** The requirements for market data used in a set of calculations. */ private final MarketDataRequirements requirements; /** Configuration specifying how market data values should be built. */ private final MarketDataConfig marketDataConfig; /** * Returns a tree builder that builds the dependency tree for the market data required by a set of calculations. * * @param suppliedData market data supplied by the user * @param requirements specifies the market data required for the calculations * @param marketDataConfig configuration specifying how market data values should be built * @param functions functions that create items of market data * @return a tree builder that builds the dependency tree for the market data required by a set of calculations */ static DependencyTreeBuilder of( ScenarioMarketData suppliedData, MarketDataRequirements requirements, MarketDataConfig marketDataConfig, Map<Class<? extends MarketDataId<?>>, MarketDataFunction<?, ?>> functions) { return new DependencyTreeBuilder(suppliedData, requirements, marketDataConfig, functions); } private DependencyTreeBuilder( ScenarioMarketData suppliedData, MarketDataRequirements requirements, MarketDataConfig marketDataConfig, Map<Class<? extends MarketDataId<?>>, MarketDataFunction<?, ?>> functions) { this.suppliedData = suppliedData; this.requirements = requirements; this.marketDataConfig = marketDataConfig; this.functions = functions; } /** * Returns nodes representing the dependencies of the market data required for a set of calculations. * * @return nodes representing the dependencies of the market data required for a set of calculations */ List<MarketDataNode> dependencyNodes() { return dependencyNodes(requirements); } /** * Returns nodes representing the dependencies of a set of market data. * * @param requirements requirements for market data needed for a set of calculations * @return nodes representing the dependencies of a set of market data */ private List<MarketDataNode> dependencyNodes(MarketDataRequirements requirements) { List<MarketDataNode> observableNodes = buildNodes(requirements.getObservables(), MarketDataNode.DataType.SINGLE_VALUE); List<MarketDataNode> nonObservableNodes = buildNodes(requirements.getNonObservables(), MarketDataNode.DataType.SINGLE_VALUE); List<MarketDataNode> timeSeriesNodes = buildNodes(requirements.getTimeSeries(), MarketDataNode.DataType.TIME_SERIES); return ImmutableList.<MarketDataNode>builder() .addAll(observableNodes) .addAll(nonObservableNodes) .addAll(timeSeriesNodes) .build(); } /** * Builds nodes for a set of market data IDs. * * @param ids the IDs * @param dataType the type of data represented by the IDs, either single values or time series of values * @return market data nodes for the IDs */ private List<MarketDataNode> buildNodes(Set<? extends MarketDataId<?>> ids, MarketDataNode.DataType dataType) { return ids.stream() .map(id -> buildNode(id, dataType)) .collect(toImmutableList()); } /** * Builds a node for a market data ID. * * @param id the ID * @param dataType the type of data represented by the ID, either a single value or a time series of values * @return a market data node for the ID */ private MarketDataNode buildNode(MarketDataId<?> id, MarketDataNode.DataType dataType) { // Observable data has special handling and is guaranteed to have a function. // Supplied data definitely has no dependencies because it already exists and doesn't need to be built. if (id instanceof ObservableId || isSupplied(id, dataType, suppliedData)) { return MarketDataNode.leaf(id, dataType); } // Find the function that can build the data identified by the ID @SuppressWarnings("rawtypes") MarketDataFunction function = functions.get(id.getClass()); if (function != null) { try { @SuppressWarnings("unchecked") MarketDataRequirements requirements = function.requirements(id, marketDataConfig); return MarketDataNode.child(id, dataType, dependencyNodes(requirements)); } catch (Exception e) { return MarketDataNode.child(id, dataType, ImmutableList.of()); } } else { // If there is no function insert a leaf node. It will be flagged as an error when the data is built return MarketDataNode.leaf(id, dataType); } } /** * Returns true if the market data identified by the ID and data type is present in the supplied data. * * @param id an ID identifying market data * @param dataType the data type of the market data, either a single value or a time series of values * @return true if the market data identified by the ID and data type is present in the supplied data */ private static boolean isSupplied( MarketDataId<?> id, MarketDataNode.DataType dataType, ScenarioMarketData suppliedData) { switch (dataType) { case TIME_SERIES: return (id instanceof ObservableId) && !suppliedData.getTimeSeries((ObservableId) id).isEmpty(); case SINGLE_VALUE: return suppliedData.containsValue(id); default: throw new IllegalArgumentException("Unexpected data type " + dataType); } } }