/* * Copyright 2011 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.drools.compiler.lang; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Stack; import org.antlr.runtime.BitSet; import org.antlr.runtime.EarlyExitException; import org.antlr.runtime.FailedPredicateException; import org.antlr.runtime.MismatchedNotSetException; import org.antlr.runtime.MismatchedSetException; import org.antlr.runtime.MismatchedTokenException; import org.antlr.runtime.MismatchedTreeNodeException; import org.antlr.runtime.NoViableAltException; import org.antlr.runtime.RecognitionException; import org.antlr.runtime.Token; import org.drools.compiler.compiler.DRLFactory; import org.drools.compiler.compiler.DroolsParserException; import org.kie.internal.builder.conf.LanguageLevelOption; /** * Helper class that generates DroolsParserException with user friendly error * messages. * * @see DroolsParserException */ public class DroolsParserExceptionFactory { public final static String MISMATCHED_TOKEN_MESSAGE_COMPLETE = "Line %1$d:%2$d mismatched input '%3$s' expecting '%4$s'%5$s"; public final static String MISMATCHED_TOKEN_MESSAGE_PART = "Line %1$d:%2$d mismatched input '%3$s'%4$s"; public final static String MISMATCHED_TREE_NODE_MESSAGE_COMPLETE = "Line %1$d:%2$d mismatched tree node '%3$s' expecting '%4$s'%5$s"; public final static String MISMATCHED_TREE_NODE_MESSAGE_PART = "Line %1$d:%2$d mismatched tree node '%3$s'%4$s"; public final static String NO_VIABLE_ALT_MESSAGE = "Line %1$d:%2$d no viable alternative at input '%3$s'%4$s"; public final static String EARLY_EXIT_MESSAGE = "Line %1$d:%2$d required (...)+ loop did not match anything at input '%3$s'%4$s"; public final static String MISMATCHED_SET_MESSAGE = "Line %1$d:%2$d mismatched input '%3$s' expecting one of the following tokens: '%4$s'%5$s."; public final static String MISMATCHED_NOT_SET_MESSAGE = "Line %1$d:%2$d mismatched input '%3$s' not expecting any of the following tokens: '%4$s'%5$s"; public final static String FAILED_PREDICATE_MESSAGE = "Line %1$d:%2$d rule '%3$s' failed predicate: {%4$s}?%5$s"; public final static String TRAILING_SEMI_COLON_NOT_ALLOWED_MESSAGE = "Line %1$d:%2$d trailing semi-colon not allowed%3$s"; public final static String PARSER_LOCATION_MESSAGE_COMPLETE = " in %1$s %2$s"; public final static String PARSER_LOCATION_MESSAGE_PART = " in %1$s"; public final static String UNEXPECTED_EXCEPTION = "Line %1$d:%2$d unexpected exception at input '%3$s'. Exception: %4$s. Stack trace:\n %5$s"; private final Stack<Map<DroolsParaphraseTypes, String>> paraphrases; // TODO: need to deal with this array private String[] tokenNames = null; private final LanguageLevelOption languageLevel; /** * DroolsParserErrorMessages constructor. * * @param tokenNames * tokenNames generated by ANTLR * @param paraphrases * paraphrases parser structure */ public DroolsParserExceptionFactory(Stack<Map<DroolsParaphraseTypes, String>> paraphrases, LanguageLevelOption languageLevel) { this.paraphrases = paraphrases; this.languageLevel = languageLevel; } /** * This method creates a DroolsParserException for trailing semicolon * exception, full of information. * * @param line * line number * @param column * column position * @param offset * char offset * @return DroolsParserException filled. */ public DroolsParserException createTrailingSemicolonException( int line, int column, int offset ) { String message = String .format( TRAILING_SEMI_COLON_NOT_ALLOWED_MESSAGE, line, column, formatParserLocation() ); return new DroolsParserException( "ERR 104", message, line, column, offset, null ); } /** * This method creates a DroolsParserException full of information. * * @param e * original exception * @return DroolsParserException filled. */ public DroolsParserException createDroolsException( RecognitionException e ) { List<String> codeAndMessage = createErrorMessage( e ); return new DroolsParserException( codeAndMessage.get( 1 ), codeAndMessage .get( 0 ), e.line, e.charPositionInLine, e.index, e ); } /** * This will take a RecognitionException, and create a sensible error * message out of it */ private List<String> createErrorMessage( RecognitionException e ) { List<String> codeAndMessage = new ArrayList<String>( 2 ); String message; if ( e instanceof MismatchedTokenException ) { MismatchedTokenException mte = (MismatchedTokenException) e; String expecting = mte instanceof DroolsMismatchedTokenException ? ((DroolsMismatchedTokenException)mte).getTokenText() : getBetterToken( mte.expecting ); if ( tokenNames != null && mte.expecting >= 0 && mte.expecting < tokenNames.length ) { message = String .format( MISMATCHED_TOKEN_MESSAGE_COMPLETE, e.line, e.charPositionInLine, getBetterToken( e.token ), expecting, formatParserLocation() ); codeAndMessage.add( message ); codeAndMessage.add( "ERR 102" ); } else { message = String .format( MISMATCHED_TOKEN_MESSAGE_PART, e.line, e.charPositionInLine, getBetterToken( e.token ), formatParserLocation() ); codeAndMessage.add( message ); codeAndMessage.add( "ERR 102" ); } } else if ( e instanceof MismatchedTreeNodeException ) { MismatchedTreeNodeException mtne = (MismatchedTreeNodeException) e; if ( mtne.expecting >= 0 && mtne.expecting < tokenNames.length ) { message = String .format( MISMATCHED_TREE_NODE_MESSAGE_COMPLETE, e.line, e.charPositionInLine, getBetterToken( e.token ), getBetterToken( mtne.expecting ), formatParserLocation() ); codeAndMessage.add( message ); codeAndMessage.add( "ERR 106" ); } else { message = String .format( MISMATCHED_TREE_NODE_MESSAGE_PART, e.line, e.charPositionInLine, getBetterToken( e.token ), formatParserLocation() ); codeAndMessage.add( message ); codeAndMessage.add( "ERR 106" ); } } else if ( e instanceof NoViableAltException ) { // NoViableAltException nvae = (NoViableAltException) e; message = String.format( NO_VIABLE_ALT_MESSAGE, e.line, e.charPositionInLine, getBetterToken( e.token ), formatParserLocation() ); codeAndMessage.add( message ); codeAndMessage.add( "ERR 101" ); } else if ( e instanceof EarlyExitException ) { // EarlyExitException eee = (EarlyExitException) e; message = String.format( EARLY_EXIT_MESSAGE, e.line, e.charPositionInLine, getBetterToken( e.token ), formatParserLocation() ); codeAndMessage.add( message ); codeAndMessage.add( "ERR 105" ); } else if ( e instanceof MismatchedSetException ) { MismatchedSetException mse = (MismatchedSetException) e; String expected = expectedTokensAsString( mse.expecting ); message = String.format( MISMATCHED_SET_MESSAGE, e.line, e.charPositionInLine, getBetterToken( e.token ), expected, formatParserLocation() ); codeAndMessage.add( message ); codeAndMessage.add( "ERR 107" ); } else if ( e instanceof DroolsMismatchedSetException ) { DroolsMismatchedSetException mse = (DroolsMismatchedSetException) e; String expected = Arrays.asList( mse.getTokenText() ).toString(); message = String.format( MISMATCHED_SET_MESSAGE, e.line, e.charPositionInLine, getBetterToken( e.token ), expected, formatParserLocation() ); codeAndMessage.add( message ); codeAndMessage.add( "ERR 107" ); } else if ( e instanceof MismatchedNotSetException ) { MismatchedNotSetException mse = (MismatchedNotSetException) e; String expected = expectedTokensAsString( mse.expecting ); message = String.format( MISMATCHED_NOT_SET_MESSAGE, e.line, e.charPositionInLine, getBetterToken( e.token ), expected, formatParserLocation() ); codeAndMessage.add( message ); codeAndMessage.add( "ERR 108" ); } else if ( e instanceof FailedPredicateException ) { FailedPredicateException fpe = (FailedPredicateException) e; message = String.format( FAILED_PREDICATE_MESSAGE, e.line, e.charPositionInLine, fpe.ruleName, fpe.predicateText, formatParserLocation() ); codeAndMessage.add( message ); codeAndMessage.add( "ERR 103" ); } if ( codeAndMessage.get( 0 ).length() == 0 ) { codeAndMessage.add( "?????" ); } return codeAndMessage; } public DroolsParserException createDroolsException( Exception e, Token token ) { StringWriter sw = new StringWriter(); e.printStackTrace( new PrintWriter(sw) ); return new DroolsParserException( String.format( DroolsParserExceptionFactory.UNEXPECTED_EXCEPTION, token.getLine(), token.getCharPositionInLine(), getBetterToken( token ), e.toString(), sw.toString() ), e ); } private String expectedTokensAsString( BitSet set ) { StringBuilder buf = new StringBuilder(); buf.append( "{ " ); int i = 0; for ( int token : set.toArray() ) { if ( i > 0 ) buf.append( ", " ); buf.append( getBetterToken( token ) ); i++; } buf.append( " }" ); return buf.toString(); } /** * This will take Paraphrases stack, and create a sensible location */ private String formatParserLocation() { StringBuilder sb = new StringBuilder(); if ( paraphrases != null ) { for ( Map<DroolsParaphraseTypes, String> map : paraphrases ) { for ( Entry<DroolsParaphraseTypes, String> activeEntry : map.entrySet() ) { if ( activeEntry.getValue().length() == 0 ) { String kStr = getLocationName( activeEntry.getKey() ); if( kStr.length() > 0 ){ sb.append( String.format( PARSER_LOCATION_MESSAGE_PART, kStr ) ); } } else { sb.append( String.format( PARSER_LOCATION_MESSAGE_COMPLETE, getLocationName( activeEntry.getKey() ), activeEntry.getValue() ) ); } } } } return sb.toString(); } /** * Returns a string based on Paraphrase Type * * @param type * Paraphrase Type * @return a string representing the */ private String getLocationName( DroolsParaphraseTypes type ) { switch ( type ) { case PACKAGE : return "package"; case IMPORT : return "import"; case FUNCTION_IMPORT : return "function import"; case ACCUMULATE_IMPORT : return "accumulate import"; case GLOBAL : return "global"; case FUNCTION : return "function"; case QUERY : return "query"; case TEMPLATE : return "template"; case RULE : return "rule"; case RULE_ATTRIBUTE : return "rule attribute"; case PATTERN : return "pattern"; case EVAL : return "eval"; default : return ""; } } /** * Helper method that creates a user friendly token definition * * @param token * token * @return user friendly token definition */ private String getBetterToken( Token token ) { if ( token == null ) { return ""; } return DRLFactory.getBetterToken(token.getType(), token.getText(), languageLevel); } /** * Helper method that creates a user friendly token definition * * @param tokenType * token type * @return user friendly token definition */ private String getBetterToken( int tokenType ) { return DRLFactory.getBetterToken( tokenType, null, languageLevel ); } }