/**
   * 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.");
 }
 /**
  * Recursively parses and breaks down the expression string to build a numeric 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 buildNumericSource(IndexSchema schema, String expressionString) {
   int paren = expressionString.indexOf('(');
   String[] arguments;
   String operands;
   if (paren < 0) {
     return buildFieldSource(schema, expressionString, NUMBER_TYPE);
   } else {
     try {
       operands = expressionString.substring(paren + 1, expressionString.lastIndexOf(')')).trim();
     } catch (Exception e) {
       throw new SolrException(
           ErrorCode.BAD_REQUEST, "Missing closing parenthesis in [" + expressionString + "]");
     }
     arguments = ExpressionFactory.getArguments(operands);
   }
   String operation = expressionString.substring(0, paren).trim();
   if (operation.equals(AnalyticsParams.CONSTANT_NUMBER)) {
     if (arguments.length != 1) {
       throw new SolrException(
           ErrorCode.BAD_REQUEST,
           "The constant number declaration ["
               + expressionString
               + "] does not have exactly 1 argument.");
     }
     return new ConstDoubleSource(Double.parseDouble(arguments[0]));
   } else if (operation.equals(AnalyticsParams.NEGATE)) {
     if (arguments.length != 1) {
       throw new SolrException(
           ErrorCode.BAD_REQUEST,
           "The negate operation [" + expressionString + "] does not have exactly 1 argument.");
     }
     ValueSource argSource = buildNumericSource(schema, arguments[0]);
     if (argSource == null) {
       throw new SolrException(
           ErrorCode.BAD_REQUEST,
           "The operation \""
               + AnalyticsParams.NEGATE
               + "\" requires a numeric field or operation as argument. \""
               + arguments[0]
               + "\" is not a numeric field or operation.");
     }
     return new NegateDoubleFunction(argSource);
   } else if (operation.equals(AnalyticsParams.ABSOLUTE_VALUE)) {
     if (arguments.length != 1) {
       throw new SolrException(
           ErrorCode.BAD_REQUEST,
           "The absolute value operation ["
               + expressionString
               + "] does not have exactly 1 argument.");
     }
     ValueSource argSource = buildNumericSource(schema, arguments[0]);
     if (argSource == null) {
       throw new SolrException(
           ErrorCode.BAD_REQUEST,
           "The operation \""
               + AnalyticsParams.NEGATE
               + "\" requires a numeric field or operation as argument. \""
               + arguments[0]
               + "\" is not a numeric field or operation.");
     }
     return new AbsoluteValueDoubleFunction(argSource);
   } else if (operation.equals(AnalyticsParams.FILTER)) {
     return buildFilterSource(schema, operands, NUMBER_TYPE);
   }
   List<ValueSource> subExpressions = new ArrayList<>();
   for (String argument : arguments) {
     ValueSource argSource = buildNumericSource(schema, argument);
     if (argSource == null) {
       throw new SolrException(
           ErrorCode.BAD_REQUEST,
           "The operation \""
               + operation
               + "\" requires numeric fields or operations as arguments. \""
               + argument
               + "\" is not a numeric field or operation.");
     }
     subExpressions.add(argSource);
   }
   if (operation.equals(AnalyticsParams.ADD)) {
     return new AddDoubleFunction(subExpressions.toArray(new ValueSource[0]));
   } else if (operation.equals(AnalyticsParams.MULTIPLY)) {
     return new MultiplyDoubleFunction(subExpressions.toArray(new ValueSource[0]));
   } else if (operation.equals(AnalyticsParams.DIVIDE)) {
     if (subExpressions.size() != 2) {
       throw new SolrException(
           ErrorCode.BAD_REQUEST,
           "The divide operation [" + expressionString + "] does not have exactly 2 arguments.");
     }
     return new DivDoubleFunction(subExpressions.get(0), subExpressions.get(1));
   } else if (operation.equals(AnalyticsParams.POWER)) {
     if (subExpressions.size() != 2) {
       throw new SolrException(
           ErrorCode.BAD_REQUEST,
           "The power operation [" + expressionString + "] does not have exactly 2 arguments.");
     }
     return new PowDoubleFunction(subExpressions.get(0), subExpressions.get(1));
   } else if (operation.equals(AnalyticsParams.LOG)) {
     if (subExpressions.size() != 2) {
       throw new SolrException(
           ErrorCode.BAD_REQUEST,
           "The log operation [" + expressionString + "] does not have exactly 2 arguments.");
     }
     return new LogDoubleFunction(subExpressions.get(0), subExpressions.get(1));
   }
   if (AnalyticsParams.DATE_OPERATION_SET.contains(operation)
       || AnalyticsParams.STRING_OPERATION_SET.contains(operation)) {
     return null;
   }
   throw new SolrException(
       ErrorCode.BAD_REQUEST, "The operation [" + expressionString + "] is not supported.");
 }