/* * 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.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; private final ASTPrinter printer = getASTPrinter(); private static ASTPrinter getASTPrinter() { return new ASTPrinter( org.hibernate.hql.internal.antlr.HqlTokenTypes.class ); } /** * 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 ErrorCounter( 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) { 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" ); } }