/** * 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 java.util.List; import java.util.Optional; import java.util.Set; import org.joda.beans.Bean; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.opengamma.strata.calc.runner.CalculationFunctions; import com.opengamma.strata.market.amount.LegAmount; import com.opengamma.strata.market.amount.LegAmounts; /** * Evaluates a token against a bean to produce another object. * <p> * The token must be the name of one of the properties of the bean and the result is the value of the property. * <p> * There is special handling of beans with a single property. The name of the property can be omitted from * the expression if the bean only has one property. * <p> * For example, the bean {@link LegAmounts} has a single property named {@code amounts} containing a list of * {@link LegAmount} instances. The following expressions are equivalent and both return the first amount in the * list. {@code LegInitialNotional} is a measure that produces {@code LegAmounts}. * <pre> * Measures.LegInitialNotional.0 * Measures.LegInitialNotional.amounts.0 * </pre> * <p> * If the token matches the property then the default behaviour applies; the property value is returned and * the remaining tokens do not include the property token. If the token doesn't match the property, the property value * is returned but the token isn't consumed. i.e. the remaining tokens returned from {@link #evaluate} include * the first token. */ public class BeanTokenEvaluator extends TokenEvaluator<Bean> { @Override public Class<Bean> getTargetType() { return Bean.class; } @Override public Set<String> tokens(Bean bean) { if (bean.propertyNames().size() == 1) { String singlePropertyName = Iterables.getOnlyElement(bean.propertyNames()); Object propertyValue = bean.property(singlePropertyName).get(); Set<String> valueTokens = ValuePathEvaluator.tokens(propertyValue); return ImmutableSet.<String>builder() .add(singlePropertyName) .addAll(valueTokens) .build(); } else { return bean.propertyNames(); } } @Override public EvaluationResult evaluate( Bean bean, CalculationFunctions functions, String firstToken, List<String> remainingTokens) { Optional<String> propertyName = bean.propertyNames().stream() .filter(p -> p.equalsIgnoreCase(firstToken)) .findFirst(); if (propertyName.isPresent()) { Object propertyValue = bean.property(propertyName.get()).get(); return propertyValue != null ? EvaluationResult.success(propertyValue, remainingTokens) : EvaluationResult.failure("No value available for property '{}'", firstToken); } // The bean has a single property which doesn't match the token. // Return the property value without consuming any tokens. // This allows skipping over properties when the bean only has a single property. if (bean.propertyNames().size() == 1) { String singlePropertyName = Iterables.getOnlyElement(bean.propertyNames()); Object propertyValue = bean.property(singlePropertyName).get(); List<String> tokens = ImmutableList.<String>builder().add(firstToken).addAll(remainingTokens).build(); return propertyValue != null ? EvaluationResult.success(propertyValue, tokens) : EvaluationResult.failure("No value available for property '{}'", firstToken); } return invalidTokenFailure(bean, firstToken); } }