| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.directory.shared.ldap.util; |
| |
| |
| import java.io.PrintStream; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.sql.SQLException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.StringTokenizer; |
| |
| import org.apache.directory.shared.i18n.I18n; |
| |
| |
| /** |
| * <p> |
| * Provides utilities for manipulating and examining <code>Throwable</code> |
| * objects. |
| * </p> |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| * @version $Rev$ |
| */ |
| public class ExceptionUtils |
| { |
| |
| /** |
| * <p> |
| * Used when printing stack frames to denote the start of a wrapped |
| * exception. |
| * </p> |
| * <p/> |
| * <p> |
| * Package private for accessibility by test suite. |
| * </p> |
| */ |
| static final String WRAPPED_MARKER = " [wrapped] "; |
| |
| /** |
| * <p> |
| * The names of methods commonly used to access a wrapped exception. |
| * </p> |
| */ |
| private static String[] CAUSE_METHOD_NAMES = |
| { "getCause", "getNextException", "getTargetException", "getException", "getSourceException", "getRootCause", |
| "getCausedByException", "getNested", "getLinkedException", "getNestedException", "getLinkedCause", |
| "getThrowable", }; |
| |
| /** |
| * <p> |
| * The Method object for JDK1.4 getCause. |
| * </p> |
| */ |
| private static final Method THROWABLE_CAUSE_METHOD; |
| |
| static |
| { |
| Method getCauseMethod; |
| try |
| { |
| getCauseMethod = Throwable.class.getMethod( "getCause", (Class[])null ); |
| } |
| catch ( Exception e ) |
| { |
| getCauseMethod = null; |
| } |
| THROWABLE_CAUSE_METHOD = getCauseMethod; |
| } |
| |
| |
| /** |
| * <p> |
| * Public constructor allows an instance of <code>ExceptionUtils</code> to |
| * be created, although that is not normally necessary. |
| * </p> |
| */ |
| public ExceptionUtils() |
| { |
| } |
| |
| |
| /** |
| * <p> |
| * Checks if a String is not empty ("") and not null. |
| * </p> |
| * |
| * @param str |
| * the String to check, may be null |
| * @return <code>true</code> if the String is not empty and not null |
| */ |
| private static boolean isNotEmpty( String str ) |
| { |
| return ( str != null && str.length() > 0 ); |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| /** |
| * <p> |
| * Adds to the list of method names used in the search for |
| * <code>Throwable</code> objects. |
| * </p> |
| * |
| * @param methodName |
| * the methodName to add to the list, <code>null</code> and |
| * empty strings are ignored |
| * @since 2.0 |
| */ |
| public static void addCauseMethodName( String methodName ) |
| { |
| if ( isNotEmpty( methodName ) ) |
| { |
| List<String> list = new ArrayList<String>( Arrays.asList( CAUSE_METHOD_NAMES ) ); |
| list.add( methodName ); |
| CAUSE_METHOD_NAMES = list.toArray( new String[list.size()] ); |
| } |
| } |
| |
| |
| /** |
| * <p> |
| * Introspects the <code>Throwable</code> to obtain the cause. |
| * </p> |
| * <p/> |
| * <p> |
| * The method searches for methods with specific names that return a |
| * <code>Throwable</code> object. This will pick up most wrapping |
| * exceptions, including those from JDK 1.4, and The method names can be |
| * added to using {@link #addCauseMethodName(String)}. |
| * </p> |
| * <p/> |
| * <p> |
| * The default list searched for are: |
| * </p> |
| * <ul> |
| * <li><code>getCause()</code></li> |
| * <li><code>getNextException()</code></li> |
| * <li><code>getTargetException()</code></li> |
| * <li><code>getException()</code></li> |
| * <li><code>getSourceException()</code></li> |
| * <li><code>getRootCause()</code></li> |
| * <li><code>getCausedByException()</code></li> |
| * <li><code>getNested()</code></li> |
| * </ul> |
| * <p/> |
| * <p> |
| * In the absence of any such method, the object is inspected for a |
| * <code>detail</code> field assignable to a <code>Throwable</code>. |
| * </p> |
| * <p/> |
| * <p> |
| * If none of the above is found, returns <code>null</code>. |
| * </p> |
| * |
| * @param throwable |
| * the throwable to introspect for a cause, may be null |
| * @return the cause of the <code>Throwable</code>, <code>null</code> |
| * if none found or null throwable input |
| * @since 1.0 |
| */ |
| public static Throwable getCause( Throwable throwable ) |
| { |
| return getCause( throwable, CAUSE_METHOD_NAMES ); |
| } |
| |
| |
| /** |
| * <p> |
| * Introspects the <code>Throwable</code> to obtain the cause. |
| * </p> |
| * <p/> |
| * <ol> |
| * <li>Try known exception types.</li> |
| * <li>Try the supplied array of method names.</li> |
| * <li>Try the field 'detail'.</li> |
| * </ol> |
| * <p/> |
| * <p> |
| * A <code>null</code> set of method names means use the default set. A |
| * <code>null</code> in the set of method names will be ignored. |
| * </p> |
| * |
| * @param throwable |
| * the throwable to introspect for a cause, may be null |
| * @param methodNames |
| * the method names, null treated as default set |
| * @return the cause of the <code>Throwable</code>, <code>null</code> |
| * if none found or null throwable input |
| * @since 1.0 |
| */ |
| public static Throwable getCause( Throwable throwable, String[] methodNames ) |
| { |
| if ( throwable == null ) |
| { |
| return null; |
| } |
| Throwable cause = getCauseUsingWellKnownTypes( throwable ); |
| if ( cause == null ) |
| { |
| if ( methodNames == null ) |
| { |
| methodNames = CAUSE_METHOD_NAMES; |
| } |
| for ( int i = 0; i < methodNames.length; i++ ) |
| { |
| String methodName = methodNames[i]; |
| if ( methodName != null ) |
| { |
| cause = getCauseUsingMethodName( throwable, methodName ); |
| if ( cause != null ) |
| { |
| break; |
| } |
| } |
| } |
| |
| if ( cause == null ) |
| { |
| cause = getCauseUsingFieldName( throwable, "detail" ); |
| } |
| } |
| return cause; |
| } |
| |
| |
| /** |
| * <p> |
| * Introspects the <code>Throwable</code> to obtain the root cause. |
| * </p> |
| * <p/> |
| * <p> |
| * This method walks through the exception chain to the last element, "root" |
| * of the tree, using {@link #getCause(Throwable)}, and returns that |
| * exception. |
| * </p> |
| * |
| * @param throwable |
| * the throwable to get the root cause for, may be null |
| * @return the root cause of the <code>Throwable</code>, |
| * <code>null</code> if none found or null throwable input |
| */ |
| public static Throwable getRootCause( Throwable throwable ) |
| { |
| Throwable cause = getCause( throwable ); |
| if ( cause != null ) |
| { |
| throwable = cause; |
| while ( ( throwable = getCause( throwable ) ) != null ) |
| { |
| cause = throwable; |
| } |
| } |
| return cause; |
| } |
| |
| |
| /** |
| * <p> |
| * Finds a <code>Throwable</code> for known types. |
| * </p> |
| * <p/> |
| * <p> |
| * Uses <code>instanceof</code> checks to examine the exception, looking |
| * for well known types which could contain chained or wrapped exceptions. |
| * </p> |
| * |
| * @param throwable |
| * the exception to examine |
| * @return the wrapped exception, or <code>null</code> if not found |
| */ |
| private static Throwable getCauseUsingWellKnownTypes( Throwable throwable ) |
| { |
| if ( throwable instanceof Nestable ) |
| { |
| return ( ( Nestable ) throwable ).getCause(); |
| } |
| else if ( throwable instanceof SQLException ) |
| { |
| return ( ( SQLException ) throwable ).getNextException(); |
| } |
| else if ( throwable instanceof InvocationTargetException ) |
| { |
| return ( ( InvocationTargetException ) throwable ).getTargetException(); |
| } |
| else |
| { |
| return null; |
| } |
| } |
| |
| |
| /** |
| * <p> |
| * Finds a <code>Throwable</code> by method name. |
| * </p> |
| * |
| * @param throwable |
| * the exception to examine |
| * @param methodName |
| * the name of the method to find and invoke |
| * @return the wrapped exception, or <code>null</code> if not found |
| */ |
| // This will suppress PMD.EmptyCatchBlock warnings in this method |
| @SuppressWarnings("PMD.EmptyCatchBlock") |
| private static Throwable getCauseUsingMethodName( Throwable throwable, String methodName ) |
| { |
| Method method = null; |
| try |
| { |
| method = throwable.getClass().getMethod( methodName, (Class[])null ); |
| } |
| catch ( NoSuchMethodException ignored ) |
| { |
| } |
| catch ( SecurityException ignored ) |
| { |
| } |
| |
| if ( method != null && Throwable.class.isAssignableFrom( method.getReturnType() ) ) |
| { |
| try |
| { |
| return ( Throwable ) method.invoke( throwable, ArrayUtils.EMPTY_OBJECT_ARRAY ); |
| } |
| catch ( IllegalAccessException ignored ) |
| { |
| } |
| catch ( IllegalArgumentException ignored ) |
| { |
| } |
| catch ( InvocationTargetException ignored ) |
| { |
| } |
| } |
| return null; |
| } |
| |
| |
| /** |
| * <p> |
| * Finds a <code>Throwable</code> by field name. |
| * </p> |
| * |
| * @param throwable |
| * the exception to examine |
| * @param fieldName |
| * the name of the attribute to examine |
| * @return the wrapped exception, or <code>null</code> if not found |
| */ |
| // This will suppress PMD.EmptyCatchBlock warnings in this method |
| @SuppressWarnings("PMD.EmptyCatchBlock") |
| private static Throwable getCauseUsingFieldName( Throwable throwable, String fieldName ) |
| { |
| Field field = null; |
| try |
| { |
| field = throwable.getClass().getField( fieldName ); |
| } |
| catch ( NoSuchFieldException ignored ) |
| { |
| } |
| catch ( SecurityException ignored ) |
| { |
| } |
| |
| if ( field != null && Throwable.class.isAssignableFrom( field.getType() ) ) |
| { |
| try |
| { |
| return ( Throwable ) field.get( throwable ); |
| } |
| catch ( IllegalAccessException ignored ) |
| { |
| } |
| catch ( IllegalArgumentException ignored ) |
| { |
| } |
| } |
| return null; |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| /** |
| * <p> |
| * Checks if the Throwable class has a <code>getCause</code> method. |
| * </p> |
| * <p/> |
| * <p> |
| * This is true for JDK 1.4 and above. |
| * </p> |
| * |
| * @return true if Throwable is nestable |
| * @since 2.0 |
| */ |
| public static boolean isThrowableNested() |
| { |
| return ( THROWABLE_CAUSE_METHOD != null ); |
| } |
| |
| |
| /** |
| * <p> |
| * Checks whether this <code>Throwable</code> class can store a cause. |
| * </p> |
| * <p/> |
| * <p> |
| * This method does <b>not</b> check whether it actually does store a |
| * cause. |
| * <p> |
| * |
| * @param throwable |
| * the <code>Throwable</code> to examine, may be null |
| * @return boolean <code>true</code> if nested otherwise |
| * <code>false</code> |
| * @since 2.0 |
| */ |
| // This will suppress PMD.EmptyCatchBlock warnings in this method |
| @SuppressWarnings("PMD.EmptyCatchBlock") |
| public static boolean isNestedThrowable( Throwable throwable ) |
| { |
| if ( throwable == null ) |
| { |
| return false; |
| } |
| |
| if ( throwable instanceof Nestable ) |
| { |
| return true; |
| } |
| else if ( throwable instanceof SQLException ) |
| { |
| return true; |
| } |
| else if ( throwable instanceof InvocationTargetException ) |
| { |
| return true; |
| } |
| else if ( isThrowableNested() ) |
| { |
| return true; |
| } |
| |
| Class cls = throwable.getClass(); |
| for ( int i = 0, isize = CAUSE_METHOD_NAMES.length; i < isize; i++ ) |
| { |
| try |
| { |
| Method method = cls.getMethod( CAUSE_METHOD_NAMES[i], (Class[])null ); |
| if ( method != null && Throwable.class.isAssignableFrom( method.getReturnType() ) ) |
| { |
| return true; |
| } |
| } |
| catch ( NoSuchMethodException ignored ) |
| { |
| } |
| catch ( SecurityException ignored ) |
| { |
| } |
| } |
| |
| try |
| { |
| Field field = cls.getField( "detail" ); |
| if ( field != null ) |
| { |
| return true; |
| } |
| } |
| catch ( NoSuchFieldException ignored ) |
| { |
| } |
| catch ( SecurityException ignored ) |
| { |
| } |
| |
| return false; |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| /** |
| * <p> |
| * Counts the number of <code>Throwable</code> objects in the exception |
| * chain. |
| * </p> |
| * <p/> |
| * <p> |
| * A throwable without cause will return <code>1</code>. A throwable with |
| * one cause will return <code>2</code> and so on. A <code>null</code> |
| * throwable will return <code>0</code>. |
| * </p> |
| * |
| * @param throwable |
| * the throwable to inspect, may be null |
| * @return the count of throwables, zero if null input |
| */ |
| public static int getThrowableCount( Throwable throwable ) |
| { |
| int count = 0; |
| while ( throwable != null ) |
| { |
| count++; |
| throwable = ExceptionUtils.getCause( throwable ); |
| } |
| return count; |
| } |
| |
| |
| /** |
| * <p> |
| * Returns the list of <code>Throwable</code> objects in the exception |
| * chain. |
| * </p> |
| * <p/> |
| * <p> |
| * A throwable without cause will return an array containing one element - |
| * the input throwable. A throwable with one cause will return an array |
| * containing two elements. - the input throwable and the cause throwable. A |
| * <code>null</code> throwable will return an array size zero. |
| * </p> |
| * |
| * @param throwable |
| * the throwable to inspect, may be null |
| * @return the array of throwables, never null |
| */ |
| public static Throwable[] getThrowables( Throwable throwable ) |
| { |
| List<Throwable> list = new ArrayList<Throwable>(); |
| |
| while ( throwable != null ) |
| { |
| list.add( throwable ); |
| throwable = ExceptionUtils.getCause( throwable ); |
| } |
| |
| return list.toArray( new Throwable[list.size()] ); |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| /** |
| * <p> |
| * Returns the (zero based) index of the first <code>Throwable</code> that |
| * matches the specified type in the exception chain. |
| * </p> |
| * <p/> |
| * <p> |
| * A <code>null</code> throwable returns <code>-1</code>. A |
| * <code>null</code> type returns <code>-1</code>. No match in the |
| * chain returns <code>-1</code>. |
| * </p> |
| * |
| * @param throwable |
| * the throwable to inspect, may be null |
| * @param type |
| * the type to search for |
| * @return the index into the throwable chain, -1 if no match or null input |
| */ |
| public static int indexOfThrowable( Throwable throwable, Class type ) |
| { |
| return indexOfThrowable( throwable, type, 0 ); |
| } |
| |
| |
| /** |
| * <p> |
| * Returns the (zero based) index of the first <code>Throwable</code> that |
| * matches the specified type in the exception chain from a specified index. |
| * </p> |
| * <p/> |
| * <p> |
| * A <code>null</code> throwable returns <code>-1</code>. A |
| * <code>null</code> type returns <code>-1</code>. No match in the |
| * chain returns <code>-1</code>. A negative start index is treated as |
| * zero. A start index greater than the number of throwables returns |
| * <code>-1</code>. |
| * </p> |
| * |
| * @param throwable |
| * the throwable to inspect, may be null |
| * @param type |
| * the type to search for |
| * @param fromIndex |
| * the (zero based) index of the starting position, negative |
| * treated as zero, larger than chain size returns -1 |
| * @return the index into the throwable chain, -1 if no match or null input |
| */ |
| public static int indexOfThrowable( Throwable throwable, Class type, int fromIndex ) |
| { |
| if ( throwable == null ) |
| { |
| return -1; |
| } |
| if ( fromIndex < 0 ) |
| { |
| fromIndex = 0; |
| } |
| Throwable[] throwables = ExceptionUtils.getThrowables( throwable ); |
| if ( fromIndex >= throwables.length ) |
| { |
| return -1; |
| } |
| for ( int i = fromIndex; i < throwables.length; i++ ) |
| { |
| if ( throwables[i].getClass().equals( type ) ) |
| { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| /** |
| * <p> |
| * Prints a compact stack trace for the root cause of a throwable to |
| * <code>System.err</code>. |
| * </p> |
| * <p/> |
| * <p> |
| * The compact stack trace starts with the root cause and prints stack |
| * frames up to the place where it was caught and wrapped. Then it prints |
| * the wrapped exception and continues with stack frames until the wrapper |
| * exception is caught and wrapped again, etc. |
| * </p> |
| * <p/> |
| * <p> |
| * The method is equivalent to <code>printStackTrace</code> for throwables |
| * that don't have nested causes. |
| * </p> |
| * |
| * @param throwable |
| * the throwable to output |
| * @since 2.0 |
| */ |
| public static void printRootCauseStackTrace( Throwable throwable ) |
| { |
| printRootCauseStackTrace( throwable, System.err ); |
| } |
| |
| |
| /** |
| * <p> |
| * Prints a compact stack trace for the root cause of a throwable. |
| * </p> |
| * <p/> |
| * <p> |
| * The compact stack trace starts with the root cause and prints stack |
| * frames up to the place where it was caught and wrapped. Then it prints |
| * the wrapped exception and continues with stack frames until the wrapper |
| * exception is caught and wrapped again, etc. |
| * </p> |
| * <p/> |
| * <p> |
| * The method is equivalent to <code>printStackTrace</code> for throwables |
| * that don't have nested causes. |
| * </p> |
| * |
| * @param throwable |
| * the throwable to output, may be null |
| * @param stream |
| * the stream to output to, may not be null |
| * @throws IllegalArgumentException |
| * if the stream is <code>null</code> |
| * @since 2.0 |
| */ |
| public static void printRootCauseStackTrace( Throwable throwable, PrintStream stream ) |
| { |
| if ( throwable == null ) |
| { |
| return; |
| } |
| if ( stream == null ) |
| { |
| throw new IllegalArgumentException( "The PrintStream must not be null" ); |
| } |
| String trace[] = getRootCauseStackTrace( throwable ); |
| for ( int i = 0; i < trace.length; i++ ) |
| { |
| stream.println( trace[i] ); |
| } |
| stream.flush(); |
| } |
| |
| |
| /** |
| * <p> |
| * Prints a compact stack trace for the root cause of a throwable. |
| * </p> |
| * <p/> |
| * <p> |
| * The compact stack trace starts with the root cause and prints stack |
| * frames up to the place where it was caught and wrapped. Then it prints |
| * the wrapped exception and continues with stack frames until the wrapper |
| * exception is caught and wrapped again, etc. |
| * </p> |
| * <p/> |
| * <p> |
| * The method is equivalent to <code>printStackTrace</code> for throwables |
| * that don't have nested causes. |
| * </p> |
| * |
| * @param throwable |
| * the throwable to output, may be null |
| * @param writer |
| * the writer to output to, may not be null |
| * @throws IllegalArgumentException |
| * if the writer is <code>null</code> |
| * @since 2.0 |
| */ |
| public static void printRootCauseStackTrace( Throwable throwable, PrintWriter writer ) |
| { |
| if ( throwable == null ) |
| { |
| return; |
| } |
| if ( writer == null ) |
| { |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_04356 ) ); |
| } |
| String trace[] = getRootCauseStackTrace( throwable ); |
| for ( int i = 0; i < trace.length; i++ ) |
| { |
| writer.println( trace[i] ); |
| } |
| writer.flush(); |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| /** |
| * <p> |
| * Creates a compact stack trace for the root cause of the supplied |
| * <code>Throwable</code>. |
| * </p> |
| * |
| * @param throwable |
| * the throwable to examine, may be null |
| * @return an array of stack trace frames, never null |
| * @since 2.0 |
| */ |
| public static String[] getRootCauseStackTrace( Throwable throwable ) |
| { |
| if ( throwable == null ) |
| { |
| return ArrayUtils.EMPTY_STRING_ARRAY; |
| } |
| |
| Throwable throwables[] = getThrowables( throwable ); |
| int count = throwables.length; |
| List<String> frames = new ArrayList<String>(); |
| List<String> nextTrace = getStackFrameList( throwables[count - 1] ); |
| |
| for ( int i = count; --i >= 0; ) |
| { |
| List<String> trace = nextTrace; |
| |
| if ( i != 0 ) |
| { |
| nextTrace = getStackFrameList( throwables[i - 1] ); |
| removeCommonFrames( trace, nextTrace ); |
| } |
| if ( i == count - 1 ) |
| { |
| frames.add( throwables[i].toString() ); |
| } |
| else |
| { |
| frames.add( WRAPPED_MARKER + throwables[i].toString() ); |
| } |
| for ( int j = 0; j < trace.size(); j++ ) |
| { |
| frames.add( trace.get( j ) ); |
| } |
| } |
| return frames.toArray( new String[0] ); |
| } |
| |
| |
| /** |
| * <p> |
| * Removes common frames from the cause trace given the two stack traces. |
| * </p> |
| * |
| * @param causeFrames |
| * stack trace of a cause throwable |
| * @param wrapperFrames |
| * stack trace of a wrapper throwable |
| * @throws IllegalArgumentException |
| * if either argument is null |
| * @since 2.0 |
| */ |
| public static void removeCommonFrames( List causeFrames, List wrapperFrames ) |
| { |
| if ( causeFrames == null || wrapperFrames == null ) |
| { |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_04357 ) ); |
| } |
| int causeFrameIndex = causeFrames.size() - 1; |
| int wrapperFrameIndex = wrapperFrames.size() - 1; |
| while ( causeFrameIndex >= 0 && wrapperFrameIndex >= 0 ) |
| { |
| // Remove the frame from the cause trace if it is the same |
| // as in the wrapper trace |
| String causeFrame = ( String ) causeFrames.get( causeFrameIndex ); |
| String wrapperFrame = ( String ) wrapperFrames.get( wrapperFrameIndex ); |
| if ( causeFrame.equals( wrapperFrame ) ) |
| { |
| causeFrames.remove( causeFrameIndex ); |
| } |
| causeFrameIndex--; |
| wrapperFrameIndex--; |
| } |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| /** |
| * <p> |
| * Gets the stack trace from a Throwable as a String. |
| * </p> |
| * |
| * @param throwable |
| * the <code>Throwable</code> to be examined |
| * @return the stack trace as generated by the exception's |
| * <code>printStackTrace(PrintWriter)</code> method |
| */ |
| public static String getStackTrace( Throwable throwable ) |
| { |
| StringWriter sw = new StringWriter(); |
| PrintWriter pw = new PrintWriter( sw, true ); |
| throwable.printStackTrace( pw ); |
| return sw.getBuffer().toString(); |
| } |
| |
| |
| /** |
| * <p> |
| * A way to get the entire nested stack-trace of an throwable. |
| * </p> |
| * |
| * @param throwable |
| * the <code>Throwable</code> to be examined |
| * @return the nested stack trace, with the root cause first |
| * @since 2.0 |
| */ |
| public static String getFullStackTrace( Throwable throwable ) |
| { |
| StringWriter sw = new StringWriter(); |
| PrintWriter pw = new PrintWriter( sw, true ); |
| Throwable[] ts = getThrowables( throwable ); |
| for ( int i = 0; i < ts.length; i++ ) |
| { |
| ts[i].printStackTrace( pw ); |
| if ( isNestedThrowable( ts[i] ) ) |
| { |
| break; |
| } |
| } |
| return sw.getBuffer().toString(); |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| /** |
| * <p> |
| * Captures the stack trace associated with the specified |
| * <code>Throwable</code> object, decomposing it into a list of stack |
| * frames. |
| * </p> |
| * |
| * @param throwable |
| * the <code>Throwable</code> to examine, may be null |
| * @return an array of strings describing each stack frame, never null |
| */ |
| public static String[] getStackFrames( Throwable throwable ) |
| { |
| if ( throwable == null ) |
| { |
| return ArrayUtils.EMPTY_STRING_ARRAY; |
| } |
| return getStackFrames( getStackTrace( throwable ) ); |
| } |
| |
| |
| /** |
| * <p> |
| * Functionality shared between the <code>getStackFrames(Throwable)</code> |
| * methods of this and the |
| */ |
| static String[] getStackFrames( String stackTrace ) |
| { |
| String linebreak = SystemUtils.LINE_SEPARATOR; |
| StringTokenizer frames = new StringTokenizer( stackTrace, linebreak ); |
| List<String> list = new LinkedList<String>(); |
| |
| while ( frames.hasMoreTokens() ) |
| { |
| list.add( frames.nextToken() ); |
| } |
| |
| return list.toArray( new String[list.size()] ); |
| } |
| |
| |
| /** |
| * <p> |
| * Produces a <code>List</code> of stack frames - the message is not |
| * included. |
| * </p> |
| * <p/> |
| * <p> |
| * This works in most cases - it will only fail if the exception message |
| * contains a line that starts with: |
| * <code>" at".</code> |
| * </p> |
| * |
| * @param t |
| * is any throwable |
| * @return List of stack frames |
| */ |
| static List<String> getStackFrameList( Throwable t ) |
| { |
| String stackTrace = getStackTrace( t ); |
| String linebreak = SystemUtils.LINE_SEPARATOR; |
| StringTokenizer frames = new StringTokenizer( stackTrace, linebreak ); |
| List<String> list = new LinkedList<String>(); |
| boolean traceStarted = false; |
| |
| while ( frames.hasMoreTokens() ) |
| { |
| String token = frames.nextToken(); |
| // Determine if the line starts with <whitespace>at |
| int at = token.indexOf( "at" ); |
| |
| if ( at != -1 && token.substring( 0, at ).trim().length() == 0 ) |
| { |
| traceStarted = true; |
| list.add( token ); |
| } |
| else if ( traceStarted ) |
| { |
| break; |
| } |
| } |
| return list; |
| } |
| |
| |
| public static String printErrors( List<Throwable> errors ) |
| { |
| StringBuilder sb = new StringBuilder(); |
| |
| for ( Throwable error:errors ) |
| { |
| sb.append( "Error : " ).append( error.getMessage() ).append( "\n" ); |
| } |
| |
| return sb.toString(); |
| } |
| } |