/*
* Copyright (c) 2013-2014 the original author or authors
*
* Licensed 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 io.werval.util;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Scanner;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static io.werval.util.IllegalArguments.ensureNotNull;
/**
* Stacktraces utilities.
*/
public final class Stacktraces
{
private static final class RecursivePredicate
implements Predicate<Throwable>
{
private final Predicate<Throwable> predicate;
private RecursivePredicate( Predicate<Throwable> predicate )
{
this.predicate = predicate;
}
@Override
public boolean test( Throwable throwable )
{
if( predicate.test( throwable ) )
{
return true;
}
if( throwable.getCause() != null && test( throwable.getCause() ) )
{
return true;
}
for( Throwable suppressed : throwable.getSuppressed() )
{
if( test( suppressed ) )
{
return true;
}
}
return false;
}
}
private static final Pattern LINKS_PATTERN = Pattern.compile( "(?<left>.+\\()(?<file>.+\\..+):(?<line>[0-9]+)\\)" );
private static final Pattern LINKS_PACKAGE_PATTERN = Pattern.compile( ".*at (?<packageName>.+)\\(" );
public static Predicate<Throwable> containsEqual( Class<? extends Throwable> throwableClass )
{
ensureNotNull( "Throwable class", throwableClass );
return new RecursivePredicate( (ex) -> Classes.equal( throwableClass ).test( ex.getClass() ) );
}
public static Predicate<Throwable> containsAssignable( Class<? extends Throwable> throwableClass )
{
ensureNotNull( "Throwable class", throwableClass );
return new RecursivePredicate( (ex) -> Classes.assignable( throwableClass ).test( ex.getClass() ) );
}
public static Predicate<Throwable> containsMessage( String message )
{
ensureNotNull( "Message", message );
return new RecursivePredicate( (ex) -> message.equals( ex.getMessage() ) );
}
public static Predicate<Throwable> containsInMessage( String string )
{
ensureNotNull( "String", string );
return new RecursivePredicate( (ex) -> ex.getMessage() == null ? false : ex.getMessage().contains( string ) );
}
/**
* URL Generator to use with {@link #toHtml(java.lang.Throwable, io.werval.util.Stacktraces.FileURLGenerator)}.
*/
public interface FileURLGenerator
{
/**
* Generate URL for the given filename and line number.
*
* @param packageName Package name
* @param filename File name
* @param line Line number
*
* @return URL for the given filename and line number, or null if not found
*/
String urlFor( String packageName, String filename, int line );
}
/**
* Null object that implements {@link FileURLGenerator}.
*/
public static final class NullFileURLGenerator
implements FileURLGenerator
{
@Override
public String urlFor( String packageName, String filename, int line )
{
return null;
}
}
public static String toString( Throwable throwable )
{
StringWriter traceWriter = new StringWriter();
throwable.printStackTrace( new PrintWriter( traceWriter ) );
return traceWriter.toString();
}
public static CharSequence toHtml( Throwable throwable, FileURLGenerator urlGen )
{
// Parameters
ensureNotNull( "Throwable", throwable );
ensureNotNull( "FileURLGenerator", urlGen );
// Get trace
String originalTrace = toString( throwable );
// Add links
StringWriter traceWriter = new StringWriter();
Scanner scanner = new Scanner( originalTrace );
while( scanner.hasNextLine() )
{
String line = scanner.nextLine();
Matcher matcher = LINKS_PATTERN.matcher( line );
if( matcher.matches() )
{
Matcher packageNameMatcher = LINKS_PACKAGE_PATTERN.matcher( matcher.group( "left" ) );
packageNameMatcher.matches();
String packageName = packageNameMatcher.group( "packageName" );
packageName = packageName.substring( 0, Strings.lastIndexOfNth( packageName, 2, "." ) );
String filename = matcher.group( "file" );
String lineNumber = matcher.group( "line" );
String url = urlGen.urlFor( packageName, filename, Integer.parseInt( lineNumber ) );
if( Strings.isEmpty( url ) )
{
traceWriter.append( line ).append( "\n" );
}
else
{
traceWriter.append( matcher.group( "left" ) ).
append( "<a href=\"" ).append( url ).append( "\">" ).
append( filename ).append( ":" ).append( lineNumber ).append( "</a>)\n" );
}
}
else
{
traceWriter.append( line ).append( "\n" );
}
}
// Put in HTML container
return new StringBuilder()
.append( "<div class=\"werval-stacktrace\" style=\"white-space: pre; font-family: monospace\">\n" )
.append( traceWriter.toString() )
.append( "</div>\n" );
}
private Stacktraces()
{
}
}