org.hibernate.hql.internal.ast.HqlParser.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.hql.internal.ast.HqlParser.java

Source

/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.hql.internal.ast;

import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.hibernate.QueryException;
import org.hibernate.hql.internal.antlr.HqlBaseParser;
import org.hibernate.hql.internal.antlr.HqlTokenTypes;
import org.hibernate.hql.internal.ast.util.ASTPrinter;
import org.hibernate.hql.internal.ast.util.ASTUtil;
import org.hibernate.hql.internal.ast.util.TokenPrinters;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper;

import antlr.ASTPair;
import antlr.MismatchedTokenException;
import antlr.RecognitionException;
import antlr.Token;
import antlr.TokenStreamException;
import antlr.collections.AST;

/**
 * Implements the semantic action methods defined in the HQL base parser to keep the grammar
 * source file a little cleaner.  Extends the parser class generated by ANTLR.
 *
 * @author Joshua Davis (pgmjsd@sourceforge.net)
 */
public final class HqlParser extends HqlBaseParser {
    private static final CoreMessageLogger LOG = CoreLogging.messageLogger(HqlParser.class);

    private final ParseErrorHandler parseErrorHandler;

    /**
     * Get a HqlParser instance for the given HQL string.
     *
     * @param hql The HQL query string
     *
     * @return The parser.
     */
    public static HqlParser getInstance(String hql) {
        return new HqlParser(hql);
    }

    private HqlParser(String hql) {
        // The fix for HHH-558...
        super(new HqlLexer(new StringReader(hql)));
        parseErrorHandler = new ErrorTracker(hql);
        // Create nodes that track line and column number.
        setASTFactory(new HqlASTFactory());
    }

    // handle trace logging ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    private int traceDepth;

    @Override
    public void traceIn(String ruleName) {
        if (!LOG.isTraceEnabled()) {
            return;
        }
        if (inputState.guessing > 0) {
            return;
        }
        String prefix = StringHelper.repeat('-', (traceDepth++ * 2)) + "-> ";
        LOG.trace(prefix + ruleName);
    }

    @Override
    public void traceOut(String ruleName) {
        if (!LOG.isTraceEnabled()) {
            return;
        }
        if (inputState.guessing > 0) {
            return;
        }
        String prefix = "<-" + StringHelper.repeat('-', (--traceDepth * 2)) + " ";
        LOG.trace(prefix + ruleName);
    }

    @Override
    public void reportError(RecognitionException e) {
        parseErrorHandler.reportError(e); // Use the delegate.
    }

    @Override
    public void reportError(String s) {
        parseErrorHandler.reportError(s); // Use the delegate.
    }

    @Override
    public void reportWarning(String s) {
        parseErrorHandler.reportWarning(s);
    }

    public ParseErrorHandler getParseErrorHandler() {
        return parseErrorHandler;
    }

    /**
     * Overrides the base behavior to retry keywords as identifiers.
     *
     * @param token The token.
     * @param ex The recognition exception.
     *
     * @return AST - The new AST.
     *
     * @throws antlr.RecognitionException if the substitution was not possible.
     * @throws antlr.TokenStreamException if the substitution was not possible.
     */
    @Override
    public AST handleIdentifierError(Token token, RecognitionException ex)
            throws RecognitionException, TokenStreamException {
        // If the token can tell us if it could be an identifier...
        if (token instanceof HqlToken) {
            HqlToken hqlToken = (HqlToken) token;
            // ... and the token could be an identifier and the error is
            // a mismatched token error ...
            if (hqlToken.isPossibleID() && (ex instanceof MismatchedTokenException)) {
                MismatchedTokenException mte = (MismatchedTokenException) ex;
                // ... and the expected token type was an identifier, then:
                if (mte.expecting == HqlTokenTypes.IDENT) {
                    // Use the token as an identifier.
                    reportWarning("Keyword  '" + token.getText()
                            + "' is being interpreted as an identifier due to: " + mte.getMessage());
                    // Add the token to the AST.
                    ASTPair currentAST = new ASTPair();
                    token.setType(HqlTokenTypes.WEIRD_IDENT);
                    astFactory.addASTChild(currentAST, astFactory.create(token));
                    consume();
                    return currentAST.root;
                }
            } // if
        } // if
          // Otherwise, handle the error normally.
        return super.handleIdentifierError(token, ex);
    }

