@Override
    public int _handleToken(ParseContext ctx) throws InvalidQueryException {
      Token[] tokens = ctx.getTokens();
      int idx = ctx.getCurrentTokensIndex();
      Token token = tokens[idx];
      RelationalOperator relationalOp = RelationalOperatorFactory.createOperator(token.getValue());

      ctx.addExpression(new RelationalExpression(relationalOp));
      ctx.setCurrentTokensIndex(++idx);

      TokenHandler propertyHandler = new PropertyOperandTokenHandler();
      propertyHandler.handleToken(ctx);

      // handle right operand if applicable to operator
      idx = ctx.getCurrentTokensIndex();
      if (ctx.getCurrentTokensIndex() < tokens.length
          && tokens[idx].getType().equals(Token.TYPE.VALUE_OPERAND)) {
        TokenHandler valueHandler = new ValueOperandTokenHandler();
        valueHandler.handleToken(ctx);
      }

      // skip closing bracket
      idx = ctx.getCurrentTokensIndex();
      if (idx >= tokens.length || tokens[idx].getType() != Token.TYPE.BRACKET_CLOSE) {
        throw new InvalidQueryException("Missing closing bracket for in expression.");
      }
      return 1;
    }
  /**
   * Create parse context from an array of tokens. The parse context contains a list of expressions
   * and other information about the expressions an parsed tokens.
   *
   * @param tokens an array of tokens which represent the query, each token contains type and value
   *     information
   * @return a parse context which contains a list of expressions
   * @throws InvalidQueryException if unable to properly parse the tokens into a parse context
   */
  private ParseContext parseExpressions(Token[] tokens) throws InvalidQueryException {
    ParseContext ctx = new ParseContext(tokens);

    while (ctx.getCurrentTokensIndex() < tokens.length) {
      TOKEN_HANDLERS.get(tokens[ctx.getCurrentTokensIndex()].getType()).handleToken(ctx);
    }

    if (ctx.getPrecedenceLevel() != 0) {
      throw new InvalidQueryException("Invalid query string: mismatched parentheses.");
    }

    return ctx;
  }
    @Override
    public int _handleToken(ParseContext ctx) throws InvalidQueryException {
      Token token = ctx.getTokens()[ctx.getCurrentTokensIndex()];
      ctx.getPrecedingExpression().setLeftOperand(token.getValue());

      return 1;
    }
    /**
     * Process a token. Handles common token processing functionality then delegates to the
     * individual concrete handlers.
     *
     * @param ctx the current parse context
     * @throws InvalidQueryException if unable to process the token
     */
    public void handleToken(ParseContext ctx) throws InvalidQueryException {
      Token token = ctx.getTokens()[ctx.getCurrentTokensIndex()];
      if (!validate(ctx.getPreviousTokenType())) {
        throw new InvalidQueryException(
            "Unexpected token encountered in query string. Last Token Type="
                + ctx.getPreviousTokenType()
                + ", Current Token[type="
                + token.getType()
                + ", value='"
                + token.getValue()
                + "']");
      }
      ctx.setTokenType(token.getType());

      int idxIncrement = _handleToken(ctx);
      ctx.setCurrentTokensIndex(ctx.getCurrentTokensIndex() + idxIncrement);
    }
    @Override
    public int _handleToken(ParseContext ctx) throws InvalidQueryException {
      Token token = ctx.getTokens()[ctx.getCurrentTokensIndex()];
      RelationalOperator relationalOp = RelationalOperatorFactory.createOperator(token.getValue());
      // todo: use factory to create expression
      ctx.addExpression(new RelationalExpression(relationalOp));

      return 1;
    }
    @Override
    public int _handleToken(ParseContext ctx) throws InvalidQueryException {
      Token token = ctx.getTokens()[ctx.getCurrentTokensIndex()];
      LogicalOperator logicalOp =
          LogicalOperatorFactory.createOperator(token.getValue(), ctx.getPrecedenceLevel());
      ctx.updateMaxPrecedence(logicalOp.getPrecedence());
      ctx.addExpression(LogicalExpressionFactory.createLogicalExpression(logicalOp));

      return 1;
    }