/** * Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.report.framework.expression; import static com.opengamma.strata.collect.Guavate.toImmutableList; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.IntStream; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.opengamma.strata.basics.index.IborIndex; import com.opengamma.strata.calc.Measure; import com.opengamma.strata.calc.runner.CalculationFunctions; import com.opengamma.strata.collect.result.FailureReason; import com.opengamma.strata.collect.result.Result; import com.opengamma.strata.product.fra.Fra; import com.opengamma.strata.product.fra.FraTrade; import com.opengamma.strata.report.ReportCalculationResults; /** * Evaluates a path describing a value to be shown in a trade report. * <p> * For example, if the expression is '{@code Product.index.name}' and the results contain {@link FraTrade} instances * the following calls will be made for each trade in the results: * <ul> * <li>{@code FraTrade.getProduct()} returning a {@link Fra}</li> * <li>{@code Fra.getIndex()} returning an {@link IborIndex}</li> * <li>{@code IborIndex.getName()} returning the index name</li> * </ul> * The result of evaluating the expression is the index name. */ public final class ValuePathEvaluator { /** The separator used in the value path. */ private static final String PATH_SEPARATOR = "\\."; private static final ImmutableList<TokenEvaluator<?>> EVALUATORS = ImmutableList.of( new CurrencyAmountTokenEvaluator(), new MapTokenEvaluator(), new CurrencyParameterSensitivitiesTokenEvaluator(), new CurrencyParameterSensitivityTokenEvaluator(), new PositionTokenEvaluator(), new TradeTokenEvaluator(), new SecurityTokenEvaluator(), new BeanTokenEvaluator(), new IterableTokenEvaluator()); //------------------------------------------------------------------------- /** * Gets the measure encoded in a value path, if present. * * @param valuePath the value path * @return the measure, if present */ public static Optional<Measure> measure(String valuePath) { try { List<String> tokens = tokenize(valuePath); ValueRootType rootType = ValueRootType.parseToken(tokens.get(0)); if (rootType != ValueRootType.MEASURES || tokens.size() < 2) { return Optional.empty(); } Measure measure = Measure.of(tokens.get(1)); return Optional.of(measure); } catch (Exception ex) { return Optional.empty(); } } /** * Evaluates a value path against a set of results, returning the resolved result for each trade. * * @param valuePath the value path * @param results the calculation results * @return the list of resolved results for each trade */ public static List<Result<?>> evaluate(String valuePath, ReportCalculationResults results) { List<String> tokens = tokenize(valuePath); if (tokens.size() < 1) { return Collections.nCopies( results.getTargets().size(), Result.failure(FailureReason.INVALID, "Column expressions must not be empty")); } CalculationFunctions functions = results.getCalculationFunctions(); int rowCount = results.getCalculationResults().getRowCount(); return IntStream.range(0, rowCount) .mapToObj(rowIndex -> evaluate(functions, tokens, RootEvaluator.INSTANCE, new ResultsRow(results, rowIndex))) .collect(toImmutableList()); } // Tokens always has at least one token private static <T> Result<?> evaluate( CalculationFunctions functions, List<String> tokens, TokenEvaluator<T> evaluator, T target) { List<String> remaining = tokens.subList(1, tokens.size()); EvaluationResult evaluationResult = evaluator.evaluate(target, functions, tokens.get(0), remaining); if (evaluationResult.isComplete()) { return evaluationResult.getResult(); } Object value = evaluationResult.getResult().getValue(); Optional<TokenEvaluator<Object>> nextEvaluator = getEvaluator(value.getClass()); return nextEvaluator.isPresent() ? evaluate(functions, evaluationResult.getRemainingTokens(), nextEvaluator.get(), value) : noEvaluatorResult(remaining, value); } private static Result<?> noEvaluatorResult(List<String> remaining, Object value) { return Result.failure( FailureReason.INVALID, "Expression '{}' cannot be invoked on type {}", Joiner.on('.').join(remaining), value.getClass().getName()); } /** * Gets the supported tokens on the given object. * * @param object the object for which to return the valid tokens * @return the tokens */ public static Set<String> tokens(Object object) { return getEvaluator(object.getClass()).map(evaluator -> evaluator.tokens(object)).orElse(ImmutableSet.of()); } //------------------------------------------------------------------------- // splits a value path into tokens for processing private static List<String> tokenize(String valuePath) { String[] tokens = valuePath.split(PATH_SEPARATOR); return ImmutableList.copyOf(tokens); } @SuppressWarnings("unchecked") private static Optional<TokenEvaluator<Object>> getEvaluator(Class<?> targetClass) { return EVALUATORS.stream() .filter(e -> e.getTargetType().isAssignableFrom(targetClass)) .map(e -> (TokenEvaluator<Object>) e) .findFirst(); } //------------------------------------------------------------------------- private ValuePathEvaluator() { } }