001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.commons.utils; 018 019import static org.apache.juneau.commons.utils.Utils.*; 020 021import java.io.*; 022import java.util.*; 023 024import org.apache.juneau.commons.reflect.*; 025import org.apache.juneau.commons.settings.*; 026 027/** 028 * Various utility methods for creating and working with throwables. 029 */ 030public class ThrowableUtils { 031 032 static Setting<Boolean> VERBOSE = Settings.get().get("juneau.enableVerboseExceptions").asBoolean(); 033 034 /** 035 * Interface used with {@link Utils#safeSupplier(SupplierWithThrowable)}. 036 * 037 * @param <T> The supplier type. 038 */ 039 @FunctionalInterface 040 public interface SupplierWithThrowable<T> { 041 042 /** 043 * Gets a result. 044 * 045 * @return a result 046 * @throws Throwable if supplier threw an exception. 047 */ 048 T get() throws Throwable; 049 } 050 051 /** 052 * Shortcut for creating a {@link BeanRuntimeException} with a message and associated class. 053 * 054 * @param c The class associated with the exception. 055 * @param msg The message. 056 * @param args Optional {@link String#format(String, Object...)} arguments. 057 * @return A new {@link BeanRuntimeException}. 058 */ 059 public static BeanRuntimeException bex(Class<?> c, String msg, Object...args) { 060 return log(new BeanRuntimeException(c, msg, args)); 061 } 062 063 /** 064 * Shortcut for creating a {@link BeanRuntimeException} with a message and associated class. 065 * 066 * <p> 067 * Same as the {@code bex(Class, String, Object...)} method but accepts a {@link ClassInfo} instead of a {@link Class}. 068 * 069 * @param c The class info associated with the exception. 070 * @param msg The message. 071 * @param args Optional {@link String#format(String, Object...)} arguments. 072 * @return A new {@link BeanRuntimeException}. 073 */ 074 public static BeanRuntimeException bex(ClassInfo c, String msg, Object...args) { 075 return log(new BeanRuntimeException(c.inner(), msg, args)); 076 } 077 078 /** 079 * Creates a {@link RuntimeException}. 080 * 081 * @param msg The exception message. 082 * @param args The arguments to substitute into the message. 083 * @return A new RuntimeException with the formatted message. 084 */ 085 public static BeanRuntimeException bex(String msg, Object...args) { 086 return log(new BeanRuntimeException(msg, args)); 087 } 088 089 /** 090 * Creates a {@link RuntimeException} wrapping the given throwable. 091 * 092 * @param cause The cause of the exception. 093 * @return A new RuntimeException wrapping the given cause. 094 */ 095 public static BeanRuntimeException bex(Throwable cause) { 096 return log(new BeanRuntimeException(cause)); 097 } 098 099 /** 100 * Shortcut for creating a {@link BeanRuntimeException} with a cause, message, and associated class. 101 * 102 * @param e The cause of the exception. 103 * @param c The class associated with the exception. 104 * @param msg The message. 105 * @param args Optional {@link String#format(String, Object...)} arguments. 106 * @return A new {@link BeanRuntimeException}. 107 */ 108 public static BeanRuntimeException bex(Throwable e, Class<?> c, String msg, Object...args) { 109 return log(new BeanRuntimeException(e, c, msg, args)); 110 } 111 112 /** 113 * Shortcut for creating a {@link BeanRuntimeException} with a cause, message, and associated class. 114 * 115 * <p> 116 * Same as the {@code bex(Throwable, Class, String, Object...)} method but accepts a {@link ClassInfo} instead of a {@link Class}. 117 * 118 * @param e The cause of the exception. 119 * @param c The class info associated with the exception. 120 * @param msg The message. 121 * @param args Optional {@link String#format(String, Object...)} arguments. 122 * @return A new {@link BeanRuntimeException}. 123 */ 124 public static BeanRuntimeException bex(Throwable e, ClassInfo c, String msg, Object...args) { 125 return log(new BeanRuntimeException(e, c.inner(), msg, args)); 126 } 127 128 /** 129 * Creates a {@link RuntimeException} with a cause. 130 * 131 * @param cause The cause of the exception. 132 * @param msg The exception message. 133 * @param args The arguments to substitute into the message. 134 * @return A new RuntimeException with the formatted message and cause. 135 */ 136 public static BeanRuntimeException bex(Throwable cause, String msg, Object...args) { 137 return log(new BeanRuntimeException(f(msg, args), cause)); 138 } 139 140 /** 141 * Shortcut for creating an {@link ExecutableException} with a message. 142 * 143 * @param msg The message. 144 * @param args Optional {@link String#format(String, Object...)} arguments. 145 * @return A new {@link ExecutableException}. 146 */ 147 public static ExecutableException exex(String msg, Object...args) { 148 return log(new ExecutableException(msg, args)); 149 } 150 151 /** 152 * Creates an {@link ExecutableException} wrapping the given throwable. 153 * 154 * @param cause The cause of the exception. 155 * @return A new ExecutableException wrapping the given cause. 156 */ 157 public static ExecutableException exex(Throwable cause) { 158 return log(new ExecutableException(cause)); 159 } 160 161 /** 162 * Creates an {@link ExecutableException} with a cause. 163 * 164 * @param cause The cause of the exception. 165 * @param msg The exception message. 166 * @param args The arguments to substitute into the message. 167 * @return A new ExecutableException with the formatted message and cause. 168 */ 169 public static ExecutableException exex(Throwable cause, String msg, Object...args) { 170 return log(new ExecutableException(cause, msg, args)); 171 } 172 173 /** 174 * Casts or wraps the specified throwable to the specified type. 175 * 176 * @param <T> The class to cast to. 177 * @param type The class to cast to. 178 * @param t The throwable to cast. 179 * @return Either the same exception if it's already the specified type, or a wrapped exception. 180 */ 181 public static <T> T castException(Class<T> type, Throwable t) { 182 return safeSupplier(() -> { 183 return type.isInstance(t) ? type.cast(t) : type.getConstructor(Throwable.class).newInstance(t); 184 }); 185 } 186 187 /** 188 * Searches through the cause chain of an exception to find an exception of the specified type. 189 * 190 * @param <T> The cause type. 191 * @param e The exception to search. 192 * @param cause The cause type to search for. 193 * @return An {@link Optional} containing the cause if found, or empty if not found. 194 */ 195 public static <T extends Throwable> Optional<T> findCause(Throwable e, Class<T> cause) { 196 while (nn(e)) { 197 if (cause.isInstance(e)) 198 return opt(cause.cast(e)); 199 e = e.getCause(); 200 } 201 return opte(); 202 } 203 204 /** 205 * Convenience method for getting a stack trace as a string. 206 * 207 * @param t The throwable to get the stack trace from. 208 * @return The same content that would normally be rendered via <c>t.printStackTrace()</c> 209 */ 210 public static String getStackTrace(Throwable t) { 211 var sw = new StringWriter(); 212 try (var pw = new PrintWriter(sw)) { 213 t.printStackTrace(pw); 214 } 215 return sw.toString(); 216 } 217 218 /** 219 * Prints a stack trace with a maximum depth limit. 220 * 221 * <p> 222 * This method is useful for {@link StackOverflowError} situations where printing the full stack trace 223 * can cause additional errors due to stack exhaustion. The stack trace will be limited to the specified 224 * maximum number of elements. 225 * 226 * <h5 class='section'>Example:</h5> 227 * <p class='bjava'> 228 * <jk>try</jk> { 229 * <jc>// Some code that might cause StackOverflowError</jc> 230 * } <jk>catch</jk> (StackOverflowError <jv>e</jv>) { 231 * <jc>// Print only first 100 stack trace elements</jc> 232 * ThrowableUtils.<jsm>printStackTrace</jsm>(<jv>e</jv>, System.err, 100); 233 * } 234 * </p> 235 * 236 * @param t The throwable to print the stack trace for. 237 * @param pw The print writer to write to. 238 * @param maxDepth The maximum number of stack trace elements to print. If <jk>null</jk> or negative, prints all elements. 239 */ 240 public static void printStackTrace(Throwable t, PrintWriter pw, Integer maxDepth) { 241 try { 242 pw.println(t); 243 var stackTrace = t.getStackTrace(); 244 var depth = maxDepth != null && maxDepth > 0 ? Math.min(maxDepth, stackTrace.length) : stackTrace.length; 245 for (var i = 0; i < depth; i++) { 246 pw.println("\tat " + stackTrace[i]); 247 } 248 if (maxDepth != null && maxDepth > 0 && stackTrace.length > maxDepth) { 249 pw.println("\t... (" + (stackTrace.length - maxDepth) + " more)"); 250 } 251 var cause = t.getCause(); 252 if (cause != null && cause != t) { 253 pw.print("Caused by: "); 254 printStackTrace(cause, pw, maxDepth); 255 } 256 } catch (Exception e) { 257 pw.println("Error printing stack trace: " + e.getMessage()); 258 } 259 } 260 261 /** 262 * Prints a stack trace with a maximum depth limit to standard error. 263 * 264 * <p> 265 * This method is useful for {@link StackOverflowError} situations where printing the full stack trace 266 * can cause additional errors due to stack exhaustion. The stack trace will be limited to the specified 267 * maximum number of elements. 268 * 269 * <p> 270 * Same as {@link #printStackTrace(Throwable, PrintWriter, Integer)} but writes to {@link System#err}. 271 * 272 * <h5 class='section'>Example:</h5> 273 * <p class='bjava'> 274 * <jk>try</jk> { 275 * <jc>// Some code that might cause StackOverflowError</jc> 276 * } <jk>catch</jk> (StackOverflowError <jv>e</jv>) { 277 * <jc>// Print only first 100 stack trace elements to stderr</jc> 278 * ThrowableUtils.<jsm>printStackTrace</jsm>(<jv>e</jv>, 100); 279 * } 280 * </p> 281 * 282 * @param t The throwable to print the stack trace for. 283 * @param maxDepth The maximum number of stack trace elements to print. If <jk>null</jk> or negative, prints all elements. 284 */ 285 public static void printStackTrace(Throwable t, Integer maxDepth) { 286 printStackTrace(t, new PrintWriter(System.err, true), maxDepth); 287 } 288 289 /** 290 * Same as {@link Throwable#getCause()} but searches the throwable chain for an exception of the specified type. 291 * 292 * @param c The throwable type to search for. 293 * @param <T> The throwable type to search for. 294 * @param t The throwable to search. 295 * @return The exception, or <jk>null</jk> if not found. 296 */ 297 public static <T extends Throwable> T getThrowableCause(Class<T> c, Throwable t) { 298 while (nn(t)) { 299 t = t.getCause(); 300 if (c.isInstance(t)) 301 return c.cast(t); 302 } 303 return null; 304 } 305 306 /** 307 * Calculates a 16-bit hash for the specified throwable based on it's stack trace. 308 * 309 * @param t The throwable to calculate the stack trace on. 310 * @param stopClass Optional stop class on which to stop calculation of a stack trace beyond when found. 311 * @return A calculated hash. 312 */ 313 public static int hash(Throwable t, String stopClass) { 314 var i = 0; 315 while (nn(t)) { 316 for (var e : t.getStackTrace()) { 317 if (e.getClassName().equals(stopClass)) 318 break; 319 if (e.getClassName().indexOf('$') == -1) 320 i ^= e.hashCode(); 321 } 322 t = t.getCause(); 323 } 324 return i; 325 } 326 327 /** 328 * Creates an {@link IllegalArgumentException}. 329 * 330 * @param msg The exception message. 331 * @param args The arguments to substitute into the message. 332 * @return A new IllegalArgumentException with the formatted message. 333 */ 334 public static IllegalArgumentException illegalArg(String msg, Object...args) { 335 return log(new IllegalArgumentException(f(msg, args))); 336 } 337 338 private static <T extends Throwable> T log(T exception) { 339 if (VERBOSE.orElse(false)) exception.printStackTrace(); 340 return exception; 341 } 342 343 /** 344 * Creates an {@link IllegalStateException} with a formatted message. 345 * 346 * <p> 347 * This is a convenience method for creating state exceptions with formatted messages. 348 * The message is formatted using {@link Utils#f(String, Object...)}. 349 * 350 * <h5 class='section'>Example:</h5> 351 * <p class='bjava'> 352 * <jk>throw</jk> <jsm>illegalState</jsm>(<js>"Invalid state: {0}"</js>, <jv>state</jv>); 353 * </p> 354 * 355 * @param msg The message format string. 356 * @param args The arguments for the message format string. 357 * @return A new IllegalStateException with the formatted message. 358 * @see Utils#f(String, Object...) 359 * @see #illegalArg(String, Object...) 360 */ 361 public static IllegalStateException illegalState(String msg, Object...args) { 362 return log(new IllegalStateException(f(msg, args))); 363 } 364 365 366 /** 367 * Creates an {@link IllegalArgumentException} wrapping the given throwable. 368 * 369 * @param cause The cause of the exception. 370 * @return A new IllegalArgumentException wrapping the given cause. 371 */ 372 public static IllegalArgumentException illegalArg(Throwable cause) { 373 return log(new IllegalArgumentException(cause)); 374 } 375 376 /** 377 * Creates an {@link IllegalArgumentException} with a cause. 378 * 379 * @param cause The cause of the exception. 380 * @param msg The exception message. 381 * @param args The arguments to substitute into the message. 382 * @return A new IllegalArgumentException with the formatted message and cause. 383 */ 384 public static IllegalArgumentException illegalArg(Throwable cause, String msg, Object...args) { 385 return log(new IllegalArgumentException(f(msg, args), cause)); 386 } 387 388 /** 389 * Creates an {@link java.io.IOException}. 390 * 391 * @param msg The exception message. 392 * @param args The arguments to substitute into the message. 393 * @return A new IOException with the formatted message. 394 */ 395 public static java.io.IOException ioex(String msg, Object...args) { 396 return log(new java.io.IOException(f(msg, args))); 397 } 398 399 /** 400 * Creates an {@link java.io.IOException} wrapping the given throwable. 401 * 402 * @param cause The cause of the exception. 403 * @return A new IOException wrapping the given cause. 404 */ 405 public static java.io.IOException ioex(Throwable cause) { 406 return log(new java.io.IOException(cause)); 407 } 408 409 /** 410 * Creates an {@link java.io.IOException} with a cause. 411 * 412 * @param cause The cause of the exception. 413 * @param msg The exception message. 414 * @param args The arguments to substitute into the message. 415 * @return A new IOException with the formatted message and cause. 416 */ 417 public static java.io.IOException ioex(Throwable cause, String msg, Object...args) { 418 return log(new java.io.IOException(f(msg, args), cause)); 419 } 420 421 /** 422 * Convenience method for calling {@link Throwable#getLocalizedMessage()}. 423 * 424 * @param t The throwable. 425 * @return The localized message of the throwable. 426 */ 427 public static String lm(Throwable t) { 428 return t.getLocalizedMessage(); 429 } 430 431 /** 432 * Creates a {@link RuntimeException}. 433 * 434 * @param msg The exception message. 435 * @param args The arguments to substitute into the message. 436 * @return A new RuntimeException with the formatted message. 437 */ 438 public static RuntimeException rex(String msg, Object...args) { 439 return log(new RuntimeException(f(msg, args))); 440 } 441 442 /** 443 * Creates a {@link RuntimeException} wrapping the given throwable. 444 * 445 * @param cause The cause of the exception. 446 * @return A new RuntimeException wrapping the given cause. 447 */ 448 public static RuntimeException rex(Throwable cause) { 449 return log(new RuntimeException(cause)); 450 } 451 452 /** 453 * Creates a {@link RuntimeException} with a cause. 454 * 455 * @param cause The cause of the exception. 456 * @param msg The exception message. 457 * @param args The arguments to substitute into the message. 458 * @return A new RuntimeException with the formatted message and cause. 459 */ 460 public static RuntimeException rex(Throwable cause, String msg, Object...args) { 461 return log(new RuntimeException(f(msg, args), cause)); 462 } 463 464 /** 465 * Creates a new {@link RuntimeException}. 466 * 467 * @param cause The caused-by exception. 468 * @return A new {@link RuntimeException}, or the same exception if it's already of that type. 469 */ 470 public static RuntimeException toRex(Throwable cause) { 471 return castException(RuntimeException.class, cause); 472 } 473 474 /** 475 * Creates an {@link UnsupportedOperationException} with a default message. 476 * 477 * @return A new UnsupportedOperationException with the message "Not supported." 478 */ 479 public static UnsupportedOperationException unsupportedOp() { 480 return log(new UnsupportedOperationException("Not supported.")); 481 } 482 483 /** 484 * Creates an {@link UnsupportedOperationException}. 485 * 486 * @param msg The exception message. 487 * @param args The arguments to substitute into the message. 488 * @return A new UnsupportedOperationException with the formatted message. 489 */ 490 public static UnsupportedOperationException unsupportedOp(String msg, Object...args) { 491 return log(new UnsupportedOperationException(f(msg, args))); 492 } 493 494 /** 495 * Creates an {@link UnsupportedOperationException} wrapping the given throwable. 496 * 497 * @param cause The cause of the exception. 498 * @return A new UnsupportedOperationException wrapping the given cause. 499 */ 500 public static UnsupportedOperationException unsupportedOp(Throwable cause) { 501 return log(new UnsupportedOperationException(cause)); 502 } 503 504 /** 505 * Creates an {@link UnsupportedOperationException} with a cause. 506 * 507 * @param cause The cause of the exception. 508 * @param msg The exception message. 509 * @param args The arguments to substitute into the message. 510 * @return A new UnsupportedOperationException with the formatted message and cause. 511 */ 512 public static UnsupportedOperationException unsupportedOp(Throwable cause, String msg, Object...args) { 513 return log(new UnsupportedOperationException(f(msg, args), cause)); 514 } 515 516 /** 517 * Creates an {@link UnsupportedOperationException} for read-only objects. 518 * 519 * @return A new UnsupportedOperationException with the message "Object is read only." 520 */ 521 public static UnsupportedOperationException unsupportedOpReadOnly() { 522 return log(new UnsupportedOperationException("Object is read only.")); 523 } 524}