/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.cassandra.cql3;
import java.util.LinkedList;
import org.antlr.runtime.BaseRecognizer;
import org.antlr.runtime.Parser;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.Token;
import org.antlr.runtime.TokenStream;
import org.apache.cassandra.exceptions.SyntaxException;
ErrorListener
that collect and enhance the errors send by the CQL lexer and parser.
/**
* <code>ErrorListener</code> that collect and enhance the errors send by the CQL lexer and parser.
*/
public final class ErrorCollector implements ErrorListener
{
The offset of the first token of the snippet.
/**
* The offset of the first token of the snippet.
*/
private static final int FIRST_TOKEN_OFFSET = 10;
The offset of the last token of the snippet.
/**
* The offset of the last token of the snippet.
*/
private static final int LAST_TOKEN_OFFSET = 2;
The CQL query.
/**
* The CQL query.
*/
private final String query;
The error messages.
/**
* The error messages.
*/
private final LinkedList<String> errorMsgs = new LinkedList<>();
Creates a new ErrorCollector
instance to collect the syntax errors associated to the specified CQL
query.
Params: - query – the CQL query that will be parsed
/**
* Creates a new <code>ErrorCollector</code> instance to collect the syntax errors associated to the specified CQL
* query.
*
* @param query the CQL query that will be parsed
*/
public ErrorCollector(String query)
{
this.query = query;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
@Override
public void syntaxError(BaseRecognizer recognizer, String[] tokenNames, RecognitionException e)
{
String hdr = recognizer.getErrorHeader(e);
String msg = recognizer.getErrorMessage(e, tokenNames);
StringBuilder builder = new StringBuilder().append(hdr)
.append(' ')
.append(msg);
if (recognizer instanceof Parser)
appendQuerySnippet((Parser) recognizer, builder);
errorMsgs.add(builder.toString());
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
@Override
public void syntaxError(BaseRecognizer recognizer, String errorMsg)
{
errorMsgs.add(errorMsg);
}
Throws the first syntax error found by the lexer or the parser if it exists.
Throws: - SyntaxException – the syntax error.
/**
* Throws the first syntax error found by the lexer or the parser if it exists.
*
* @throws SyntaxException the syntax error.
*/
public void throwFirstSyntaxError() throws SyntaxException
{
if (!errorMsgs.isEmpty())
throw new SyntaxException(errorMsgs.getFirst());
}
Appends a query snippet to the message to help the user to understand the problem.
Params: - parser – the parser used to parse the query
- builder – the
StringBuilder
used to build the error message
/**
* Appends a query snippet to the message to help the user to understand the problem.
*
* @param parser the parser used to parse the query
* @param builder the <code>StringBuilder</code> used to build the error message
*/
private void appendQuerySnippet(Parser parser, StringBuilder builder)
{
TokenStream tokenStream = parser.getTokenStream();
int index = tokenStream.index();
int size = tokenStream.size();
Token from = tokenStream.get(getSnippetFirstTokenIndex(index));
Token to = tokenStream.get(getSnippetLastTokenIndex(index, size));
Token offending = tokenStream.get(getOffendingTokenIndex(index, size));
appendSnippet(builder, from, to, offending);
}
Appends a query snippet to the message to help the user to understand the problem.
Params: - from – the first token to include within the snippet
- to – the last token to include within the snippet
- offending – the token which is responsible for the error
/**
* Appends a query snippet to the message to help the user to understand the problem.
*
* @param from the first token to include within the snippet
* @param to the last token to include within the snippet
* @param offending the token which is responsible for the error
*/
final void appendSnippet(StringBuilder builder,
Token from,
Token to,
Token offending)
{
if (!areTokensValid(from, to, offending))
return;
String[] lines = query.split("\n");
boolean includeQueryStart = (from.getLine() == 1) && (from.getCharPositionInLine() == 0);
boolean includeQueryEnd = (to.getLine() == lines.length)
&& (getLastCharPositionInLine(to) == lines[lines.length - 1].length());
builder.append(" (");
if (!includeQueryStart)
builder.append("...");
String toLine = lines[lineIndex(to)];
int toEnd = getLastCharPositionInLine(to);
lines[lineIndex(to)] = toEnd >= toLine.length() ? toLine : toLine.substring(0, toEnd);
lines[lineIndex(offending)] = highlightToken(lines[lineIndex(offending)], offending);
lines[lineIndex(from)] = lines[lineIndex(from)].substring(from.getCharPositionInLine());
for (int i = lineIndex(from), m = lineIndex(to); i <= m; i++)
builder.append(lines[i]);
if (!includeQueryEnd)
builder.append("...");
builder.append(")");
}
Checks if the specified tokens are valid.
Params: - tokens – the tokens to check
Returns: true
if all the specified tokens are valid ones,
false
otherwise.
/**
* Checks if the specified tokens are valid.
*
* @param tokens the tokens to check
* @return <code>true</code> if all the specified tokens are valid ones,
* <code>false</code> otherwise.
*/
private static boolean areTokensValid(Token... tokens)
{
for (Token token : tokens)
{
if (!isTokenValid(token))
return false;
}
return true;
}
Checks that the specified token is valid.
Params: - token – the token to check
Returns: true
if it is considered as valid, false
otherwise.
/**
* Checks that the specified token is valid.
*
* @param token the token to check
* @return <code>true</code> if it is considered as valid, <code>false</code> otherwise.
*/
private static boolean isTokenValid(Token token)
{
return token.getLine() > 0 && token.getCharPositionInLine() >= 0;
}
Returns the index of the offending token. In the case where the offending token is an extra
character at the end, the index returned by the TokenStream
might be after the last token.
To avoid that problem we need to make sure that the index of the offending token is a valid index
(one for which a token exist).
Params: - index – the token index returned by the
TokenStream
- size – the
TokenStream
size
Returns: the valid index of the offending token
/**
* Returns the index of the offending token. <p>In the case where the offending token is an extra
* character at the end, the index returned by the <code>TokenStream</code> might be after the last token.
* To avoid that problem we need to make sure that the index of the offending token is a valid index
* (one for which a token exist).</p>
*
* @param index the token index returned by the <code>TokenStream</code>
* @param size the <code>TokenStream</code> size
* @return the valid index of the offending token
*/
private static int getOffendingTokenIndex(int index, int size)
{
return Math.min(index, size - 1);
}
Puts the specified token within square brackets.
Params: - line – the line containing the token
- token – the token to put within square brackets
/**
* Puts the specified token within square brackets.
*
* @param line the line containing the token
* @param token the token to put within square brackets
*/
private static String highlightToken(String line, Token token)
{
String newLine = insertChar(line, getLastCharPositionInLine(token), ']');
return insertChar(newLine, token.getCharPositionInLine(), '[');
}
Returns the index of the last character relative to the beginning of the line 0..n-1
Params: - token – the token
Returns: the index of the last character relative to the beginning of the line 0..n-1
/**
* Returns the index of the last character relative to the beginning of the line 0..n-1
*
* @param token the token
* @return the index of the last character relative to the beginning of the line 0..n-1
*/
private static int getLastCharPositionInLine(Token token)
{
return token.getCharPositionInLine() + getLength(token);
}
Return the token length.
Params: - token – the token
Returns: the token length
/**
* Return the token length.
*
* @param token the token
* @return the token length
*/
private static int getLength(Token token)
{
return token.getText().length();
}
Inserts a character at a given position within a String
.
Params: - s – the
String
in which the character must be inserted - index – the position where the character must be inserted
- c – the character to insert
Returns: the modified String
/**
* Inserts a character at a given position within a <code>String</code>.
*
* @param s the <code>String</code> in which the character must be inserted
* @param index the position where the character must be inserted
* @param c the character to insert
* @return the modified <code>String</code>
*/
private static String insertChar(String s, int index, char c)
{
return new StringBuilder().append(s.substring(0, index))
.append(c)
.append(s.substring(index))
.toString();
}
Returns the index of the line number on which this token was matched; index=0..n-1
Params: - token – the token
Returns: the index of the line number on which this token was matched; index=0..n-1
/**
* Returns the index of the line number on which this token was matched; index=0..n-1
*
* @param token the token
* @return the index of the line number on which this token was matched; index=0..n-1
*/
private static int lineIndex(Token token)
{
return token.getLine() - 1;
}
Returns the index of the last token which is part of the snippet.
Params: - index – the index of the token causing the error
- size – the total number of tokens
Returns: the index of the last token which is part of the snippet.
/**
* Returns the index of the last token which is part of the snippet.
*
* @param index the index of the token causing the error
* @param size the total number of tokens
* @return the index of the last token which is part of the snippet.
*/
private static int getSnippetLastTokenIndex(int index, int size)
{
return Math.min(size - 1, index + LAST_TOKEN_OFFSET);
}
Returns the index of the first token which is part of the snippet.
Params: - index – the index of the token causing the error
Returns: the index of the first token which is part of the snippet.
/**
* Returns the index of the first token which is part of the snippet.
*
* @param index the index of the token causing the error
* @return the index of the first token which is part of the snippet.
*/
private static int getSnippetFirstTokenIndex(int index)
{
return Math.max(0, index - FIRST_TOKEN_OFFSET);
}
}