Java tutorial
/* * 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"); } }