001    /*
002     * Copyright 2004 Stephen J. McConnell.
003     *
004     * Licensed  under the  Apache License,  Version 2.0  (the "License");
005     * you may not use  this file  except in  compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *   http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed  under the  License is distributed on an "AS IS" BASIS,
012     * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
013     * implied.
014     *
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package net.dpml.util;
020    
021    import java.io.PrintWriter;
022    import java.io.StringWriter;
023    import java.lang.reflect.Method;
024    import java.util.StringTokenizer;
025    
026    /**
027     * General utilities supporting the packaging of exception messages.
028     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
029     * @version 1.0.3
030     */
031    public final class ExceptionHelper
032    {
033        // ------------------------------------------------------------------------
034        // static
035        // ------------------------------------------------------------------------
036    
037        /**
038         * Returns the exception and causal exceptions as a formatted string.
039         * @param e the exception
040         * @return String the formatting string
041         */
042        public static String packException( final Throwable e )
043        {
044            return packException( null, e );
045        }
046    
047        /**
048         * Returns the exception and causal exceptions as a formatted string.
049         * @param e the exception
050         * @param stack TRUE to generate a stack trace
051         * @return String the formatting string
052         */
053        public static String packException( final Throwable e, boolean stack )
054        {
055            return packException( null, e, stack );
056        }
057    
058    
059        /**
060         * Returns the exception and causal exceptions as a formatted string.
061         * @param message the header message
062         * @param e the exception
063         * @return String the formatting string
064         */
065        public static String packException( final String message, final Throwable e )
066        {
067            return packException( message, e, false );
068        }
069    
070        /**
071         * Returns the exception and causal exceptions as a formatted string.
072         * @param message the header message
073         * @param e the exception
074         * @param stack TRUE to generate a stack trace
075         * @return String the formatting string
076         */
077        public static String packException(
078           final String message, final Throwable e, boolean stack )
079        {
080            StringBuffer buffer = new StringBuffer();
081            packException( buffer, 0, message, e, stack );
082            buffer.append( getLine( END ) );
083            return buffer.toString();
084        }
085    
086    
087        /**
088         * Returns the exception and causal exceptions as a formatted string.
089         * @param message the header message
090         * @param e the exceptions
091         * @param stack TRUE to generate a stack trace
092         * @return String the formatting string
093         */
094        public static String packException(
095           final String message, final Throwable[] e, boolean stack )
096        {
097            final String lead = COMPOSITE + "(" + e.length + " entries) ";
098            StringBuffer buffer = new StringBuffer( getLine( lead ) );
099            if( null != message )
100            {
101                buffer.append( message );
102                buffer.append( "\n" );
103            }
104            for( int i=0; i < e.length; i++ )
105            {
106                packException( buffer, i + 1, null, e[i], stack );
107            }
108            buffer.append( getLine( END ) );
109            return buffer.toString();
110        }
111    
112        // ------------------------------------------------------------------------
113        // static implementation
114        // ------------------------------------------------------------------------
115    
116       /**
117        * Line separator character.
118        */
119        private static final String LINE_SEPARATOR =
120          System.getProperty( "line.separator" );
121    
122       /**
123        * Header token.
124        */
125        private static final String HEADER = "----";
126    
127       /**
128        * Exception token.
129        */
130        private static final String EXCEPTION = HEADER + " exception report ";
131    
132       /**
133        * Composite token.
134        */
135        private static final String COMPOSITE = HEADER + " composite report ";
136    
137       /**
138        * Runtime token.
139        */
140        private static final String RUNTIME = HEADER + " runtime exception report ";
141    
142       /**
143        * Error token.
144        */
145        private static final String ERROR = HEADER + " error report ";
146    
147        /**
148        * Cause token.
149        */
150        private static final String CAUSE = HEADER + " cause ";
151    
152       /**
153        * Trace token.
154        */
155        private static final String TRACE = HEADER + " stack trace ";
156    
157       /**
158        * End token.
159        */
160        private static final String END = "";
161    
162       /**
163        * Nominal width of character display.
164        */
165        private static final int WIDTH = 80;
166    
167        /**
168         * Returns the exception and causal exceptions as a formatted string.
169         * @param buffer the string buffer
170         * @param j the causal message sequence
171         * @param message the header message
172         * @param e the exception
173         * @param stack TRUE to generate a stack trace
174         */
175        private static void packException(
176           final StringBuffer buffer, int j, final String message, final Throwable e, boolean stack )
177        {
178            if( e instanceof Error )
179            {
180                buffer.append( getLine( ERROR, j ) );
181            }
182            else if( e instanceof RuntimeException )
183            {
184                buffer.append( getLine( RUNTIME, j ) );
185            }
186            else
187            {
188                buffer.append( getLine( EXCEPTION, j ) );
189            }
190    
191            if( null != message )
192            {
193                buffer.append( message );
194                buffer.append( "\n" );
195            }
196            
197            if( e == null )
198            {
199                return;
200            }
201    
202            buffer.append( "Exception: " + e.getClass().getName() + "\n" );
203            if( null != e.getMessage() )
204            {
205                buffer.append( "Message: " + e.getMessage() + "\n" );
206            }
207            packCause( buffer, getCause( e ) ).toString();
208            Throwable root = getLastThrowable( e );
209            if( ( root != null ) && stack )
210            {
211                buffer.append( getLine( TRACE ) );
212                String[] trace = captureStackTrace( root );
213                for( int i = 0; i < trace.length; i++ )
214                {
215                    buffer.append( trace[i] + "\n" );
216                }
217            }
218        }
219    
220       /**
221        * Pack a causal exception.
222        * @param buffer the buffer to pack the exception report
223        * @param cause the causal exception to pack
224        * @return the buffer
225        */
226        private static StringBuffer packCause( StringBuffer buffer, Throwable cause )
227        {
228            if( cause == null )
229            {
230                return buffer;
231            }
232            buffer.append( getLine( CAUSE ) );
233            buffer.append( "Exception: " + cause.getClass().getName() + "\n" );
234            buffer.append( "Message: " + cause.getMessage() + "\n" );
235            return packCause( buffer, getCause( cause ) );
236        }
237    
238       /**
239        * Return the last throwable in the chain.
240        * @param exception the exception to extract the last throwable from
241        * @return the initiating cause
242        */
243        private static Throwable getLastThrowable( Throwable exception )
244        {
245            Throwable cause = getCause( exception );
246            if( cause != null )
247            {
248                return getLastThrowable( cause );
249            }
250            else
251            {
252                return exception;
253            }
254        }
255    
256       /**
257        * Get a causal exception using reflection.
258        * @param exception the exception
259        * @return the causal exception
260        */
261        private static Throwable getCause( Throwable exception )
262        {
263            if( null == exception )
264            {
265                return null;
266            }
267    
268            try
269            {
270                Class clazz = exception.getClass();
271                Method method = clazz.getMethod( "getCause", new Class[0] );
272                return (Throwable) method.invoke( exception, new Object[0] );
273            }
274            catch( Throwable e )
275            {
276                return null;
277            }
278        }
279    
280        /**
281         * Captures the stack trace associated with this exception.
282         *
283         * @param throwable a <code>Throwable</code>
284         * @return an array of Strings describing stack frames.
285         */
286        private static String[] captureStackTrace( final Throwable throwable )
287        {
288            final StringWriter sw = new StringWriter();
289            throwable.printStackTrace( new PrintWriter( sw, true ) );
290            return splitString( sw.toString(), LINE_SEPARATOR );
291        }
292    
293        /**
294         * Splits the string on every token into an array of stack frames.
295         *
296         * @param string the string to split
297         * @param onToken the token to split on
298         * @return the resultant array
299         */
300        private static String[] splitString( final String string, final String onToken )
301        {
302            final int offset = 4;
303            final StringTokenizer tokenizer = new StringTokenizer( string, onToken );
304            final String[] result = new String[tokenizer.countTokens()];
305    
306            for( int i = 0; i < result.length; i++ )
307            {
308                String token = tokenizer.nextToken();
309                if( token.startsWith( "\tat " ) )
310                {
311                    result[i] = token.substring( offset );
312                }
313                else
314                {
315                    result[i] = token;
316                }
317            }
318    
319            return result;
320        }
321    
322       /**
323        * Return a line of '-' characters.
324        * @param lead the lead
325        * @return the line
326        */
327        private static String getLine( String lead )
328        {
329            return getLine( lead, 0 );
330        }
331    
332       /**
333        * Get a line of '-' characters with padded offset.
334        * @param lead the leading characters
335        * @param count the number of characters to fill
336        * @return the filled out line
337        */
338        private static String getLine( String lead, int count )
339        {
340            StringBuffer buffer = new StringBuffer( lead );
341            int q = 0;
342            if( count  > 0 )
343            {
344                String v = "" + count + " ";
345                buffer.append( "" + count );
346                buffer.append( " " );
347                q = v.length() + 1;
348            }
349            int j = WIDTH - ( lead.length() + q );
350            for( int i=0; i < j; i++ )
351            {
352                buffer.append( "-" );
353            }
354            buffer.append( "\n" );
355            return buffer.toString();
356        }
357    
358       /**
359        * Disabled.
360        */
361        private ExceptionHelper()
362        {
363            // disable
364        }
365    }