    /**
     * Returns an equivalent tree for (NOT (a relop b) ), for example:<pre>
     * (NOT (GT a b) ) => (LE a b)
     * </pre>
     *
     * @param x The sub tree to transform, the parent is assumed to be NOT.
     *
     * @return AST - The equivalent sub-tree.
     */
    @Override
    public AST negateNode(AST x) {
        //TODO: switch statements are always evil! We already had bugs because
        //      of forgotten token types. Use polymorphism for this!
        switch (x.getType()) {
        case OR: {
            x.setType(AND);
            x.setText("{and}");
            x.setFirstChild(negateNode(x.getFirstChild()));
            x.getFirstChild().setNextSibling(negateNode(x.getFirstChild().getNextSibling()));
            return x;
        }
        case AND: {
            x.setType(OR);
            x.setText("{or}");
            x.setFirstChild(negateNode(x.getFirstChild()));
            x.getFirstChild().setNextSibling(negateNode(x.getFirstChild().getNextSibling()));
            return x;
        }
        case EQ: {
            // (NOT (EQ a b) ) => (NE a b)
            x.setType(NE);
            x.setText("{not}" + x.getText());
            return x;
        }
        case NE: {
            // (NOT (NE a b) ) => (EQ a b)
            x.setType(EQ);
            x.setText("{not}" + x.getText());
            return x;
        }
        case GT: {
            // (NOT (GT a b) ) => (LE a b)
            x.setType(LE);
            x.setText("{not}" + x.getText());
            return x;
        }
        case LT: {
            // (NOT (LT a b) ) => (GE a b)
            x.setType(GE);
            x.setText("{not}" + x.getText());
            return x;
        }
        case GE: {
            // (NOT (GE a b) ) => (LT a b)
            x.setType(LT);
            x.setText("{not}" + x.getText());
            return x;
        }
        case LE: {
            // (NOT (LE a b) ) => (GT a b)
            x.setType(GT);
            x.setText("{not}" + x.getText());
            return x;
        }
        case LIKE: {
            // (NOT (LIKE a b) ) => (NOT_LIKE a b)
            x.setType(NOT_LIKE);
            x.setText("{not}" + x.getText());
            return x;
        }
        case NOT_LIKE: {
            // (NOT (NOT_LIKE a b) ) => (LIKE a b)
            x.setType(LIKE);
            x.setText("{not}" + x.getText());
            return x;
        }
        case IN: {
            x.setType(NOT_IN);
            x.setText("{not}" + x.getText());
            return x;
        }
        case NOT_IN: {
            x.setType(IN);
            x.setText("{not}" + x.getText());
            return x;
        }
        case IS_NULL: {
            // (NOT (IS_NULL a b) ) => (IS_NOT_NULL a b)
            x.setType(IS_NOT_NULL);
            x.setText("{not}" + x.getText());
            return x;
        }
        case IS_NOT_NULL: {
            // (NOT (IS_NOT_NULL a b) ) => (IS_NULL a b)
            x.setType(IS_NULL);
            x.setText("{not}" + x.getText());
            return x;
        }
        case BETWEEN: {
            // (NOT (BETWEEN a b) ) => (NOT_BETWEEN a b)
            x.setType(NOT_BETWEEN);
            x.setText("{not}" + x.getText());
            return x;
        }
        case NOT_BETWEEN: {
            // (NOT (NOT_BETWEEN a b) ) => (BETWEEN a b)
            x.setType(BETWEEN);
            x.setText("{not}" + x.getText());
            return x;
        }
        /* This can never happen because this rule will always eliminate the child NOT.
                 case NOT: {
        // (NOT (NOT x) ) => (x)
        return x.getFirstChild();
                 }
        */
        default: {
            // Just add a 'not' parent.
            AST not = super.negateNode(x);
            if (not != x) {
                // relink the next sibling to the new 'not' parent
                not.setNextSibling(x.getNextSibling());
                x.setNextSibling(null);
            }
            return not;
        }
        }
    }

