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}