/**
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.sesame.marketdata.builders;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.threeten.bp.LocalDate;
import org.threeten.bp.ZonedDateTime;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.opengamma.engine.marketdata.spec.MarketDataSpecification;
import com.opengamma.sesame.marketdata.CompositeMarketDataBundle;
import com.opengamma.sesame.marketdata.MarketDataEnvironment;
import com.opengamma.sesame.marketdata.MarketDataEnvironmentBuilder;
import com.opengamma.sesame.marketdata.MarketDataFactory;
import com.opengamma.sesame.marketdata.MarketDataId;
import com.opengamma.sesame.marketdata.MarketDataRequirement;
import com.opengamma.sesame.marketdata.MarketDataSource;
import com.opengamma.sesame.marketdata.SingleValueRequirement;
import com.opengamma.sesame.marketdata.TimeSeriesRequirement;
import com.opengamma.sesame.marketdata.scenarios.CyclePerturbations;
import com.opengamma.sesame.marketdata.scenarios.SingleScenarioDefinition;
import com.opengamma.timeseries.date.DateTimeSeries;
import com.opengamma.timeseries.date.localdate.ImmutableLocalDateDoubleTimeSeries;
import com.opengamma.timeseries.date.localdate.ImmutableLocalDateObjectTimeSeries;
import com.opengamma.timeseries.date.localdate.LocalDateDoubleTimeSeries;
import com.opengamma.timeseries.date.localdate.LocalDateObjectTimeSeries;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.result.Result;
import com.opengamma.util.time.LocalDateRange;
/**
* Sources and builds market data required for performing calculations in the calculation engine.
* <p>
* This class is used by the engine if it needs to perform some calculations and doesn't have all the required
* market data. It sits between the low level market data provider and the {@link MarketDataBuilder} instances
* that do the actual work of building the market data.
*/
public class MarketDataEnvironmentFactory {
private static final Logger s_logger = LoggerFactory.getLogger(MarketDataEnvironmentFactory.class);
// might need to make this a multimap and offer a requirement to each applicable builder in turn
private final Map<Class<? extends MarketDataId>, MarketDataBuilder> _builders;
private final MarketDataFactory _marketDataFactory;
/**
* @param marketDataFactory provides sources of low-level market data
* @param builders build high level market data from lower level data
*/
public MarketDataEnvironmentFactory(MarketDataFactory marketDataFactory, MarketDataBuilder... builders) {
this(marketDataFactory, Arrays.asList(builders));
}
/**
* @param marketDataFactory provides sources of low-level market data
* @param builders build high level market data from lower level data
*/
public MarketDataEnvironmentFactory(MarketDataFactory marketDataFactory, List<MarketDataBuilder> builders) {
ArgumentChecker.notNull(builders, "builders");
_marketDataFactory = ArgumentChecker.notNull(marketDataFactory, "marketDataFactory");
ImmutableMap.Builder<Class<? extends MarketDataId>, MarketDataBuilder> mapBuilder =
ImmutableMap.builder();
for (MarketDataBuilder builder : builders) {
mapBuilder.put(builder.getKeyType(), builder);
}
_builders = mapBuilder.build();
}
/**
* Builds a bundle of market data to satisfy a set of requirements.
* <p>
* The return value includes the supplied data as well as the data built by the factory.
* <p>
* If {@code requirements} contains a requirement that can't be satisfied by {@code suppliedData} then this
* method will attempt to build it by delegating to the {@link MarketDataBuilder} instances.
*
* @param suppliedData existing market data
* @param requirements requirements for the market data that the factory should build
* @param marketDataSpec specifies which low-level market data providers should be used to source the raw
* data for building the market data
* @param valuationTime the valuation time used when building market data
* @return a bundle of market data including the supplied data and the data built by the factory
*/
public MarketDataEnvironment build(MarketDataEnvironment suppliedData,
Set<MarketDataRequirement> requirements,
SingleScenarioDefinition scenario,
MarketDataSpecification marketDataSpec,
ZonedDateTime valuationTime) {
// create a data source to provide low-level data from market data providers
@SuppressWarnings("unchecked")
MarketDataSource marketDataSource = _marketDataFactory.create(marketDataSpec);
// figure out which perturbations should be applied to the market data for this calculation cycle
CyclePerturbations cyclePerturbations = new CyclePerturbations(requirements, scenario);
// build the data
MarketDataEnvironment builtData =
buildEnvironment(suppliedData, requirements, cyclePerturbations, valuationTime, marketDataSource);
// filter the single values to only include the ones in the requirements, not the intermediate values that
// were only used to build other values
Map<SingleValueRequirement, Object> singleValues = requestedSingleValues(requirements, builtData);
// filter the time series to only include the ones in the requirements, not the intermediate values that
// were only used to build other values
Map<MarketDataId<?>, DateTimeSeries<LocalDate, ?>> timeSeries = requestedTimeSeries(requirements, builtData);
MarketDataEnvironment filteredData = new MarketDataEnvironmentBuilder()
.addSingleValues(singleValues)
.addTimeSeries(timeSeries)
.valuationTime(valuationTime)
.build();
MarketDataEnvironment mergedData = MarketDataEnvironmentBuilder.merge(suppliedData, filteredData);
return cyclePerturbations.apply(mergedData);
}
/**
* Filters the time series in the built data to only include the time series in the original requirements.
* <p>
* The built data also includes any time series that was used to build other values but wasn't explicitly requested.
*
* @param requirements requirements for the market data needed to perform the calculations
* @param builtData all the market data that was built, including the values needed for the calculations and
* also the intermediate values that were used to build other market data
* @return the time series that were requested in the original requirements
*/
private Map<MarketDataId<?>, DateTimeSeries<LocalDate, ?>> requestedTimeSeries(Set<MarketDataRequirement> requirements,
MarketDataEnvironment builtData) {
// the time series that have been built
Map<MarketDataId<?>, DateTimeSeries<LocalDate, ?>> timeSeries = new HashMap<>(builtData.getTimeSeries());
// time series IDs in the original requirements
Set<MarketDataId<?>> requiredTimeSeriesIds = timeSeriesMarketDataIds(requirements);
// only want to return the data in the original requirements, not the intermediate data that was only used
// for building the requirements and their dependencies
timeSeries.keySet().retainAll(requiredTimeSeriesIds);
// the difference between the original time series requirements and the ones that have been built
Set<MarketDataId<?>> missingTimeSeries = Sets.difference(requiredTimeSeriesIds, timeSeries.keySet());
if (!missingTimeSeries.isEmpty()) {
s_logger.warn("Unable to satisfy requirements for time series {}", missingTimeSeries);
}
return timeSeries;
}
/**
* Filters the single values in the built data to only include the data in the original requirements.
* <p>
* The built data also includes any value that was used to build other values but wasn't explicitly requested.
*
* @param requirements requirements for the market data needed to perform the calculations
* @param builtData all the market data that was built, including the values needed for the calculations and
* also the intermediate values that were used to build other market data
* @return the single values that were requested in the original requirements
*/
private Map<SingleValueRequirement, Object> requestedSingleValues(Set<MarketDataRequirement> requirements,
MarketDataEnvironment builtData) {
// the single market data values that were built
Map<SingleValueRequirement, Object> singleValues = new HashMap<>(builtData.getData());
// single value requirements in the original requirements
Set<SingleValueRequirement> singleValueRequirements = singleValueRequirements(requirements);
// only want to return the data in the original requirements, not the intermediate data that was only used
// for building the requirements and their dependencies
singleValues.keySet().retainAll(singleValueRequirements);
// the difference between the original requirements and the ones that have been built
Set<SingleValueRequirement> missingRequirements = Sets.difference(singleValueRequirements, singleValues.keySet());
if (!missingRequirements.isEmpty()) {
s_logger.warn("Unable to satisfy requirements for market data {}", missingRequirements);
}
return singleValues;
}
/**
* Builds a market data environment to satisfy the requirements.
*
* @param suppliedData the market data that was supplied by the user. The engine won't attempt to build market
* data if it was supplied
* @param requirements requirements for the market data that the factory should build
* @param valuationTime the valuation time used for building the market data
* @param marketDataSource source of raw market data
* @return a market data environment containing the data specified by the requirements
*/
private MarketDataEnvironment buildEnvironment(MarketDataEnvironment suppliedData,
Set<MarketDataRequirement> requirements,
CyclePerturbations cyclePerturbations,
ZonedDateTime valuationTime,
MarketDataSource marketDataSource) {
// the data built so far, including data that isn't in the requirements but is needed to build the required data
MarketDataEnvironment builtData = new MarketDataEnvironmentBuilder().valuationTime(valuationTime).build();
// build a tree representing the market data and the data required to build it
MarketDataNode root = buildDependencyRoot(requirements, valuationTime, suppliedData);
// this is where the scenario framework needs to decide which perturbations to apply
// it has access to the whole tree of market data so it can tell when data has already been perturbed
// does the decision have to be made here? or can it be deferred for shocks that are applied after the data
// has been built?
// interesting question - if input shocks are considered separately from output shocks, they've implicitly got
// a higher priority. is that behaviour we want? if not, we have to decide up from which output shocks to apply
// without having access to the output data. is that a problem?
// for this to be a problem we would need an output shock predicated on the data that is higher priority than
// an input shock that matched the same piece of data. there's no way to know whether the output shock will
// apply until the data is built, and then it's too late to apply the input shock. unless we go back and
// build the data again with the input shock applied. that's nasty
// market data is built in multiple passes over the dependency tree
// in each iteration the leaves are removed from the tree and market data is built to satisfy the leaf dependencies
// the built market data is accumulated and passed to the builders each iteration
// the process ends when only the root node remains
while (!root.isLeaf()) {
Set<MarketDataRequirement> leafRequirements = removeLeaves(root);
List<PartitionedRequirements> partitionedRequirements = PartitionedRequirements.partition(leafRequirements);
builtData = buildMarketData(suppliedData,
builtData,
partitionedRequirements,
marketDataSource,
valuationTime,
cyclePerturbations);
}
return builtData;
}
/**
* Recursively removes the leaf nodes from a node and all nodes below it in the tree. This method mutates the node
* and its children.
*
* @param node a node
* @return the leaf requirements removed from the requirements tree
*/
static Set<MarketDataRequirement> removeLeaves(MarketDataNode node) {
List<MarketDataNode> children = node.getChildren();
ImmutableSet.Builder<MarketDataRequirement> builder = ImmutableSet.builder();
for (Iterator<MarketDataNode> it = children.iterator(); it.hasNext(); ) {
MarketDataNode childNode = it.next();
if (childNode.isLeaf()) {
it.remove();
builder.add(childNode.getRequirement());
} else {
builder.addAll(removeLeaves(childNode));
}
}
return builder.build();
}
/**
* Builds the market data in the {@code partitionedRequirements}
*
* @param suppliedData existing market data
* @param builtData the data built so far. Contains all available data required to satisfy the requirements
* @param partitionedRequirements market data requirements, partitioned by the type of their {@link MarketDataId}
* @param marketDataSource provider of low-level market data used for building the market data
* @param valuationTime the valuation time used when building market data
* @param cyclePerturbations the perturbations that should be applied to the market data for this calculation cycle
* @return the market data for the requirements, including all the data in {@code builtData}
*/
private MarketDataEnvironment buildMarketData(MarketDataEnvironment suppliedData,
MarketDataEnvironment builtData,
List<PartitionedRequirements> partitionedRequirements,
MarketDataSource marketDataSource,
ZonedDateTime valuationTime,
CyclePerturbations cyclePerturbations) {
MarketDataEnvironmentBuilder environmentBuilder = builtData.toBuilder();
// submit the requirements to the appropriate builder in bulk
for (PartitionedRequirements partitionedRequirement : partitionedRequirements) {
Class<? extends MarketDataId> idType = partitionedRequirement._idType;
Set<SingleValueRequirement> singleValueReqsForKey = partitionedRequirement._singleValueRequirements;
Set<TimeSeriesRequirement> timeSeriesReqsForKey = partitionedRequirement._timeSeriesRequirements;
// this will never be null, requirements are only in the tree if they have a builder
MarketDataBuilder dataBuilder = _builders.get(idType);
CompositeMarketDataBundle marketDataBundle =
new CompositeMarketDataBundle(suppliedData.toBundle(), builtData.toBundle());
// build the data
Map<SingleValueRequirement, Result<?>> singleValueData =
dataBuilder.buildSingleValues(marketDataBundle,
valuationTime,
singleValueReqsForKey,
marketDataSource,
cyclePerturbations);
// it would be an optimisation to pass in existing time series in case there are requirements for
// overlapping series for the same ID. as things stand the overlapping values would be built twice.
// but if the builder can see the existing series it can avoid rebuilding duplicate data.
// the merging logic below will ensure the correct data is created anyway, at the cost of some efficiency
Map<TimeSeriesRequirement, Result<? extends DateTimeSeries<LocalDate, ?>>> timeSeriesData =
dataBuilder.buildTimeSeries(marketDataBundle, timeSeriesReqsForKey, marketDataSource, cyclePerturbations);
// the single values that were successfully built
Map<SingleValueRequirement, Object> singleValues = successfulSingleValues(singleValueData);
// the time series that were successfully built
// TODO this merges time series built at the same time but not in different passes
// need to include time series from the the built data too
Map<MarketDataId<?>, ? extends DateTimeSeries<LocalDate, ?>> timeSeries = successfulTimeSeries(timeSeriesData);
// environment contains the passed in data, plus all the data built so far
environmentBuilder.addSingleValues(singleValues).addTimeSeries(timeSeries).valuationTime(valuationTime).build();
}
return environmentBuilder.build();
}
/**
* Collects and returns the time series that were successfully built and logs warnings for any failures.
*
* @param timeSeriesData the results of building the time series
* @return the time series that were successfully built
*/
private static Map<MarketDataId<?>, ? extends DateTimeSeries<LocalDate, ?>> successfulTimeSeries(
Map<TimeSeriesRequirement, Result<? extends DateTimeSeries<LocalDate, ?>>> timeSeriesData) {
Map<MarketDataId<?>, DateTimeSeries<LocalDate, ?>> timeSeries = new HashMap<>();
for (Map.Entry<TimeSeriesRequirement, Result<? extends DateTimeSeries<LocalDate, ?>>> entry :
timeSeriesData.entrySet()) {
Result<? extends DateTimeSeries<LocalDate, ?>> result = entry.getValue();
if (result.isSuccess()) {
MarketDataId id = entry.getKey().getMarketDataId();
timeSeries.put(id, mergeTimeSeries(id, timeSeries, result.getValue()));
} else {
s_logger.warn("Failed to build time series {}, {}", entry.getKey(), result);
}
}
return timeSeries;
}
/**
* Collects and returns the market data values that were successfully built and logs warnings for any failures.
*
* @param singleValueData the results of building the single market data values
* @return the market data values that were successfully built
*/
private static Map<SingleValueRequirement, Object> successfulSingleValues(
Map<SingleValueRequirement, Result<?>> singleValueData) {
Map<SingleValueRequirement, Object> singleValues = new HashMap<>();
for (Map.Entry<SingleValueRequirement, Result<?>> entry : singleValueData.entrySet()) {
Result<?> result = entry.getValue();
if (result.isSuccess()) {
singleValues.put(entry.getKey(), result.getValue());
} else {
s_logger.warn("Failed to build market data {}, {}", entry.getKey(), result);
}
}
return singleValues;
}
/**
* Checks if a time series already exists for an ID and if so merges two time series of data for that ID.
*
* @param marketDataId ID of the data in the time series
* @param builtData map of time series that have already been built
* @param timeSeries newly built time series
* @return a time series containing all the data for the ID
* @throws IllegalArgumentException if the time series have different types. This should never happen unless
* there's a bug in the {@code MarketDataBuilder} that created them
*/
@SuppressWarnings("unchecked") // LocalDateObjectTimeSeriesBuilder.putAll has a baffling signature that needs this
private static DateTimeSeries<LocalDate, ?> mergeTimeSeries(
MarketDataId<?> marketDataId,
Map<MarketDataId<?>, DateTimeSeries<LocalDate, ?>> builtData,
DateTimeSeries<LocalDate, ?> timeSeries) {
DateTimeSeries<LocalDate, ?> existingTimeSeries = builtData.get(marketDataId);
if (existingTimeSeries == null) {
return timeSeries;
}
// this should never happen unless the builder has returned two different types of time series for the same
// ID which would definitely be a bug
if (existingTimeSeries.getClass() != timeSeries.getClass()) {
throw new IllegalArgumentException("Time series must be of the same type. ID: " + marketDataId +
", type1: " + existingTimeSeries.getClass().getName() +
", type2: " + timeSeries.getClass().getName());
}
// the time series each have data not present in the other. Merge into a single time series
// only LocalDate time series are supported but there isn't a common supertype for the LocalDate time series impls
if (existingTimeSeries instanceof LocalDateDoubleTimeSeries) {
return ImmutableLocalDateDoubleTimeSeries.builder()
.putAll((LocalDateDoubleTimeSeries) existingTimeSeries)
.putAll((LocalDateDoubleTimeSeries) timeSeries)
.build();
} else {
return ImmutableLocalDateObjectTimeSeries.builder()
.putAll((LocalDateObjectTimeSeries) existingTimeSeries)
.putAll((LocalDateObjectTimeSeries) timeSeries)
.build();
}
}
/**
* Extracts the time series market data IDs from a set of requirements
*
* @param requirements the requirements
* @return the market data IDs of any {@link TimeSeriesRequirement} instances in the requirements
*/
private Set<MarketDataId<?>> timeSeriesMarketDataIds(Set<MarketDataRequirement> requirements) {
Set<MarketDataId<?>> ids = new HashSet<>();
for (MarketDataRequirement requirement : requirements) {
if (requirement instanceof TimeSeriesRequirement) {
ids.add(requirement.getMarketDataId());
}
}
return ids;
}
/**
* Extracts the single value requirements from a set of requirements.
*
* @param requirements some market data requirements
* @return the {@code SingleValueRequirement} instances from the input set
*/
private static Set<SingleValueRequirement> singleValueRequirements(Set<MarketDataRequirement> requirements) {
Set<SingleValueRequirement> singleValueRequirements = new HashSet<>();
for (MarketDataRequirement requirement : requirements) {
if (requirement instanceof SingleValueRequirement) {
singleValueRequirements.add((SingleValueRequirement) requirement);
}
}
return singleValueRequirements;
}
/**
* Builds a tree representing the dependencies between items of market data.
* <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>
* For example, if a function requests a curve, there will be a node below the root representing that curve.
* The curve's node will have 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.
* <p>
* The leaf nodes in the tree represent market data that can be sourced from a market data provider or
* is provided by the user when running the view.
* <p>
* The building process starts the the leaf nodes. They are removed from the tree, and the requirements are
* passed to the appropriate builders to build the market data values. After removing the leaf nodes, the tree
* contains a new set of leaf nodes. The process is repeated, and the leaf requirements are passed to the
* builders, along with their dependent market data values built in the previous step. This process continues
* until there are no nodes remaining below the root.
*
* @param requirements requirements representing the market data that must be provided
* @param valuationTime valuation time used when building market data
* @param suppliedData data supplied by the user
* @return the root node of the market data dependency tree
*/
MarketDataNode buildDependencyRoot(Set<MarketDataRequirement> requirements,
ZonedDateTime valuationTime,
MarketDataEnvironment suppliedData) {
MarketDataNode root = new MarketDataNode();
for (MarketDataRequirement requirement : requirements) {
// these are always needed, if they were supplied they shouldn't even be in the set of requirements
MarketDataNode childNode = buildDependencyNode(requirement, valuationTime, suppliedData);
root.addChild(childNode);
}
return root;
}
private MarketDataNode buildDependencyNode(MarketDataRequirement requirement,
ZonedDateTime valuationTime,
MarketDataEnvironment suppliedData) {
Class<? extends MarketDataId> idType = requirement.getMarketDataId().getClass();
MarketDataBuilder marketDataBuilder = _builders.get(idType);
if (marketDataBuilder != null) {
MarketDataNode node = new MarketDataNode(requirement);
Set<MarketDataRequirement> childReqs = requirement.getRequirements(marketDataBuilder, valuationTime, suppliedData);
for (MarketDataRequirement childReq : childReqs) {
if (!containsData(suppliedData, childReq)) {
MarketDataNode childNode = buildDependencyNode(childReq, valuationTime, suppliedData);
node.addChild(childNode);
}
}
return node;
} else {
throw new IllegalArgumentException("No MarketDataBuilder registered to handle " +
"MarketDataIds of type " + idType.getName());
}
}
/**
* Returns true if the environment contains data to satisfy the requirement.
*/
private static boolean containsData(MarketDataEnvironment marketData, MarketDataRequirement req) {
if (req instanceof SingleValueRequirement) {
return marketData.getData().containsKey(req);
}
MarketDataId id = req.getMarketDataId();
DateTimeSeries<LocalDate, ?> timeSeries = marketData.getTimeSeries().get(id);
if (timeSeries == null) {
return false;
}
LocalDateRange requiredDateRange = req.getMarketDataTime().getDateRange();
// if the time series date range completely contains the requirement range then the environment contains
// all the required data
LocalDate reqStart = requiredDateRange.getStartDateInclusive();
LocalDate reqEnd = requiredDateRange.getEndDateInclusive();
LocalDate timeSeriesStart = timeSeries.getEarliestTime();
LocalDate timeSeriesEnd = timeSeries.getLatestTime();
return timeSeriesStart.compareTo(reqStart) <= 0 && timeSeriesEnd.compareTo(reqEnd) >= 0;
}
/**
* Sets of requirements for a single type of {@link MarketDataId}.
*/
private static class PartitionedRequirements {
private final Class<? extends MarketDataId> _idType;
private final Set<SingleValueRequirement> _singleValueRequirements;
private final Set<TimeSeriesRequirement> _timeSeriesRequirements;
private PartitionedRequirements(Class<? extends MarketDataId> idType,
Set<SingleValueRequirement> singleValueRequirements,
Set<TimeSeriesRequirement> timeSeriesRequirements) {
_idType = idType;
_singleValueRequirements = singleValueRequirements;
_timeSeriesRequirements = timeSeriesRequirements;
}
/**
* Partitions a set of requirements by the type of their {@link MarketDataId}.
* <p>
* The type of the market data ID is used to find the correct {@link MarketDataBuilder}. Sorting the requirements
* by the type of their ID allows them to be submitted to the builders in bulk.
*
* @param requirements the requirements
* @return a multimap of requirements, keyed by the class of their market data ID
*/
private static List<PartitionedRequirements> partition(Set<MarketDataRequirement> requirements) {
// the key type is used to find the correct market data builder which can satisfy the requirements
SetMultimap<Class<? extends MarketDataId>, SingleValueRequirement> singleValueRequirements = HashMultimap.create();
SetMultimap<Class<? extends MarketDataId>, TimeSeriesRequirement> timeSeriesRequirements = HashMultimap.create();
for (MarketDataRequirement requirement : requirements) {
MarketDataId marketDataId = requirement.getMarketDataId();
Class<? extends MarketDataId> keyClass = marketDataId.getClass();
if (requirement instanceof SingleValueRequirement) {
singleValueRequirements.put(keyClass, (SingleValueRequirement) requirement);
} else {
timeSeriesRequirements.put(keyClass, (TimeSeriesRequirement) requirement);
}
}
List<PartitionedRequirements> partitionedRequirements = new ArrayList<>();
Set<Class<? extends MarketDataId>> idTypes = Sets.union(singleValueRequirements.keySet(),
timeSeriesRequirements.keySet());
for (Class<? extends MarketDataId> idType : idTypes) {
Set<SingleValueRequirement> singleValueReqsForType = singleValueRequirements.get(idType);
Set<TimeSeriesRequirement> timeSeriesReqsForType = timeSeriesRequirements.get(idType);
partitionedRequirements.add(new PartitionedRequirements(idType, singleValueReqsForType, timeSeriesReqsForType));
}
return partitionedRequirements;
}
}
/**
* Mutable node in a tree of dependencies between items of market data.
*/
static class MarketDataNode {
private final MarketDataRequirement _requirement;
private final List<MarketDataNode> _children = new ArrayList<>();
// for building the root node - maybe create factory methods and hide the constructors
MarketDataNode() {
_requirement = null;
}
MarketDataNode(MarketDataRequirement requirement) {
_requirement = ArgumentChecker.notNull(requirement, "requirement");
}
List<MarketDataNode> getChildren() {
return _children;
}
MarketDataRequirement getRequirement() {
return _requirement;
}
boolean isLeaf() {
return _children.isEmpty();
}
void addChild(MarketDataNode childNode) {
_children.add(childNode);
}
@Override
public int hashCode() {
// deriving the hash code from the fields is dangerous because they're mutable.
// strictly speaking this is correct, if a bit unusual
return 42;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
final MarketDataNode other = (MarketDataNode) obj;
return Objects.equals(this._requirement, other._requirement) && Objects.equals(this._children, other._children);
}
@Override
public String toString() {
return "MarketDataNode [_requirement=" + _requirement + ", _children=" + _children + "]";
}
}
}