/*
* *************************************************************************************
* Copyright (C) 2008 EsperTech, Inc. All rights reserved. *
* http://esper.codehaus.org *
* http://www.espertech.com *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the GPL license *
* a copy of which has been included with this distribution in the license.txt file. *
* *************************************************************************************
*/
package com.espertech.esper.epl.parse;
import com.espertech.esper.client.EPStatementSyntaxException;
import com.espertech.esper.client.PropertyAccessException;
import com.espertech.esper.collection.UniformPair;
import com.espertech.esper.epl.generated.EsperEPL2GrammarLexer;
import com.espertech.esper.epl.generated.EsperEPL2GrammarParser;
import org.antlr.v4.runtime.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.StringWriter;
import java.util.Set;
import java.util.Stack;
/**
* Converts recognition exceptions.
*/
public class ExceptionConvertor
{
private static final Log log = LogFactory.getLog(ExceptionConvertor.class);
protected final static String END_OF_INPUT_TEXT = "end-of-input";
/**
* Converts from a syntax error to a nice statement exception.
* @param e is the syntax error
* @param expression is the expression text
* @param parser the parser that parsed the expression
* @param addPleaseCheck indicates to add "please check" paraphrases
* @return syntax exception
*/
public static EPStatementSyntaxException convertStatement(RecognitionException e, String expression, boolean addPleaseCheck, EsperEPL2GrammarParser parser)
{
UniformPair<String> pair = convert(e, expression, addPleaseCheck, parser);
return new EPStatementSyntaxException(pair.getFirst(), pair.getSecond());
}
/**
* Converts from a syntax error to a nice property exception.
* @param e is the syntax error
* @param expression is the expression text
* @param parser the parser that parsed the expression
* @param addPleaseCheck indicates to add "please check" paraphrases
* @return syntax exception
*/
public static PropertyAccessException convertProperty(RecognitionException e, String expression, boolean addPleaseCheck, EsperEPL2GrammarParser parser)
{
UniformPair<String> pair = convert(e, expression, addPleaseCheck, parser);
return new PropertyAccessException(pair.getFirst(), pair.getSecond());
}
/**
* Converts from a syntax error to a nice exception.
* @param e is the syntax error
* @param expression is the expression text
* @param parser the parser that parsed the expression
* @param addPleaseCheck indicates to add "please check" paraphrases
* @return syntax exception
*/
public static UniformPair<String> convert(RecognitionException e, String expression, boolean addPleaseCheck, EsperEPL2GrammarParser parser)
{
if (expression.trim().length() == 0)
{
String message = "Unexpected " + END_OF_INPUT_TEXT;
return new UniformPair<String>(message, expression);
}
Token t;
Token tBeforeBefore = null;
Token tBefore = null;
Token tAfter = null;
int tIndex = e.getOffendingToken() != null ? e.getOffendingToken().getTokenIndex() : Integer.MAX_VALUE;
if (tIndex < parser.getTokenStream().size())
{
t = parser.getTokenStream().get(tIndex);
if ((tIndex + 1) < parser.getTokenStream().size())
{
tAfter = parser.getTokenStream().get(tIndex + 1);
}
if (tIndex - 1 >= 0) {
tBefore = parser.getTokenStream().get(tIndex - 1);
}
if (tIndex - 2 >= 0) {
tBeforeBefore = parser.getTokenStream().get(tIndex - 2);
}
}
else
{
if (parser.getTokenStream().size() >= 1) {
tBeforeBefore = parser.getTokenStream().get(parser.getTokenStream().size() - 1);
}
if (parser.getTokenStream().size() >= 2) {
tBefore = parser.getTokenStream().get(parser.getTokenStream().size() - 2);
}
t = parser.getTokenStream().get(parser.getTokenStream().size() - 1);
}
Token tEnd = null;
if (parser.getTokenStream().size() > 0) {
tEnd = parser.getTokenStream().get(parser.getTokenStream().size() - 1);
}
String positionInfo = getPositionInfo(t);
String token = t.getType() == EsperEPL2GrammarParser.EOF ? "end-of-input" : "'" + t.getText() + "'";
Stack stack = parser.getParaphrases();
String check = "";
boolean isSelect = stack.size() == 1 && stack.get(0).equals("select clause");
if ((stack.size() > 0) && addPleaseCheck)
{
String delimiter = "";
StringBuilder checkList = new StringBuilder();
checkList.append(", please check the ");
while(stack.size() != 0)
{
checkList.append(delimiter);
checkList.append(stack.pop());
delimiter = " within the ";
}
check = checkList.toString();
}
// check if token is a reserved keyword
Set<String> keywords = parser.getKeywords();
boolean reservedKeyword = false;
if (keywords.contains(token.toLowerCase()))
{
token += " (a reserved keyword)";
reservedKeyword = true;
}
else
{
if ((tBefore != null) &&
(tAfter != null) &&
(keywords.contains("'" + tBefore.getText().toLowerCase() + "'")) &&
(keywords.contains("'" + tAfter.getText().toLowerCase() + "'")))
{
token += " ('" + tBefore.getText() + "' and '" + tAfter.getText() + "' are a reserved keyword)";
reservedKeyword = true;
}
else if ((tBefore != null) &&
(keywords.contains("'" + tBefore.getText().toLowerCase() + "'")))
{
token += " ('" + tBefore.getText() + "' is a reserved keyword)";
reservedKeyword = true;
}
else if (tEnd != null && keywords.contains("'" + tEnd.getText().toLowerCase() + "'")) {
token += " ('" + tEnd.getText() + "' is a reserved keyword)";
reservedKeyword = true;
}
}
// special handling for the select-clause "as" keyword, which is required
if (isSelect && !reservedKeyword) {
check += getSelectClauseAsText(tBeforeBefore, t);
}
String message = "Incorrect syntax near " + token + positionInfo + check;
if (e instanceof NoViableAltException || e instanceof LexerNoViableAltException)
{
Token nvaeToken;
if (e instanceof NoViableAltException) {
NoViableAltException nvae = (NoViableAltException) e;
nvaeToken = nvae.getOffendingToken();
}
else {
LexerNoViableAltException nvae = (LexerNoViableAltException) e;
nvaeToken = nvae.getOffendingToken();
}
int nvaeTokenType = nvaeToken != null ? nvaeToken.getType() : EsperEPL2GrammarLexer.EOF;
if (nvaeTokenType == EsperEPL2GrammarLexer.EOF)
{
if (token.equals(END_OF_INPUT_TEXT)) {
message = "Unexpected " + END_OF_INPUT_TEXT + positionInfo + check;
}
else {
message = "Unexpected " + END_OF_INPUT_TEXT + " near " + token + positionInfo + check;
}
}
else
{
if (parser.getParserTokenParaphrases().get(nvaeTokenType) != null)
{
message = "Incorrect syntax near " + token + positionInfo + check;
}
else
{
// find next keyword in the next 3 tokens
int currentIndex = tIndex + 1;
while ((currentIndex > 0) &&
(currentIndex < parser.getTokenStream().size() - 1) &&
(currentIndex < tIndex + 3))
{
Token next = parser.getTokenStream().get(currentIndex);
currentIndex++;
String quotedToken = "'" + next.getText() + "'";
if (parser.getKeywords().contains(quotedToken))
{
check += " near reserved keyword '" + next.getText() + "'";
break;
}
}
message = "Incorrect syntax near " + token + positionInfo + check;
}
}
}
if (e instanceof InputMismatchException)
{
InputMismatchException mismatched = (InputMismatchException) e;
String expected;
if (mismatched.getExpectedTokens().size() > 1) {
StringWriter writer = new StringWriter();
writer.append("any of the following tokens {");
String delimiter = "";
for (int i = 0; i < mismatched.getExpectedTokens().size(); i++) {
writer.append(delimiter);
if (i > 5) {
writer.append("...");
writer.append(Integer.toString(mismatched.getExpectedTokens().size() - 5));
writer.append(" more");
break;
}
delimiter = ", ";
writer.append(getTokenText(parser, mismatched.getExpectedTokens().get(i)));
}
writer.append("}");
expected = writer.toString();
}
else {
expected = getTokenText(parser, mismatched.getExpectedTokens().get(0));
}
int offendingTokenType = mismatched.getOffendingToken().getType();
String unexpected = getTokenText(parser, offendingTokenType);
String expecting = " expecting " + expected.trim() + " but found " + unexpected.trim();
message = "Incorrect syntax near " + token + expecting + positionInfo + check;
}
return new UniformPair<String>(message, expression);
}
private static String getTokenText(EsperEPL2GrammarParser parser, int tokenIndex) {
String expected = END_OF_INPUT_TEXT;
if ((tokenIndex >= 0) && (tokenIndex < parser.getTokenNames().length)) {
expected = parser.getTokenNames()[tokenIndex];
}
if (parser.getLexerTokenParaphrases().get(tokenIndex) != null) {
expected = parser.getLexerTokenParaphrases().get(tokenIndex);
}
if (parser.getParserTokenParaphrases().get(tokenIndex) != null) {
expected = parser.getParserTokenParaphrases().get(tokenIndex);
}
return expected;
}
/**
* Returns the position information string for a parser exception.
* @param t the token to return the information for
* @return is a string with line and column information
*/
private static String getPositionInfo(Token t)
{
return t.getLine() > 0 && t.getCharPositionInLine() > 0
? " at line " + t.getLine() + " column " + t.getCharPositionInLine()
: "";
}
private static String getSelectClauseAsText(Token tBeforeBefore, Token t) {
if (tBeforeBefore != null &&
tBeforeBefore.getType() == EsperEPL2GrammarParser.IDENT &&
t != null &&
t.getType() == EsperEPL2GrammarParser.IDENT) {
return " (did you forget 'as'?)";
}
return "";
}
}