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    }