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 }