class SecurityTokenEvaluator extends TokenEvaluator<Security> {

  @Override
  public EvaluationResult evaluate(
      Security security,
      CalculationFunctions functions,
      String firstToken,
      List<String> remainingTokens) {

    MetaBean metaBean = MetaBean.of(security.getClass());

    // security
    Optional<String> securityPropertyName = metaBean.metaPropertyMap().keySet().stream()
        .filter(p -> p.equalsIgnoreCase(firstToken))
        .findFirst();
    if (securityPropertyName.isPresent()) {
      Object propertyValue = metaBean.metaProperty(securityPropertyName.get()).get((Bean) security);
      return propertyValue != null ?
          EvaluationResult.success(propertyValue, remainingTokens) :
          EvaluationResult.failure("Property '{}' not set", firstToken);
    }

    // security info
    Optional<String> securityInfoPropertyName = security.getInfo().propertyNames().stream()
        .filter(p -> p.equalsIgnoreCase(firstToken))
        .findFirst();
    if (securityInfoPropertyName.isPresent()) {
      Object propertyValue = security.getInfo().property(securityInfoPropertyName.get()).get();
      return propertyValue != null ?
          EvaluationResult.success(propertyValue, remainingTokens) :
          EvaluationResult.failure("Property '{}' not set", firstToken);
    }

    // security price info
    Optional<String> securityPriceInfoPropertyName = security.getInfo().getPriceInfo().propertyNames().stream()
        .filter(p -> p.equalsIgnoreCase(firstToken))
        .findFirst();
    if (securityPriceInfoPropertyName.isPresent()) {
      Object propertyValue = security.getInfo().getPriceInfo().property(securityPriceInfoPropertyName.get()).get();
      return propertyValue != null ?
          EvaluationResult.success(propertyValue, remainingTokens) :
          EvaluationResult.failure("Property '{}' not set", firstToken);
    }

    // not found
    return invalidTokenFailure(security, firstToken);
  }

}