001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 *
019 */
020 package org.apache.directory.shared.ldap.util;
021
022
023 import java.io.PrintStream;
024 import java.io.PrintWriter;
025 import java.io.Serializable;
026 import java.io.StringWriter;
027 import java.util.ArrayList;
028 import java.util.Arrays;
029 import java.util.Collections;
030 import java.util.List;
031
032
033 /**
034 * <p>
035 * A shared implementation of the nestable exception functionality.
036 * </p>
037 * <p>
038 * The code is shared between
039 * </p>
040 *
041 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
042 */
043 public class NestableDelegate implements Serializable
044 {
045
046 static final long serialVersionUID = -4140246270875850555L;
047
048 /**
049 * Constructor error message.
050 */
051 private transient static final String MUST_BE_THROWABLE = "The Nestable implementation passed to the NestableDelegate(Nestable) "
052 + "constructor must extend java.lang.Throwable";
053
054 /**
055 * Holds the reference to the exception or error that we're wrapping (which
056 * must be a {@link org.apache.commons.lang.exception.Nestable}
057 * implementation).
058 */
059 private Throwable nestable = null;
060
061 /**
062 * Whether to print the stack trace top-down. This public flag may be set by
063 * calling code, typically in initialisation.
064 *
065 * @since 2.0
066 */
067 public static boolean topDown = true;
068
069 /**
070 * Whether to trim the repeated stack trace. This public flag may be set by
071 * calling code, typically in initialisation.
072 *
073 * @since 2.0
074 */
075 public static boolean trimStackFrames = true;
076
077
078 /**
079 * Constructs a new <code>NestableDelegate</code> instance to manage the
080 * specified <code>Nestable</code>.
081 *
082 * @param nestable
083 * the Nestable implementation (<i>must</i> extend
084 * {@link java.lang.Throwable})
085 * @since 2.0
086 */
087 public NestableDelegate(Nestable nestable)
088 {
089 if ( nestable instanceof Throwable )
090 {
091 this.nestable = ( Throwable ) nestable;
092 }
093 else
094 {
095 throw new IllegalArgumentException( MUST_BE_THROWABLE );
096 }
097 }
098
099
100 /**
101 * Returns the error message of the <code>Throwable</code> in the chain of
102 * <code>Throwable</code>s at the specified index, numbered from 0.
103 *
104 * @param index
105 * the index of the <code>Throwable</code> in the chain of
106 * <code>Throwable</code>s
107 * @return the error message, or null if the <code>Throwable</code> at the
108 * specified index in the chain does not contain a message
109 * @throws IndexOutOfBoundsException
110 * if the <code>index</code> argument is negative or not less
111 * than the count of <code>Throwable</code>s in the chain
112 * @since 2.0
113 */
114 public String getMessage( int index )
115 {
116 Throwable t = this.getThrowable( index );
117 if ( Nestable.class.isInstance( t ) )
118 {
119 return ( ( Nestable ) t ).getMessage( 0 );
120 }
121 else
122 {
123 return t.getMessage();
124 }
125 }
126
127
128 /**
129 * Returns the full message contained by the <code>Nestable</code> and any
130 * nested <code>Throwable</code>s.
131 *
132 * @param baseMsg
133 * the base message to use when creating the full message. Should
134 * be generally be called via
135 * <code>nestableHelper.getMessage(super.getMessage())</code>,
136 * where <code>super</code> is an instance of {@link
137 * java.lang.Throwable}.
138 * @return The concatenated message for this and all nested
139 * <code>Throwable</code>s
140 * @since 2.0
141 */
142 public String getMessage( String baseMsg )
143 {
144 StringBuffer msg = new StringBuffer();
145 if ( baseMsg != null )
146 {
147 msg.append( baseMsg );
148 }
149
150 Throwable nestedCause = ExceptionUtils.getCause( this.nestable );
151 if ( nestedCause != null )
152 {
153 String causeMsg = nestedCause.getMessage();
154 if ( causeMsg != null )
155 {
156 if ( baseMsg != null )
157 {
158 msg.append( ": " );
159 }
160 msg.append( causeMsg );
161 }
162
163 }
164 return ( msg.length() > 0 ? msg.toString() : null );
165 }
166
167
168 /**
169 * Returns the error message of this and any nested <code>Throwable</code>s
170 * in an array of Strings, one element for each message. Any
171 * <code>Throwable</code> not containing a message is represented in the
172 * array by a null. This has the effect of cause the length of the returned
173 * array to be equal to the result of the {@link #getThrowableCount()}
174 * operation.
175 *
176 * @return the error messages
177 * @since 2.0
178 */
179 public String[] getMessages()
180 {
181 Throwable[] throwables = this.getThrowables();
182 String[] msgs = new String[throwables.length];
183 for ( int i = 0; i < throwables.length; i++ )
184 {
185 msgs[i] = ( Nestable.class.isInstance( throwables[i] ) ? ( ( Nestable ) throwables[i] ).getMessage( 0 )
186 : throwables[i].getMessage() );
187 }
188 return msgs;
189 }
190
191
192 /**
193 * Returns the <code>Throwable</code> in the chain of
194 * <code>Throwable</code>s at the specified index, numbered from 0.
195 *
196 * @param index
197 * the index, numbered from 0, of the <code>Throwable</code> in
198 * the chain of <code>Throwable</code>s
199 * @return the <code>Throwable</code>
200 * @throws IndexOutOfBoundsException
201 * if the <code>index</code> argument is negative or not less
202 * than the count of <code>Throwable</code>s in the chain
203 * @since 2.0
204 */
205 public Throwable getThrowable( int index )
206 {
207 if ( index == 0 )
208 {
209 return this.nestable;
210 }
211 Throwable[] throwables = this.getThrowables();
212 return throwables[index];
213 }
214
215
216 /**
217 * Returns the number of <code>Throwable</code>s contained in the
218 * <code>Nestable</code> contained by this delegate.
219 *
220 * @return the throwable count
221 * @since 2.0
222 */
223 public int getThrowableCount()
224 {
225 return ExceptionUtils.getThrowableCount( this.nestable );
226 }
227
228
229 /**
230 * Returns this delegate's <code>Nestable</code> and any nested
231 * <code>Throwable</code>s in an array of <code>Throwable</code>s, one
232 * element for each <code>Throwable</code>.
233 *
234 * @return the <code>Throwable</code>s
235 * @since 2.0
236 */
237 public Throwable[] getThrowables()
238 {
239 return ExceptionUtils.getThrowables( this.nestable );
240 }
241
242
243 /**
244 * Returns the index, numbered from 0, of the first <code>Throwable</code>
245 * that matches the specified type in the chain of <code>Throwable</code>s
246 * held in this delegate's <code>Nestable</code> with an index greater
247 * than or equal to the specified index, or -1 if the type is not found.
248 *
249 * @param type
250 * <code>Class</code> to be found
251 * @param fromIndex
252 * the index, numbered from 0, of the starting position in the
253 * chain to be searched
254 * @return index of the first occurrence of the type in the chain, or -1 if
255 * the type is not found
256 * @throws IndexOutOfBoundsException
257 * if the <code>fromIndex</code> argument is negative or not
258 * less than the count of <code>Throwable</code>s in the
259 * chain
260 * @since 2.0
261 */
262 public int indexOfThrowable( Class<?> type, int fromIndex )
263 {
264 if ( fromIndex < 0 )
265 {
266 throw new IndexOutOfBoundsException( "The start index was out of bounds: " + fromIndex );
267 }
268
269 Throwable[] throwables = ExceptionUtils.getThrowables( this.nestable );
270
271 if ( fromIndex >= throwables.length )
272 {
273 throw new IndexOutOfBoundsException( "The start index was out of bounds: " + fromIndex + " >= "
274 + throwables.length );
275 }
276
277 for ( int i = fromIndex; i < throwables.length; i++ )
278 {
279 if ( throwables[i].getClass().equals( type ) )
280 {
281 return i;
282 }
283 }
284
285 return -1;
286 }
287
288
289 /**
290 * Prints the stack trace of this exception the the standar error stream.
291 */
292 public void printStackTrace()
293 {
294 printStackTrace( System.err );
295 }
296
297
298 /**
299 * Prints the stack trace of this exception to the specified stream.
300 *
301 * @param out
302 * <code>PrintStream</code> to use for output.
303 * @see #printStackTrace(PrintWriter)
304 */
305 public void printStackTrace( PrintStream out )
306 {
307 synchronized ( out )
308 {
309 PrintWriter pw = new PrintWriter( out, false );
310 printStackTrace( pw );
311 // Flush the PrintWriter before it's GC'ed.
312 pw.flush();
313 }
314 }
315
316
317 /**
318 * Prints the stack trace of this exception to the specified writer. If the
319 * Throwable class has a <code>getCause</code> method (i.e. running on
320 * jre1.4 or higher), this method just uses Throwable's printStackTrace()
321 * method. Otherwise, generates the stack-trace, by taking into account the
322 * 'topDown' and 'trimStackFrames' parameters. The topDown and
323 * trimStackFrames are set to 'true' by default (produces jre1.4-like stack
324 * trace).
325 *
326 * @param out
327 * <code>PrintWriter</code> to use for output.
328 */
329 public void printStackTrace( PrintWriter out )
330 {
331 Throwable throwable = this.nestable;
332 // if running on jre1.4 or higher, use default printStackTrace
333 if ( ExceptionUtils.isThrowableNested() )
334 {
335 if ( throwable instanceof Nestable )
336 {
337 ( ( Nestable ) throwable ).printPartialStackTrace( out );
338 }
339 else
340 {
341 throwable.printStackTrace( out );
342 }
343 return;
344 }
345
346 // generating the nested stack trace
347 List<String[]> stacks = new ArrayList<String[]>();
348 while ( throwable != null )
349 {
350 String[] st = getStackFrames( throwable );
351 stacks.add( st );
352 throwable = ExceptionUtils.getCause( throwable );
353 }
354
355 // If NOT topDown, reverse the stack
356 String separatorLine = "Caused by: ";
357 if ( !topDown )
358 {
359 separatorLine = "Rethrown as: ";
360 Collections.reverse( stacks );
361 }
362
363 // Remove the repeated lines in the stack
364 if ( trimStackFrames )
365 {
366 trimStackFrames( stacks );
367 }
368
369 synchronized ( out )
370 {
371 boolean isFirst = true;
372
373 for ( String[] st:stacks )
374 {
375 if ( isFirst )
376 {
377 isFirst = false;
378 }
379 else
380 {
381 out.print( separatorLine );
382 }
383
384 for ( String s:st )
385 {
386 out.println( s );
387 }
388 }
389 }
390 }
391
392
393 /**
394 * Captures the stack trace associated with the specified
395 * <code>Throwable</code> object, decomposing it into a list of stack
396 * frames.
397 *
398 * @param t
399 * The <code>Throwable</code>.
400 * @return An array of strings describing each stack frame.
401 * @since 2.0
402 */
403 protected String[] getStackFrames( Throwable t )
404 {
405 StringWriter sw = new StringWriter();
406 PrintWriter pw = new PrintWriter( sw, true );
407
408 // Avoid infinite loop between decompose() and printStackTrace().
409 if ( t instanceof Nestable )
410 {
411 ( ( Nestable ) t ).printPartialStackTrace( pw );
412 }
413 else
414 {
415 t.printStackTrace( pw );
416 }
417 return ExceptionUtils.getStackFrames( sw.getBuffer().toString() );
418 }
419
420
421 /**
422 * Trims the stack frames. The first set is left untouched. The rest of the
423 * frames are truncated from the bottom by comparing with one just on top.
424 *
425 * @param stacks
426 * The list containing String[] elements
427 * @since 2.0
428 */
429 protected void trimStackFrames( List<String[]> stacks )
430 {
431 for ( int size = stacks.size(), i = size - 1; i > 0; i-- )
432 {
433 String[] curr = stacks.get( i );
434 String[] next = stacks.get( i - 1 );
435
436 List<String> currList = new ArrayList<String>( Arrays.asList( curr ) );
437 List<String> nextList = new ArrayList<String>( Arrays.asList( next ) );
438 ExceptionUtils.removeCommonFrames( currList, nextList );
439
440 int trimmed = curr.length - currList.size();
441
442 if ( trimmed > 0 )
443 {
444 currList.add( "\t... " + trimmed + " more" );
445 stacks.set( i, currList.toArray( new String[currList.size()] ) );
446 }
447 }
448 }
449 }