package org.webcat.exceptiondoctor;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import org.webcat.exceptiondoctor.runtime.Debugger;
/**
* This is an Abstract handler class that all Handlers extend. It includes many
* utility functions and standardize the exception messages.
*
* @author mike
*
*/
public abstract class AbstractExceptionHandler
implements
ExceptionHandlerInterface
{
/** The name of the exception being handled. */
protected final String exceptionName;
/**
* This sets the exception name for the exception handler.
*
* @param myExceptionName The exception name.
*/
public AbstractExceptionHandler(String myExceptionName)
{
exceptionName = myExceptionName;
}
/**
* A general method to wrap exceptions. This should never be called.
*/
public Throwable wrapException(Throwable exToWrap)
{
return null;
}
/**
* Gets the stack trace element out of an exception
*
* @param t The exception to get the stack trace element out of.
* @return a stack trace element that is not part of the JAVA API
*/
protected StackTraceElement getTopMostStackTraceElement(Throwable t)
{
// need to find the topmost StackTraceElement that is *NOT* part of the
// Java API
StackTraceElement[] elements = t.getStackTrace();
if ( elements.length == 0 )
{
return null;
}
// this is the index
int i = 0;
// start with the first one
StackTraceElement e = elements[0];
// keep looking for one that DOESN'T start with "java"
while ( checkExclusions( e ) )
{
i++;
if ( i == elements.length )
{
return null;
}
e = elements[i];
}
return e;
}
private boolean checkExclusions( StackTraceElement e )
{
try
{
InputStream input =
getClass().getResourceAsStream( "/exclude.conf" );
BufferedReader reader =
new BufferedReader( new InputStreamReader( input ) );
String exclusion = reader.readLine();
while ( exclusion != null )
{
String className = e.getClassName();
if ( className.startsWith( exclusion ) )
{
return true;
}
exclusion = reader.readLine();
}
}
catch ( IOException e1 )
{
System.err.println( "Could not open exclusion list" );
e1.printStackTrace();
return true;
}
return false;
}
/**
* Get the offending line out of an exception
*
* @param exToWrap The exception that you are looking for the offending
* line from.
* @param ste The stack trace element indicating the line to find.
* @return the offending line of source code.
*/
protected String getLine( Throwable exToWrap, StackTraceElement ste )
{
return findLine( exToWrap, ste );
}
/**
* Get the offending line out of an exception
*
* @param exToWrap The exception that you are looking for the offending
* line from.
* @return the offending line of source code.
*/
protected String getLine( Throwable exToWrap )
{
StackTraceElement ste = getTopMostStackTraceElement( exToWrap );
return findLine( exToWrap, ste );
}
private String findLine( Throwable exToWrap, StackTraceElement ste )
{
List<String> lines = new ArrayList<String>();
if ( ste == null )
{
// throw new FileNotFoundException();
return "";
}
String line = "";
BufferedReader scan = null;
try
{
scan = getReader( exToWrap, ste );
}
catch ( FileNotFoundException e )
{
return "";
}
catch ( SourceCodeHiddenException e )
{
return "";
}
int num = ste.getLineNumber();
if ( num < 0 )
{
Debugger.println( "Unknown Sourceline" );
return "";
// throw new LineNotFoundException();
}
// loop through and count how many lines have been read
try
{
line = scan.readLine();
while ( line != null )
{
lines.add( line );
line = scan.readLine();
}
}
catch ( IOException e )
{
return null;
}
finally
{
try
{
scan.close();
}
catch ( IOException e )
{
//meh if it cant close, it is ok.
}
}
if(lines.size() == 0 )
return null;
line = lines.get( num-1 );
// int endOfLine = line.trim().lastIndexOf( ';' );
// String partial = line.substring( 0, endOfLine );
// int beginOfLine = partial.lastIndexOf( ";" );
// int begOfLineBracket = line.trim().lastIndexOf( '{' );
// if ( beginOfLine < begOfLineBracket )
// {
// beginOfLine = begOfLineBracket;
// }
// line = line.substring( beginOfLine + 1, endOfLine + 1 ).trim();
// line = line.replaceAll( " ", "" );
// line = line.replaceAll( "\t", "" );
// line = line.replaceAll( "\n", "" );
return line;
}
/**
* This gets a scanner for the source code that caused the exception to
* happen
*
* @param exToWrap
* the exception to wrap
* @param oldStackTraceElement
* the stack trace element that has been found to be the first
* non java api class
* @return a scanner for the source code that caused the exception.
* @throws FileNotFoundException
*/
private BufferedReader getReader(
Throwable exToWrap,
StackTraceElement oldStackTraceElement )
throws SourceCodeHiddenException,
FileNotFoundException
{
BufferedReader scan;
// open the file
String fileName = oldStackTraceElement.getClassName();
scan = openFile( fileName );
Debugger.println( "Source file being opened -- " + fileName );
if ( scan == null )
{
StackTraceElement stackTraceSourceCode = getSourceExists( exToWrap );
if ( stackTraceSourceCode != null )
{
throw new SourceCodeHiddenException( stackTraceSourceCode,
exToWrap );
}
else
{
throw new FileNotFoundException();
}
}
return scan;
}
/**
* opens a file with the full class name.
*
* @param packageName
* fully qualified package and class name
* @return a scanner for the file.
*/
private BufferedReader openFile( String packageName )
{
BufferedReader scan;
packageName = cleanFilename( packageName );
try
{
InputStream in = getClass().getClassLoader()
.getResourceAsStream( packageName );
if ( in == null )
{
in = Thread.currentThread()
.getContextClassLoader()
.getResourceAsStream( packageName );
}
if ( in == null )
{
File existTester = new File( "src/" + packageName );
FileReader reader = null;
if ( existTester.exists() )
{
reader = new FileReader( existTester );
}
else
{
existTester = new File( packageName );
if ( existTester.exists() )
{
reader = new FileReader( existTester );
}
else
{
throw new FileNotFoundException();
}
}
scan = new BufferedReader( reader );
}
else
{
scan = new BufferedReader( new InputStreamReader( in ) );
}
}
catch ( FileNotFoundException ex )
{
scan = null;
}
return scan;
}
private String cleanFilename( String packageName )
{
packageName = packageName.replace( '.', '/' ) + ".java";
return packageName;
}
/**
* Get all of the variables used in a line of source code.
*
* @param line
* the line to be searched for variables
* @param end
* a character or set of characters that should be used to help
* find the variables.
* @return
*/
/*
* public List<String> getVariables(String line, String end) { // eliminate
* any comments first line = stripComments(line); List<String> variables =
* new ArrayList<String>(); variables.addAll(getAllArguments(line, end));
* line = ripOutArguments(line);
*
* // tokenize it based on the character you're looking for //
* StringTokenizer tok = new StringTokenizer(line, String.valueOf(line //
* .charAt(end))); StringTokenizer tok = new StringTokenizer(line, end);
*
* // create the array list of Strings to return int numTokens =
* tok.countTokens() - 1; List<String> classesAndPackages = new
* ArrayList<String>();
*
* // now look for the last part of each token (except for the last) - //
* that's your variable for (int j = 0; j < numTokens; j++) { // get the
* next par String part = tok.nextToken(); String thisVariable = null; //
* remember whether we've found any variable name yet boolean found = false;
* // now loop *backwards* and look for something that indicates the //
* start of the variable name for (int i = part.length() - 1; i >= 0; i--) {
* // keep in mind that there may be some blank spaces between the // [ and
* the variable name
*
* // If we're looking at the end of an arg list, then jump // over it in
* reverse if (part.charAt(i) == ')') { int left = part.lastIndexOf('(', i);
* if (left > 0) { i = left - 1; } }
*
* if (!isStart(part.charAt(i))) found = true; // if we find the starting
* character, save it and break if (found && isStart(part.charAt(i))) {
* thisVariable = part.substring(i + 1, part.length()); break; } } if (found
* && thisVariable == null) { // The variable is the whole "part"
* thisVariable = part;
*
* // If it is a dotted name, reconstruct it if (j > 0 && end.equals(".") &&
* variables.size() > 0) { thisVariable = variables.get(variables.size() -
* 1) + end + thisVariable; } } if (thisVariable != null) { // We should
* really ignore all class names, but without // parsing the import list for
* the class, it isn't possible // to determine whether a name is a class
* name or not. try { // The best we can do is test to see if it is a fully
* // qualified class name //Class<?> c = Class.forName(thisVariable);
* addClassAndPackages(thisVariable, classesAndPackages); } catch (Exception
* e) { try { // OK, we can also check for java.lang classes Class<?> c =
* Class.forName("java.lang." + thisVariable);
* addClassAndPackages(thisVariable, classesAndPackages); } catch (Exception
* e2) { // Ignore any errors, since they mean this isn't // a
* fully-qualified or java.lang class name } } } if (thisVariable != null &&
* !variables.contains(thisVariable)) { variables.add(thisVariable); } } for
* (String className : classesAndPackages) { variables.remove(className); }
* return variables; }
*/
/*
* private String ripOutArguments(String line) { String newLine = ""; while
* (line.indexOf('(') >= 0) { int left = line.indexOf('('); int right = left
* + 1 + getMatchingEndArea(line.substring(left + 1), '(', ')'); newLine +=
* line.substring(0, left + 1); newLine += line.substring(right); line =
* line.substring(right); } return newLine; }
*
* private int getMatchingEndArea(String line, char start, char end) { int
* level = 0; int i = 0; for (; i < line.length(); i++) { if (line.charAt(i)
* == start) level++; if (line.charAt(i) == end && level == 0) { return i; }
* else if (line.charAt(i) == end) { level--; }
*
* } return -1; }
*
* protected List<String> getAllArguments(String line, String end) {
* List<String> vars = new ArrayList<String>(); // eliminate any comments
* first while(line.indexOf(end) >0) { vars.add(line.substring(0,
* line.indexOf(end))); line = line.substring(line.indexOf(end)); } while
* (line.indexOf('(') >= 0) { line = stripComments(line); int left =
* line.indexOf('('); int right = left + 1 +
* getMatchingEndArea(line.substring(left + 1), '(', ')'); if (left < 0 ||
* right < 0) { return new ArrayList<String>(); } getArgs0(vars,
* line.substring(left + 1, right), end); line = line.substring(right); }
* return vars;
*
* }
*
* private void getArgs0(List<String> vars, String innerArgs, String end) {
* String line = innerArgs; innerArgs = innerArgs.trim(); // int left =
* innerArgs.indexOf('('); // int right = getMatchingEndParen(innerArgs); //
* if (left >= 0) // getArgs0(vars, innerArgs.substring(left, right)); if
* (innerArgs.length() == 0) { return; } int eov = innerArgs.length(); int
* i; for (i = innerArgs.length() - 1; i >= 0; i--) { if
* (innerArgs.charAt(i) == ',') { String variableName =
* innerArgs.substring(i + 1, eov);
* vars.addAll(getVariables(variableName.trim(), end));
* vars.add(variableName.trim()); eov = i; } }
* vars.addAll(getVariables(innerArgs.substring(0, eov), end));
* vars.add(innerArgs.substring(0, eov)); }
*/
/*
* private boolean isStart(char c) { return (c == ' ' || c == '.' || c ==
* '\t' || c == '('); }
*/
/**
* returns a string that will be included in the exception error message.
* This string says the type of exception it is.
*
* @return a string saying the type of exception
*/
protected String getErrorType()
{
String article = "a ";
switch ( Character.toLowerCase( exceptionName.charAt( 0 ) ) )
{
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
article = "an ";
}
return "This error is called " + article + exceptionName + ".";
}
/**
* This creates a new exception with a properly formatted exception
* message.
*
* @param exToWrap The exception to be re-written and re-wrapped.
* @param newMessage The message to be used in the new exception.
* @param exceptionType The class that the new exception with be created
* from.
* @return A new exception with a rewritten message and properly wrapped
* exception.
*/
protected Throwable buildNewException(
Throwable exToWrap,
String newMessage,
Class<?> exceptionType )
{
StackTraceElement ste = getTopMostStackTraceElement( exToWrap );
if ( exToWrap == null )
{
return null;
}
String sourceline = getSourceLine( exToWrap, ste );
newMessage = formatMessage( newMessage, sourceline, 70 );
Throwable newException = rewireException( exToWrap,
newMessage,
exceptionType,
ste );
return newException;
}
/**
* Create a new exception with a new message and the stack grace from
* an existing exception.
*
* @param exToWrap The existing exception to pull information from.
* @param newMessage The message for the new exception.
* @param exceptionType The type of the new exception.
* @param ste Currently unused (historical artifact).
* @return The newly created exception, with the stack trace of the
* original.
*/
public Throwable rewireException(
Throwable exToWrap,
String newMessage,
Class<?> exceptionType,
StackTraceElement ste )
{
Throwable newException = constructNewException( newMessage,
exceptionType );
if ( newException == null )
{
return null;
}
// StackTraceElement[] elements = { ste };
// newException.setStackTrace(elements);
//
// newException.initCause(exToWrap);
newException.setStackTrace( exToWrap.getStackTrace() );
return newException;
}
private Throwable constructNewException(
String newMessage,
Class<?> exceptionType )
{
Class<?>[] args = { String.class };
Constructor<?> classConstructor;
Throwable newException;
try
{
classConstructor = exceptionType.getConstructor( args );
newException = (Throwable)classConstructor.newInstance( newMessage );
}
catch ( Throwable e )
{
return null;
}
return newException;
}
/**
* This method sees if source code exists in the stack trace.
*
* @param exception The exception to search the stack trace of.
* @return a boolean representing the result.
*/
private StackTraceElement getSourceExists( Throwable exception )
{
StackTraceElement[] stack = exception.getStackTrace();
BufferedReader result = null;
int i;
for ( i = 0; ( i < stack.length && result == null ); i++ )
{
String fileName = stack[i].getClassName();
result = openFile( fileName );
}
if ( result != null )
{
return stack[i - 1];
}
return null;
}
/**
* Creates a string to add to the exception message containing the
* violating line of code.
*
* @param ex The exception that is being rewritten.
* @param ste The stack trace element specifying the location to use.
* @return A string with the violating source code in it.
*/
public String getSourceLine( Throwable ex, StackTraceElement ste )
{
String line = getLine( ex, ste ).trim();
line = stripComments( line );
String source = "In file " + ste.getFileName();
if ( ste.getLineNumber() > 0 )
{
source += " on line " + ste.getLineNumber();
if ( line.length() > 0 )
{
source += ", which reads:\n\n " + line + "\n";
}
else
{
source += ".";
}
}
else
{
source += ".";
}
return source;
}
private static String wrap( String message, int width, String prefix )
{
StringBuffer buf = new StringBuffer( message.length()
+ ( prefix.length() + 1 ) * ( 1 + message.length() / width ) );
int len = message.length();
int pos = 0;
while ( len - pos > width )
{
int split = message.lastIndexOf( ' ', pos + width );
if ( split < pos )
{
// can't find space earlier on line, so this must be a
// word longer than the specified width--it can't be split
split = message.indexOf( ' ', pos + width );
}
// If there are no more spaces to split on ...
if ( split < 0 )
break;
int newpos = split + 1;
// search backwards from split to skip over preceding blanks
while ( split > 0 && message.charAt( split - 1 ) == ' ' )
{
split--;
}
// search forwards to skip over trailing blanks
while ( newpos < len && message.charAt( newpos ) == ' ' )
{
newpos++;
}
buf.append( "\n" );
buf.append( prefix );
buf.append( message.substring( pos, split ) );
pos = newpos;
}
if ( len > pos )
{
buf.append( "\n" );
buf.append( prefix );
buf.append( message.substring( pos ) );
}
return buf.toString();
}
/**
* This word-wraps an exception message.
*
* @param newMessage The new message to be wrapped in the new exception.
* @param sourceLine The line of source code for the message.
* @param charCount Number of characters to format to.
* @return A string that is properly wrapped.
*/
public String formatMessage(
String newMessage,
String sourceLine,
int charCount )
{
String formattedMessage = wrap( newMessage, charCount, " " );
if ( sourceLine != null && sourceLine.length() > 0 )
{
formattedMessage = "\n " + sourceLine + formattedMessage;
}
formattedMessage += "\n\n " + getErrorType();
formattedMessage += "\n";
return formattedMessage;
}
/**
* Removes any Java-style comments from the line, as well as trimming
* whitespace.
*
* @param line
* The line to strip
* @return The line without any comments and leading/trailing space.
*/
public static String stripComments( String line )
{
return line.trim()
.replaceAll( "/\\*(.)*?\\*/", "" )
.replaceFirst( "^.*\\*/", "" )
.replaceFirst( "//.*$", "" )
.trim();
}
/*
* private static void addClassAndPackages(String className, List<String>
* toList) { int pos = className.lastIndexOf('.'); while (pos > 0) {
* toList.add(className); className = className.substring(0, pos); pos =
* className.lastIndexOf('.'); } toList.add(className); }
*/
/*
* protected List<String> getArrayVariables(String line) { List<String> vars
* = new ArrayList<String>(); while (line.indexOf('[') >= 0) { int left =
* line.indexOf('['); int right = left + 1 +
* getMatchingEndArea(line.substring(left + 1), '[', ']');
* vars.add(line.substring(left + 1, right)); line = line.substring(right);
* } return vars; }
*
* public List<String> getArrayNames(String line) { List<String> vars = new
* ArrayList<String>(); while (line.indexOf('[') >= 0) { int left =
* line.indexOf('['); int right = left + 1 +
* getMatchingEndArea(line.substring(left + 1), '[', ']'); for (int i =
* left; i >= 0; i--) { if (isStart(line.charAt(i))) { String varName =
* line.substring(i, left).trim(); if (!vars.contains(varName))
* vars.add(varName); line = line.substring(right); break; } } } return
* vars; }
*/
}