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 import org.apache.directory.shared.i18n.I18n;
033
034
035 /**
036 * <p>
037 * A shared implementation of the nestable exception functionality.
038 * </p>
039 * <p>
040 * The code is shared between
041 * </p>
042 *
043 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
044 */
045 public class NestableDelegate implements Serializable
046 {
047
048 static final long serialVersionUID = -4140246270875850555L;
049
050 /**
051 * Constructor error message.
052 */
053 private transient static final String MUST_BE_THROWABLE = I18n.err( I18n.ERR_04419 );
054
055 /**
056 * Holds the reference to the exception or error that we're wrapping (which
057 * must be a {@link org.apache.commons.lang.exception.Nestable}
058 * implementation).
059 */
060 private Throwable nestable = null;
061
062 /**
063 * Whether to print the stack trace top-down. This public flag may be set by
064 * calling code, typically in initialisation.
065 *
066 * @since 2.0
067 */
068 public static boolean topDown = true;
069
070 /**
071 * Whether to trim the repeated stack trace. This public flag may be set by
072 * calling code, typically in initialisation.
073 *
074 * @since 2.0
075 */
076 public static boolean trimStackFrames = true;
077
078
079 /**
080 * Constructs a new <code>NestableDelegate</code> instance to manage the
081 * specified <code>Nestable</code>.
082 *
083 * @param nestable
084 * the Nestable implementation (<i>must</i> extend
085 * {@link java.lang.Throwable})
086 * @since 2.0
087 */
088 public NestableDelegate(Nestable nestable)
089 {
090 if ( nestable instanceof Throwable )
091 {
092 this.nestable = ( Throwable ) nestable;
093 }
094 else
095 {
096 throw new IllegalArgumentException( MUST_BE_THROWABLE );
097 }
098 }
099
100
101 /**
102 * Returns the error message of the <code>Throwable</code> in the chain of
103 * <code>Throwable</code>s at the specified index, numbered from 0.
104 *
105 * @param index
106 * the index of the <code>Throwable</code> in the chain of
107 * <code>Throwable</code>s
108 * @return the error message, or null if the <code>Throwable</code> at the
109 * specified index in the chain does not contain a message
110 * @throws IndexOutOfBoundsException
111 * if the <code>index</code> argument is negative or not less
112 * than the count of <code>Throwable</code>s in the chain
113 * @since 2.0
114 */
115 public String getMessage( int index )
116 {
117 Throwable t = this.getThrowable( index );
118 if ( Nestable.class.isInstance( t ) )
119 {
120 return ( ( Nestable ) t ).getMessage( 0 );
121 }
122 else
123 {
124 return t.getMessage();
125 }
126 }
127
128
129 /**
130 * Returns the full message contained by the <code>Nestable</code> and any
131 * nested <code>Throwable</code>s.
132 *
133 * @param baseMsg
134 * the base message to use when creating the full message. Should
135 * be generally be called via
136 * <code>nestableHelper.getMessage(super.getMessage())</code>,
137 * where <code>super</code> is an instance of {@link
138 * java.lang.Throwable}.
139 * @return The concatenated message for this and all nested
140 * <code>Throwable</code>s
141 * @since 2.0
142 */
143 public String getMessage( String baseMsg )
144 {
145 StringBuffer msg = new StringBuffer();
146 if ( baseMsg != null )
147 {
148 msg.append( baseMsg );
149 }
150
151 Throwable nestedCause = ExceptionUtils.getCause( this.nestable );
152 if ( nestedCause != null )
153 {
154 String causeMsg = nestedCause.getMessage();
155 if ( causeMsg != null )
156 {
157 if ( baseMsg != null )
158 {
159 msg.append( ": " );
160 }
161 msg.append( causeMsg );
162 }
163
164 }
165 return ( msg.length() > 0 ? msg.toString() : null );
166 }
167
168
169 /**
170 * Returns the error message of this and any nested <code>Throwable</code>s
171 * in an array of Strings, one element for each message. Any
172 * <code>Throwable</code> not containing a message is represented in the
173 * array by a null. This has the effect of cause the length of the returned
174 * array to be equal to the result of the {@link #getThrowableCount()}
175 * operation.
176 *
177 * @return the error messages
178 * @since 2.0
179 */
180 public String[] getMessages()
181 {
182 Throwable[] throwables = this.getThrowables();
183 String[] msgs = new String[throwables.length];
184 for ( int i = 0; i < throwables.length; i++ )
185 {
186 msgs[i] = ( Nestable.class.isInstance( throwables[i] ) ? ( ( Nestable ) throwables[i] ).getMessage( 0 )
187 : throwables[i].getMessage() );
188 }
189 return msgs;
190 }
191
192
193 /**
194 * Returns the <code>Throwable</code> in the chain of
195 * <code>Throwable</code>s at the specified index, numbered from 0.
196 *
197 * @param index
198 * the index, numbered from 0, of the <code>Throwable</code> in
199 * the chain of <code>Throwable</code>s
200 * @return the <code>Throwable</code>
201 * @throws IndexOutOfBoundsException
202 * if the <code>index</code> argument is negative or not less
203 * than the count of <code>Throwable</code>s in the chain
204 * @since 2.0
205 */
206 public Throwable getThrowable( int index )
207 {
208 if ( index == 0 )
209 {
210 return this.nestable;
211 }
212 Throwable[] throwables = this.getThrowables();
213 return throwables[index];
214 }
215
216
217 /**
218 * Returns the number of <code>Throwable</code>s contained in the
219 * <code>Nestable</code> contained by this delegate.
220 *
221 * @return the throwable count
222 * @since 2.0
223 */
224 public int getThrowableCount()
225 {
226 return ExceptionUtils.getThrowableCount( this.nestable );
227 }
228
229
230 /**
231 * Returns this delegate's <code>Nestable</code> and any nested
232 * <code>Throwable</code>s in an array of <code>Throwable</code>s, one
233 * element for each <code>Throwable</code>.
234 *
235 * @return the <code>Throwable</code>s
236 * @since 2.0
237 */
238 public Throwable[] getThrowables()
239 {
240 return ExceptionUtils.getThrowables( this.nestable );
241 }
242
243
244 /**
245 * Returns the index, numbered from 0, of the first <code>Throwable</code>
246 * that matches the specified type in the chain of <code>Throwable</code>s
247 * held in this delegate's <code>Nestable</code> with an index greater
248 * than or equal to the specified index, or -1 if the type is not found.
249 *
250 * @param type
251 * <code>Class</code> to be found
252 * @param fromIndex
253 * the index, numbered from 0, of the starting position in the
254 * chain to be searched
255 * @return index of the first occurrence of the type in the chain, or -1 if
256 * the type is not found
257 * @throws IndexOutOfBoundsException
258 * if the <code>fromIndex</code> argument is negative or not
259 * less than the count of <code>Throwable</code>s in the
260 * chain
261 * @since 2.0
262 */
263 public int indexOfThrowable( Class<?> type, int fromIndex )
264 {
265 if ( fromIndex < 0 )
266 {
267 throw new IndexOutOfBoundsException( I18n.err( I18n.ERR_04420, fromIndex ) );
268 }
269
270 Throwable[] throwables = ExceptionUtils.getThrowables( this.nestable );
271
272 if ( fromIndex >= throwables.length )
273 {
274 throw new IndexOutOfBoundsException( I18n.err( I18n.ERR_04421, fromIndex, 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 }