/**
  * Recursively parses and breaks down the expression string to build a string ValueSource.
  *
  * @param schema The schema to pull fields from.
  * @param expressionString The expression string to build a ValueSource from.
  * @return The value source represented by the given expressionString
  */
 private static ValueSource buildStringSource(IndexSchema schema, String expressionString) {
   int paren = expressionString.indexOf('(');
   String[] arguments;
   if (paren < 0) {
     return buildFieldSource(schema, expressionString, FIELD_TYPE);
   } else {
     arguments =
         ExpressionFactory.getArguments(
             expressionString.substring(paren + 1, expressionString.lastIndexOf(')')).trim());
   }
   String operands = arguments[0];
   String operation = expressionString.substring(0, paren).trim();
   if (operation.equals(AnalyticsParams.CONSTANT_STRING)) {
     operands = expressionString.substring(paren + 1, expressionString.lastIndexOf(')'));
     return new ConstStringSource(operands);
   } else if (operation.equals(AnalyticsParams.FILTER)) {
     return buildFilterSource(schema, operands, FIELD_TYPE);
   } else if (operation.equals(AnalyticsParams.REVERSE)) {
     if (arguments.length != 1) {
       throw new SolrException(
           ErrorCode.BAD_REQUEST,
           "\""
               + AnalyticsParams.REVERSE
               + "\" requires exactly one argument. The number of arguments in "
               + expressionString
               + " is not 1.");
     }
     return new ReverseStringFunction(buildStringSource(schema, operands));
   }
   List<ValueSource> subExpressions = new ArrayList<>();
   for (String argument : arguments) {
     subExpressions.add(buildSourceTree(schema, argument));
   }
   if (operation.equals(AnalyticsParams.CONCATENATE)) {
     return new ConcatStringFunction(subExpressions.toArray(new ValueSource[0]));
   }
   if (AnalyticsParams.NUMERIC_OPERATION_SET.contains(operation)) {
     return buildNumericSource(schema, expressionString);
   } else if (AnalyticsParams.DATE_OPERATION_SET.contains(operation)) {
     return buildDateSource(schema, expressionString);
   }
   throw new SolrException(
       ErrorCode.BAD_REQUEST, "The operation [" + expressionString + "] is not supported.");
 }
  /**
   * Determines what type of value source the expression represents.
   *
   * @param expression The expression representing the desired ValueSource
   * @return NUMBER_TYPE, DATE_TYPE, STRING_TYPE or -1
   */
  private static int getSourceType(String expression) {
    int paren = expression.indexOf('(');
    if (paren < 0) {
      return FIELD_TYPE;
    }
    String operation = expression.substring(0, paren).trim();

    if (AnalyticsParams.NUMERIC_OPERATION_SET.contains(operation)) {
      return NUMBER_TYPE;
    } else if (AnalyticsParams.DATE_OPERATION_SET.contains(operation)) {
      return DATE_TYPE;
    } else if (AnalyticsParams.STRING_OPERATION_SET.contains(operation)) {
      return STRING_TYPE;
    } else if (operation.equals(AnalyticsParams.FILTER)) {
      return FILTER_TYPE;
    }
    throw new SolrException(
        ErrorCode.BAD_REQUEST,
        "The operation \"" + operation + "\" in [" + expression + "] is not supported.");
  }
 /**
  * Recursively parses and breaks down the expression string to build a date ValueSource.
  *
  * @param schema The schema to pull fields from.
  * @param expressionString The expression string to build a ValueSource from.
  * @return The value source represented by the given expressionString
  */
 @SuppressWarnings("deprecation")
 private static ValueSource buildDateSource(IndexSchema schema, String expressionString) {
   int paren = expressionString.indexOf('(');
   String[] arguments;
   if (paren < 0) {
     return buildFieldSource(schema, expressionString, DATE_TYPE);
   } else {
     arguments =
         ExpressionFactory.getArguments(
             expressionString.substring(paren + 1, expressionString.lastIndexOf(')')).trim());
   }
   String operands = arguments[0];
   String operation = expressionString.substring(0, paren).trim();
   if (operation.equals(AnalyticsParams.CONSTANT_DATE)) {
     if (arguments.length != 1) {
       throw new SolrException(
           ErrorCode.BAD_REQUEST,
           "The constant date declaration ["
               + expressionString
               + "] does not have exactly 1 argument.");
     }
     try {
       return new ConstDateSource(TrieDateField.parseDate(operands));
     } catch (ParseException e) {
       throw new SolrException(
           ErrorCode.BAD_REQUEST,
           "The constant " + operands + " cannot be converted into a date.",
           e);
     }
   } else if (operation.equals(AnalyticsParams.FILTER)) {
     return buildFilterSource(schema, operands, DATE_TYPE);
   }
   if (operation.equals(AnalyticsParams.DATE_MATH)) {
     List<ValueSource> subExpressions = new ArrayList<>();
     boolean first = true;
     for (String argument : arguments) {
       ValueSource argSource;
       if (first) {
         first = false;
         argSource = buildDateSource(schema, argument);
         if (argSource == null) {
           throw new SolrException(
               ErrorCode.BAD_REQUEST,
               "\""
                   + AnalyticsParams.DATE_MATH
                   + "\" requires the first argument be a date operation or field. ["
                   + argument
                   + "] is not a date operation or field.");
         }
       } else {
         argSource = buildStringSource(schema, argument);
         if (argSource == null) {
           throw new SolrException(
               ErrorCode.BAD_REQUEST,
               "\""
                   + AnalyticsParams.DATE_MATH
                   + "\" requires that all arguments except the first be string operations. ["
                   + argument
                   + "] is not a string operation.");
         }
       }
       subExpressions.add(argSource);
     }
     return new DateMathFunction(subExpressions.toArray(new ValueSource[0]));
   }
   if (AnalyticsParams.NUMERIC_OPERATION_SET.contains(operation)
       || AnalyticsParams.STRING_OPERATION_SET.contains(operation)) {
     return null;
   }
   throw new SolrException(
       ErrorCode.BAD_REQUEST, "The operation [" + expressionString + "] is not supported.");
 }