    /**
     * Post process equality expressions, clean up the subtree.
     *
     * @param x The equality expression.
     *
     * @return AST - The clean sub-tree.
     */
    @Override
    public AST processEqualityExpression(AST x) {
        if (x == null) {
            LOG.processEqualityExpression();
            return null;
        }

        int type = x.getType();
        if (type == EQ || type == NE) {
            boolean negated = type == NE;
            if (x.getNumberOfChildren() == 2) {
                AST a = x.getFirstChild();
                AST b = a.getNextSibling();
                // (EQ NULL b) => (IS_NULL b)
                if (a.getType() == NULL && b.getType() != NULL) {
                    return createIsNullParent(b, negated);
                }
                // (EQ a NULL) => (IS_NULL a)
                else if (b.getType() == NULL && a.getType() != NULL) {
                    return createIsNullParent(a, negated);
                } else if (b.getType() == EMPTY) {
                    return processIsEmpty(a, negated);
                } else {
                    return x;
                }
            } else {
                return x;
            }
        } else {
            return x;
        }
    }

    private AST createIsNullParent(AST node, boolean negated) {
        node.setNextSibling(null);
        int type = negated ? IS_NOT_NULL : IS_NULL;
        String text = negated ? "is not null" : "is null";
        return ASTUtil.createParent(astFactory, type, text, node);
    }

    private AST processIsEmpty(AST node, boolean negated) {
        node.setNextSibling(null);
        // NOTE: Because we're using ASTUtil.createParent(), the tree must be created from the bottom up.
        // IS EMPTY x => (EXISTS (QUERY (SELECT_FROM (FROM x) ) ) )
        AST ast = createSubquery(node);
        ast = ASTUtil.createParent(astFactory, EXISTS, "exists", ast);
        // Add NOT if it's negated.
        if (!negated) {
            ast = ASTUtil.createParent(astFactory, NOT, "not", ast);
        }
        return ast;
    }

    private AST createSubquery(AST node) {
        AST ast = ASTUtil.createParent(astFactory, RANGE, "RANGE", node);
        ast = ASTUtil.createParent(astFactory, FROM, "from", ast);
        ast = ASTUtil.createParent(astFactory, SELECT_FROM, "SELECT_FROM", ast);
        ast = ASTUtil.createParent(astFactory, QUERY, "QUERY", ast);
        return ast;
    }

    public void showAst(AST ast, PrintStream out) {
        showAst(ast, new PrintWriter(out));
    }

    private void showAst(AST ast, PrintWriter pw) {
        TokenPrinters.HQL_TOKEN_PRINTER.showAst(ast, pw);
    }

    @Override
    public void matchOptionalFrom() throws RecognitionException, TokenStreamException {
        returnAST = null;
        ASTPair currentAST = new ASTPair();
        AST optionalFrom_AST = null;

        if (LA(1) == FROM) {
            if (LA(2) != DOT) {
                match(FROM);
                optionalFrom_AST = (AST) currentAST.root;
                returnAST = optionalFrom_AST;
            }
        }
    }

    @Override
    public void firstPathTokenWeakKeywords() throws TokenStreamException {
        int t = LA(1);
        switch (t) {
        case DOT:
            LT(0).setType(IDENT);
        }
    }

    @Override
    public void handlePrimaryExpressionDotIdent() throws TokenStreamException {
        if (LA(2) == DOT && LA(3) != IDENT) {
            // See if the second lookahead token can be an identifier.
            HqlToken t = (HqlToken) LT(3);
            if (t.isPossibleID()) {
                // Set it!
                t.setType(IDENT);
                if (LOG.isDebugEnabled()) {
                    LOG.debugf("handleDotIdent() : new LT(3) token - %s", LT(1));
                }
            }
        }
    }

