package org.webcat.exceptiondoctor;
import java.io.*;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.util.Stack;
public abstract class AbstractHandler
{
protected abstract Class<? extends Throwable> getExceptionType();
protected abstract String getNewMessage( Throwable oldException );
protected String getExceptionName()
{
return getExceptionType().getName();
}
private File getSourceFile( StackTraceElement culprit )
{
File culpritFile = null;
String fileName = culprit.getClassName().replace( '.', '/' ) + ".java";
URL fileURL = Thread.currentThread()
.getContextClassLoader()
.getResource( fileName );
if ( fileURL != null )
{
culpritFile = new File( fileURL.getFile() );
if ( culpritFile.exists() )
return culpritFile;
}
fileURL = getClass().getClassLoader().getResource( fileName );
if ( fileURL != null )
{
culpritFile = new File( fileURL.getFile() );
if ( culpritFile.exists() )
return culpritFile;
}
culpritFile = new File( culprit.getFileName() );
if ( culpritFile.exists() )
return culpritFile;
culpritFile = new File( culprit.getClassName().replace( '.', '/' )
+ ".java" );
if ( culpritFile.exists() )
return culpritFile;
culpritFile = new File( new File( "src" ), culpritFile.getPath() );
if ( culpritFile.exists() )
return culpritFile;
return null;
}
public Throwable wrapException( Throwable oldException )
{
String newMessage = "";
if ( getExceptionName() != null && getExceptionName().length() != 0 )
newMessage += "An exception was thrown.\n";
if ( oldException.getMessage() != null
&& oldException.getMessage().length() != 0 )
newMessage += "Original Message:\n\t" + oldException.getMessage()
+ "\n";
// StackTraceElement culprit = findCulprit( oldException );
StackTraceElement[] newTrace = getCleanedTrace( oldException );
int elementNumber = findCulprit( newTrace );
StackTraceElement culprit = newTrace[elementNumber];
File source = getSourceFile( culprit );
if ( source != null )
{
newMessage += source.getName() + ":";
if ( culprit.getLineNumber() > 0 )
{
newMessage += culprit.getLineNumber() + ":\n\t";
try
{
String sourceLine = findLine( source,
culprit.getLineNumber() );
if ( sourceLine != null )
newMessage += "\t" + stripComments( sourceLine.trim() )
+ "\n";
}
catch ( IOException e )
{
// Just dont add source line
}
}
else
{
newMessage += "\n\tSource code unavailable";
}
newMessage += "\n";
}
if ( getNewMessage( oldException ) != null )
{
newMessage += "Helpful Hints:\n";
if ( culprit.equals( newTrace[0] ) )
{
newMessage += wrap( getNewMessage( oldException ), 80, "\t" )
+ "\n";
}
else
{
String libraryMessage = "";
boolean direct = false;
StackTraceElement[] oldTrace = oldException.getStackTrace();
if ( oldTrace.length >= 2 )
{
if ( oldTrace[1].equals( culprit ) )
direct = true;
libraryMessage += oldTrace[0].getClassName()
+ " appears to" + " be a library class that is used ";
if ( direct )
libraryMessage += "directly";
else
libraryMessage += "indirectly";
libraryMessage += " by your code. "
+ "This situation arose as a result of a method call";
if ( culprit.getLineNumber() >= 0
&& culprit.getFileName() != null )
{
libraryMessage += " on line " + culprit.getLineNumber()
+ " of " + culprit.getFileName() + ".";
}
else
{
libraryMessage += ".";
}
libraryMessage += " Make sure you are providing "
+ "appropriate parameters in your method call,"
+ " and that the relevant object(s) are in a state "
+ "where ";
StackTraceElement called = oldTrace[elementNumber - 1];
if ( called.getMethodName() != null )
libraryMessage += called.getMethodName();
else
libraryMessage += "the method";
libraryMessage += " is appropriate to call.";
}
newMessage += wrap( libraryMessage, 80, "\t" );
}
}
newMessage += "\nStack Trace:\n";
return createNewException( oldException, newMessage, newTrace );
}
private int findCulprit( StackTraceElement[] newTrace )
{
for ( int i = 0; i < newTrace.length; i++ )
{
if ( isStudent( newTrace[i] ) )
return i;
}
return 0;
}
private Throwable createNewException(
Throwable oldException,
String newMessage,
StackTraceElement[] newTrace )
{
Class<? extends Throwable> eType = getExceptionType();
Throwable newException;
try
{
Constructor<? extends Throwable> messageConstructor = eType.getConstructor( String.class );
newException = messageConstructor.newInstance( newMessage );
}
catch ( Exception e )
{
newException = new Throwable( newMessage );
}
newException.initCause( oldException );
newException.setStackTrace( newTrace );
return newException;
}
private StackTraceElement[] getCleanedTrace( Throwable oldException )
{
// List<StackTraceElement> cleanedElements = Arrays.asList(
// oldException.getStackTrace() );
StackTraceElement[] oldTrace = oldException.getStackTrace();
Stack<StackTraceElement> cleanedElements = new Stack<StackTraceElement>();
// Clean Bottom of the stack (Remove infrastructure)
boolean inStudentCode = false;
for ( int i = 0; i < oldTrace.length; i++ )
{
StackTraceElement currentElement = oldTrace[i];
// String packageName = getPackageName( currentElement );
if ( !inStudentCode )
{
inStudentCode = isStudent( currentElement );
}
else
{
if ( !isStudent( currentElement ) )
break;
}
cleanedElements.push( currentElement );
}
return cleanedElements.toArray( new StackTraceElement[cleanedElements.size()] );
}
private String getPackageName( StackTraceElement currentElement )
{
String packageName = currentElement.getClassName();
int lastDot = packageName.lastIndexOf( "." );
if ( lastDot <= 0 )
{
packageName = ".";
}
else
{
packageName = packageName.substring( 0, lastDot );
}
return packageName;
}
private boolean isStudent( StackTraceElement element )
{
File potentialSource = getSourceFile( element );
if ( potentialSource != null && potentialSource.exists() )
return true;
return false;
}
protected String findLine( Throwable exToWrap )
{
StackTraceElement[] newTrace = getCleanedTrace( exToWrap );
int elementNumber = findCulprit( newTrace );
StackTraceElement culprit = newTrace[elementNumber];
File source = getSourceFile( culprit );
if ( culprit.getLineNumber() < 0 || !source.exists() )
return null;
try
{
return findLine( source, culprit.getLineNumber() );
}
catch ( IOException e )
{
return null;
}
}
protected String findLine( File source, int lineNumber ) throws IOException
{
BufferedReader fread = new BufferedReader( new FileReader( source ) );
String line = null;
String read = fread.readLine();
for ( int i = 1; read != null; i++ )
{
if ( i == lineNumber )
{
line = read;
break;
}
read = fread.readLine();
}
return line;
}
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();
}
/**
* 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();
}
}