protected Query range(
      Path<?> leftHandSide,
      String field,
      @Nullable Expression<?> min,
      @Nullable Expression<?> max,
      boolean minInc,
      boolean maxInc,
      QueryMetadata metadata) {
    if (min != null && Number.class.isAssignableFrom(min.getType())
        || max != null && Number.class.isAssignableFrom(max.getType())) {
      @SuppressWarnings("unchecked") // guarded by previous check
      Constant<? extends Number> minConstant = (Constant<? extends Number>) min;
      @SuppressWarnings("unchecked") // guarded by previous check
      Constant<? extends Number> maxConstant = (Constant<? extends Number>) max;

      Class<? extends Number> numType =
          minConstant != null ? minConstant.getType() : maxConstant.getType();
      // this is not necessarily safe, but compile time checking
      // on the user side mandates these types to be interchangeable
      @SuppressWarnings("unchecked")
      Class<Number> unboundedNumType = (Class<Number>) numType;
      return numericRange(
          unboundedNumType,
          field,
          minConstant == null ? null : minConstant.getConstant(),
          maxConstant == null ? null : maxConstant.getConstant(),
          minInc,
          maxInc);
    }
    return stringRange(leftHandSide, field, min, max, minInc, maxInc, metadata);
  }
  /**
   * template method
   *
   * @param leftHandSide left hand side
   * @param rightHandSide right hand side
   * @return results
   */
  protected String[] convert(
      Path<?> leftHandSide, Expression<?> rightHandSide, QueryMetadata metadata) {
    if (rightHandSide instanceof Operation) {
      Operation<?> operation = (Operation<?>) rightHandSide;
      if (operation.getOperator() == LuceneOps.PHRASE) {
        return Iterables.toArray(WS_SPLITTER.split(operation.getArg(0).toString()), String.class);
      } else if (operation.getOperator() == LuceneOps.TERM) {
        return new String[] {operation.getArg(0).toString()};
      } else {
        throw new IllegalArgumentException(rightHandSide.toString());
      }
    } else if (rightHandSide instanceof ParamExpression<?>) {
      Object value = metadata.getParams().get(rightHandSide);
      if (value == null) {
        throw new ParamNotSetException((ParamExpression<?>) rightHandSide);
      }
      return convert(leftHandSide, value);

    } else if (rightHandSide instanceof Constant<?>) {
      return convert(leftHandSide, ((Constant<?>) rightHandSide).getConstant());
    } else {
      throw new IllegalArgumentException(rightHandSide.toString());
    }
  }