/**
* 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.toImmutableSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.joda.beans.Bean;
import org.joda.beans.Property;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multiset;
import com.google.common.primitives.Ints;
import com.opengamma.strata.basics.currency.Currency;
import com.opengamma.strata.calc.runner.CalculationFunctions;
import com.opengamma.strata.product.common.PayReceive;
import com.opengamma.strata.product.swap.SwapLeg;
import com.opengamma.strata.product.swap.SwapLegType;
/**
* Evaluates a token against an iterable object and returns a value.
* <p>
* The token can be the index of the item in the iterable (zero based). For example, this expression selects
* the start date of the first leg of a swap:
* <pre>
* Product.legs.0.startDate
* </pre>
* It is also possible to select items based on the value of their properties. For example, {@link SwapLeg} has
* a property {@code payReceive} whose value can be {@code PAY} or {@code RECEIVE}. It is possible to select
* a leg based on the value of this property:
* <pre>
* Product.legs.pay.startDate // Pay leg start date
* Product.legs.receive.startDate // Receive leg start date
* </pre>
* The comparison between property values and expression values is case-insensitive.
* <p>
* This works for any property where each item has a unique value. For example, consider a cross-currency swap where
* one leg has the currency USD and the other has the currency GBP:
* <pre>
* Product.legs.USD.startDate // USD leg start date
* Product.legs.GBP.startDate // GBP leg start date
* </pre>
* If both legs have the same currency it would obviously not be possible to use the currency to select a leg.
*/
public class IterableTokenEvaluator extends TokenEvaluator<Iterable<?>> {
private static final Set<Class<?>> SUPPORTED_FIELD_TYPES = ImmutableSet.of(
Currency.class,
SwapLegType.class,
PayReceive.class);
@Override
public Class<?> getTargetType() {
return Iterable.class;
}
@Override
public Set<String> tokens(Iterable<?> iterable) {
Multiset<String> tokens = HashMultiset.create();
int index = 0;
for (Object item : iterable) {
tokens.add(String.valueOf(index++));
tokens.addAll(fieldValues(item));
}
return tokens.stream()
.filter(token -> tokens.count(token) == 1)
.collect(toImmutableSet());
}
@Override
public EvaluationResult evaluate(
Iterable<?> iterable,
CalculationFunctions functions,
String firstToken,
List<String> remainingTokens) {
String token = firstToken.toLowerCase(Locale.ENGLISH);
Integer index = Ints.tryParse(token);
if (index != null) {
try {
return EvaluationResult.success(Iterables.get(iterable, index), remainingTokens);
} catch (IndexOutOfBoundsException e) {
return invalidTokenFailure(iterable, token);
}
}
Set<String> tokens = tokens(iterable);
for (Object item : iterable) {
if (!fieldValues(item).contains(token)) {
continue;
}
if (!tokens.contains(token)) {
return ambiguousTokenFailure(iterable, token);
}
return EvaluationResult.success(item, remainingTokens);
}
return invalidTokenFailure(iterable, token);
}
//-------------------------------------------------------------------------
private Set<String> fieldValues(Object object) {
if (!(object instanceof Bean)) {
return ImmutableSet.of();
}
Bean bean = (Bean) object;
return bean.propertyNames().stream()
.map(bean::property)
.filter(p -> SUPPORTED_FIELD_TYPES.contains(p.metaProperty().propertyType()))
.map(Property::get)
.filter(v -> v != null)
.map(Object::toString)
.map(v -> v.toLowerCase(Locale.ENGLISH))
.collect(toImmutableSet());
}
}