001 package org.hackystat.telemetry.analyzer.language.parser; 002 003 import java.util.regex.Matcher; 004 import java.util.regex.Pattern; 005 006 import org.hackystat.telemetry.analyzer.language.TelemetryLanguageException; 007 import org.hackystat.telemetry.analyzer.language.parser.impl.ParseException; 008 import org.hackystat.telemetry.analyzer.language.parser.impl.Token; 009 import org.hackystat.telemetry.analyzer.language.parser.impl.TokenMgrError; 010 011 /** 012 * Exception for telemetry query parsing related problems. 013 * 014 * @author (Cedric) Qin Zhang 015 * @version $Id$ 016 */ 017 public class ParsingException extends TelemetryLanguageException { 018 019 private static final long serialVersionUID = 1L; 020 private int lineNumber = -1; 021 private int colNumber = -1; 022 023 /** Pattern for TokenMgrError message. */ 024 private static Pattern TOKEN_MGR_ERROR_MSG_PATTERN 025 = Pattern.compile("Lexical error at line \\d+, column \\d+. "); 026 027 /** Pattern for one or more digits. */ 028 private static Pattern DIGITS_PATTERN = Pattern.compile("\\d+"); 029 030 /** 031 * Constructs this parsing exception. Only use this constructor if you don't 032 * know where the parser encounters the error. 033 * 034 * @param message The error message. 035 */ 036 public ParsingException(String message) { 037 super(message); 038 } 039 040 /** 041 * Constructs this parsing exception. 042 * 043 * @param message The error message. 044 * @param errorLineNumber The line (1-indexed) where the parser encounters the error. 045 * @param errorColumnNumber The column (1-indexed) where the parser encounters the error. 046 */ 047 public ParsingException(String message, int errorLineNumber, int errorColumnNumber) { 048 super(message); 049 this.lineNumber = errorLineNumber; 050 this.colNumber = errorColumnNumber; 051 } 052 053 /** 054 * Constructs this parsing exception. <p/> <b>Important: </b> If the exception 055 * passed in is of type <code>ParseException</code>, then there is special 056 * logic to extract error line number and column number. This works with the 057 * ParseException code generated with JavaCC 3.1. I am not sure whether it 058 * will work correctly with other versions of JavaCC, since it uses some 059 * internal knowledge of the parser to get the line number and column number 060 * where the parse error occurs. 061 * 062 * @param exception The exception to be wrapped. 063 */ 064 public ParsingException(Throwable exception) { 065 super(exception.getMessage()); 066 067 // JavaCC specific stuff, works with JavaCC 3.1 and 4.0, not sure about other versions. 068 if (exception instanceof ParseException) { 069 ParseException parseException = (ParseException) exception; 070 if (parseException.currentToken != null) { 071 try { 072 // I am not too sure about JavaCC internal implementation, so I put the following code 073 // in a try block, just in case I get some null pointer error. 074 Token nextToken = parseException.currentToken.next; 075 this.lineNumber = nextToken.beginLine; 076 this.colNumber = nextToken.beginColumn; 077 } 078 catch (Exception ex) { 079 this.lineNumber = -1; 080 this.colNumber = -1; 081 } 082 } 083 } 084 else if (exception instanceof TokenMgrError) { 085 int[] lineAndColumnNumber = this.parseTokenMgrErrorMessage((TokenMgrError) exception); 086 this.lineNumber = lineAndColumnNumber[0]; 087 this.colNumber = lineAndColumnNumber[1]; 088 } 089 } 090 091 /** 092 * Parses the message in <code>TokenMgrError</code> object, and try to 093 * extract line number and col number. 094 * 095 * @param tokenMgrError An instance of <code>TokenMgrError</code>. 096 * 097 * @return An integer array of 2 elements, the first is line number, the 098 * second is column number. Note that if there is any error extracting 099 * the information, then the returned integer array is {-1, -1}. 100 */ 101 private int[] parseTokenMgrErrorMessage(TokenMgrError tokenMgrError) { 102 // If you call TokenMgrError.getMessage(), it returns a string like: 103 // "Lexical error at line 2, column 53. Other stuff..." 104 // I am trying to extract line number and column number, this is the only way. 105 int[] lineAndColumnNumber = new int[] { -1, -1 }; 106 try { 107 String tokenMgrErrorMsg = tokenMgrError.getMessage(); 108 Matcher tokenMgrErrorMsgMatcher = TOKEN_MGR_ERROR_MSG_PATTERN.matcher(tokenMgrErrorMsg); 109 if (tokenMgrErrorMsgMatcher.find()) { 110 Matcher digitsMatcher = DIGITS_PATTERN.matcher(tokenMgrErrorMsgMatcher.group()); 111 if (digitsMatcher.find()) { 112 String strLineNumber = digitsMatcher.group(); 113 if (digitsMatcher.find()) { 114 String strColNumber = digitsMatcher.group(); 115 lineAndColumnNumber[0] = Integer.parseInt(strLineNumber); 116 lineAndColumnNumber[1] = Integer.parseInt(strColNumber); 117 } 118 } 119 } 120 } 121 catch (Exception e) { 122 lineAndColumnNumber[0] = -1; 123 lineAndColumnNumber[1] = -1; 124 } 125 return lineAndColumnNumber; 126 } 127 128 /** 129 * Gets the line number (1-indexed) where the parser encounters the error. 130 * 131 * @return The line number, or a non-positive number indication such information 132 * is not available. 133 */ 134 public int getErrorLineNumber() { 135 return this.lineNumber; 136 } 137 138 /** 139 * Gets the column number (1-indexed) where the parser encounters the error. 140 * 141 * @return The column number, or a non-positive number indication such information 142 * is not available. 143 */ 144 public int getErrorColumnNumber() { 145 return this.colNumber; 146 } 147 }