    @Override
    public void weakKeywords() throws TokenStreamException {

        int t = LA(1);
        switch (t) {
        case ORDER:
        case GROUP:
            // Case 1: Multi token keywords GROUP BY and ORDER BY
            // The next token ( LT(2) ) should be 'by'... otherwise, this is just an ident.
            if (LA(2) != LITERAL_by) {
                LT(1).setType(IDENT);
                if (LOG.isDebugEnabled()) {
                    LOG.debugf("weakKeywords() : new LT(1) token - %s", LT(1));
                }
            }
            break;
        default:
            // Case 2: The current token is after FROM and before '.'.
            if (LA(0) == FROM && t != IDENT && LA(2) == DOT) {
                HqlToken hqlToken = (HqlToken) LT(1);
                if (hqlToken.isPossibleID()) {
                    hqlToken.setType(IDENT);
                    if (LOG.isDebugEnabled()) {
                        LOG.debugf("weakKeywords() : new LT(1) token - %s", LT(1));
                    }
                }
            }
            break;
        }
    }

    @Override
    public void expectNamedParameterName() throws TokenStreamException {
        // we expect the token following a COLON (':') to be the name of a named parameter.
        // if the following token is anything other than IDENT we convert its type if possible.

        // NOTE : the LT() call is more expensive than the LA() call; so we
        // use LA() first to see if LT() is needed.
        if (LA(1) != IDENT) {
            final HqlToken nextToken = (HqlToken) LT(1);
            if (nextToken.isPossibleID()) {
                LOG.debugf("Converting keyword [%s] following COLON to IDENT as an expected parameter name",
                        nextToken.getText());
                nextToken.setType(IDENT);
            }
        }
    }

    @Override
    public void handleDotIdent() throws TokenStreamException {
        // This handles HHH-354, where there is a strange property name in a where clause.
        // If the lookahead contains a DOT then something that isn't an IDENT...
        if (LA(1) == DOT && LA(2) != IDENT) {
            // See if the second lookahead token can be an identifier.
            HqlToken t = (HqlToken) LT(2);
            if (t.isPossibleID()) {
                // Set it!
                LT(2).setType(IDENT);
                if (LOG.isDebugEnabled()) {
                    LOG.debugf("handleDotIdent() : new LT(2) token - %s", LT(1));
                }
            }
        }
    }

    @Override
    public void processMemberOf(Token n, AST p, ASTPair currentAST) {
        // convert MEMBER OF to the equivalent IN ELEMENTS structure...
        AST inNode = n == null ? astFactory.create(IN, "in") : astFactory.create(NOT_IN, "not in");
        astFactory.makeASTRoot(currentAST, inNode);

        AST inListNode = astFactory.create(IN_LIST, "inList");
        inNode.addChild(inListNode);
        AST elementsNode = astFactory.create(ELEMENTS, "elements");
        inListNode.addChild(elementsNode);
        elementsNode.addChild(p);
    }

    private Map<String, Set<String>> treatMap;

    @Override
    protected void registerTreat(AST pathToTreat, AST treatAs) {
        final String path = toPathText(pathToTreat);
        final String subclassName = toPathText(treatAs);
        LOG.debugf("Registering discovered request to treat(%s as %s)", path, subclassName);

        if (treatMap == null) {
            treatMap = new HashMap<String, Set<String>>();
        }

        Set<String> subclassNames = treatMap.get(path);
        if (subclassNames == null) {
            subclassNames = new HashSet<String>();
            treatMap.put(path, subclassNames);
        }
        subclassNames.add(subclassName);
    }

    private String toPathText(AST node) {
        final String text = node.getText();
        if (text.equals(".") && node.getFirstChild() != null && node.getFirstChild().getNextSibling() != null
                && node.getFirstChild().getNextSibling().getNextSibling() == null) {
            return toPathText(node.getFirstChild()) + '.' + toPathText(node.getFirstChild().getNextSibling());
        }
        return text;
    }

    public Map<String, Set<String>> getTreatMap() {
        return treatMap == null ? Collections.<String, Set<String>>emptyMap() : treatMap;
    }

    public static void panic() {
        //overriden to avoid System.exit
        throw new QueryException("Parser: panic");
    }
}