/**
* 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);
}
}