/** * Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.financial.view; import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.SortedMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.threeten.bp.LocalDate; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import com.opengamma.OpenGammaRuntimeException; import com.opengamma.engine.calcnode.MissingValue; import com.opengamma.engine.value.ComputedValue; import com.opengamma.engine.value.ValueRequirement; import com.opengamma.engine.value.ValueSpecification; import com.opengamma.engine.view.ViewCalculationConfiguration; import com.opengamma.engine.view.ViewComputationResultModel; import com.opengamma.engine.view.ViewDefinition; import com.opengamma.engine.view.ViewResultEntry; import com.opengamma.engine.view.compilation.CompiledViewCalculationConfiguration; import com.opengamma.engine.view.compilation.CompiledViewDefinition; import com.opengamma.timeseries.TimeSeries; 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.timeseries.date.localdate.LocalDateToIntConverter; /* package */class HistoricalViewEvaluationResultBuilder { private static final Logger s_logger = LoggerFactory.getLogger(HistoricalViewEvaluationResultBuilder.class); // TODO: If the duration of the time series is high, or there are simply a large quantity then we could modify these builders to // write directly to the time series database instead (or batch up the points in internal arrays) and the result bundle will be // the identifiers of the time series that were written to the database /** * Builder for time-series where there is at most one result for a given date. Points may be added in any order, allowing the results to be executed in parallel. */ private static class TimeSeriesBuilder { private final SortedMap<Integer, Object> _datedResultMap = new Int2ObjectRBTreeMap<Object>(); public TimeSeriesBuilder() { } public TimeSeriesBuilder addPoint(final int date, final Object value) { if (_datedResultMap.put(date, value) != null) { throw new OpenGammaRuntimeException("Received multiple results for date " + date); } return this; } @SuppressWarnings("rawtypes") public TimeSeries makeTimeSeries() { if (_datedResultMap.isEmpty() || Iterables.get(_datedResultMap.values(), 0) instanceof Number) { return makeDoubleTimeSeries(); } else { return makeObjectTimeSeries(); } } private LocalDateDoubleTimeSeries makeDoubleTimeSeries() { int[] dates = new int[_datedResultMap.size()]; double[] values = new double[_datedResultMap.size()]; int i = 0; for (Map.Entry<Integer, Object> datedResult : _datedResultMap.entrySet()) { dates[i] = datedResult.getKey(); values[i] = (Double) datedResult.getValue(); i++; } return ImmutableLocalDateDoubleTimeSeries.of(dates, values); } private LocalDateObjectTimeSeries<?> makeObjectTimeSeries() { int[] dates = new int[_datedResultMap.size()]; Object[] values = new Object[_datedResultMap.size()]; int i = 0; for (Map.Entry<Integer, Object> datedResult : _datedResultMap.entrySet()) { dates[i] = datedResult.getKey(); values[i] = datedResult.getValue(); i++; } return ImmutableLocalDateObjectTimeSeries.of(dates, values); } } private static class ConfigurationResults { private Map<ValueSpecification, Set<ValueRequirement>> _requirements; private final Map<ValueRequirement, TimeSeriesBuilder> _results = new HashMap<ValueRequirement, TimeSeriesBuilder>(); public ConfigurationResults(final Collection<ValueRequirement> requirements) { for (final ValueRequirement requirement : requirements) { _results.put(requirement, new TimeSeriesBuilder()); } } public void setRequirements(final Map<ValueSpecification, Set<ValueRequirement>> requirements) { _requirements = requirements; } public void store(final int date, final ValueSpecification specification, final Object value) { final Set<ValueRequirement> requirements = _requirements.get(specification); if (requirements != null) { for (final ValueRequirement requirement : requirements) { final TimeSeriesBuilder builder = _results.get(requirement); if (builder == null) { s_logger.warn("View produced value {} for unrequested requirement {}", specification, requirement); } else { final TimeSeriesBuilder newBuilder = builder.addPoint(date, value); if (newBuilder != builder) { _results.put(requirement, newBuilder); } } } } else { s_logger.warn("View produced unrequested value {}", specification); } } public HistoricalViewEvaluationResult makeResult() { final HistoricalViewEvaluationResult results = new HistoricalViewEvaluationResult(); for (final Map.Entry<ValueRequirement, TimeSeriesBuilder> result : _results.entrySet()) { results.addTimeSeries(result.getKey(), result.getValue().makeTimeSeries()); } return results; } } private static class MarketData { private final TimeSeriesBuilder _builder = new TimeSeriesBuilder(); private Collection<ValueSpecification> _alias; } private static class MarketDataResults extends HashMap<ValueSpecification, MarketData> { private static final long serialVersionUID = 1L; public void addMarketDataAlias(final ValueSpecification valueSpec, final Collection<ValueSpecification> aliasValueSpecs) { MarketData data = get(valueSpec); if (data == null) { data = new MarketData(); put(valueSpec, data); } if (aliasValueSpecs != null) { if (data._alias == null) { data._alias = new ArrayList<ValueSpecification>(aliasValueSpecs); } else { data._alias.addAll(aliasValueSpecs); } } } public void store(final int date, final ValueSpecification specification, final Object value) { MarketData data = get(specification); if (data != null) { data._builder.addPoint(date, value); } else { s_logger.warn("View produced unrequested market data {}", specification); } } @SuppressWarnings("rawtypes") public HistoricalViewEvaluationMarketData makeResult() { final HistoricalViewEvaluationMarketData results = new HistoricalViewEvaluationMarketData(); for (final Map.Entry<ValueSpecification, MarketData> result : entrySet()) { final TimeSeries timeSeries = result.getValue()._builder.makeTimeSeries(); for (ValueSpecification alias : result.getValue()._alias) { results.addTimeSeries(alias, timeSeries); } } return results; } } private final Map<String, ConfigurationResults> _results = new HashMap<String, ConfigurationResults>(); private final MarketDataResults _marketData; private boolean _compiled; public HistoricalViewEvaluationResultBuilder(final ViewDefinition viewDefinition, final boolean includeMarketData) { for (final ViewCalculationConfiguration calcConfig : viewDefinition.getAllCalculationConfigurations()) { final ConfigurationResults configResults = new ConfigurationResults(calcConfig.getSpecificRequirements()); _results.put(calcConfig.getName(), configResults); } if (includeMarketData) { _marketData = new MarketDataResults(); } else { _marketData = null; } } public synchronized void store(final CompiledViewDefinition viewDefinition) { if (!_compiled) { for (final CompiledViewCalculationConfiguration calcConfig : viewDefinition.getCompiledCalculationConfigurations()) { final ConfigurationResults configResults = _results.get(calcConfig.getName()); configResults.setRequirements(calcConfig.getTerminalOutputSpecifications()); if (_marketData != null) { for (Map.Entry<ValueSpecification, Collection<ValueSpecification>> marketDataAlias : calcConfig.getMarketDataAliases().entrySet()) { _marketData.addMarketDataAlias(marketDataAlias.getKey(), marketDataAlias.getValue()); } } } _compiled = true; } } public synchronized void store(LocalDate resultsDate, ViewComputationResultModel results) { assert _compiled; final int date = LocalDateToIntConverter.convertToInt(resultsDate); for (final ViewResultEntry viewResult : results.getAllResults()) { final ComputedValue computedValue = viewResult.getComputedValue(); final Object value = computedValue.getValue(); if ((value != null) && !(value instanceof MissingValue)) { final ConfigurationResults configResults = _results.get(viewResult.getCalculationConfiguration()); configResults.store(date, computedValue.getSpecification(), value); } } if (_marketData != null) { for (final ComputedValue computedValue : results.getAllMarketData()) { final Object value = computedValue.getValue(); if ((value != null) && !(value instanceof MissingValue)) { _marketData.store(date, computedValue.getSpecification(), value); } } } } public synchronized Map<String, HistoricalViewEvaluationResult> getResults() { final Map<String, HistoricalViewEvaluationResult> results = Maps.newHashMapWithExpectedSize(_results.size()); for (final Map.Entry<String, ConfigurationResults> result : _results.entrySet()) { results.put(result.getKey(), result.getValue().makeResult()); } return results; } public synchronized HistoricalViewEvaluationMarketData getMarketData() { if (_marketData == null) { return null; } return _marketData.makeResult(); } }