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
019
020import static org.apache.juneau.commons.utils.Exceptions.*;
021import static org.apache.juneau.commons.utils.ObjectUtils.*;
022
023import java.io.*;
024import java.util.*;
025import java.util.function.*;
026
027import org.apache.juneau.commons.function.*;
028import org.apache.juneau.commons.logging.*;
029import org.apache.juneau.commons.settings.*;
030
031/**
032 * Various utility methods for working with throwables.
033 *
034 * <p>
035 * Exception <i>factory</i> methods (terse {@code rex}/{@code brex}/{@code iaex}/{@code isex}/{@code uoex}/
036 * {@code uoroex}/{@code ioex}/{@code exex} constructors) live on {@link Shorts}
037 * and construct their exceptions directly with no side-effects.  This class retains only throwable
038 * <i>inspection</i>/<i>handling</i> helpers (stack-trace rendering, cause walking, safe-run wrappers, casting).
039 *
040 * <p>
041 * The opt-in {@link #log(Throwable)} helper checks the {@code juneau.enableVerboseExceptions} setting and, when
042 * enabled, prints the throwable's stack trace to the logger.  It is <b>not</b> invoked automatically by any
043 * factory — callers that want verbose logging at a specific site must call it explicitly.
044 *
045 * <h5 class='section'>Example — enabling for a single test thread:</h5>
046 * <p class='bjava'>
047 *    Settings.<jsm>get</jsm>().setLocal(<js>"juneau.enableVerboseExceptions"</js>, <js>"true"</js>);
048 *    <jk>try</jk> {
049 *       <jc>// ... code under test ...</jc>
050 *    } <jk>finally</jk> {
051 *       Settings.<jsm>get</jsm>().clearLocal();
052 *    }
053 * </p>
054 *
055 * <h5 class='section'>See Also:</h5><ul>
056 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsUtils">Utils Package</a>
057 * </ul>
058 */
059@SuppressWarnings({
060   "java:S1181" // Need to catch Throwable to handle all exception types including Error
061})
062public class ThrowableUtils {
063
064   private static final RichLogger LOG = RichLogger.getLogger(ThrowableUtils.class);
065
066   static final Setting<Boolean> VERBOSE = Settings.get().get("juneau.enableVerboseExceptions").asBoolean();
067
068   /**
069    * Interface used with {@link #safeSupplier(SupplierWithThrowable)}.
070    *
071    * @param <T> The supplier type.
072    */
073   @FunctionalInterface
074   @SuppressWarnings({
075      "java:S112" // throws Throwable intentional - supplier may throw any exception
076   })
077   public interface SupplierWithThrowable<T> {
078
079      /**
080       * Gets a result.
081       *
082       * @return a result
083       * @throws Throwable if supplier threw an exception.
084       */
085      T get() throws Throwable;
086   }
087
088   /**
089    * Casts or wraps the specified throwable to the specified type.
090    *
091    * @param <T> The class to cast to.
092    * @param type The class to cast to.  Must not be <jk>null</jk>.
093    * @param t The throwable to cast.  Can be <jk>null</jk> (produces a wrapper with a <jk>null</jk> cause).
094    * @return Either the same exception if it's already the specified type, or a wrapped exception.
095    */
096   public static <T> T castException(Class<T> type, Throwable t) {
097      return safeSupplier(() -> type.isInstance(t) ? type.cast(t) : type.getConstructor(Throwable.class).newInstance(t));
098   }
099
100   /**
101    * Searches through the cause chain of an exception to find an exception of the specified type.
102    *
103    * @param <T> The cause type.
104    * @param e The exception to search.  Can be <jk>null</jk> (returns an empty {@link Optional}).
105    * @param cause The cause type to search for.  Must not be <jk>null</jk>.
106    * @return An {@link Optional} containing the cause if found, or empty if not found.
107    */
108   public static <T extends Throwable> Optional<T> findCause(Throwable e, Class<T> cause) {
109      while (isNotNull(e)) {
110         if (cause.isInstance(e))
111            return optional(cause.cast(e));
112         e = e.getCause();
113      }
114      return emptyOptional();
115   }
116
117   /**
118    * Convenience method for getting a stack trace as a string.
119    *
120    * @param t The throwable to get the stack trace from.  Must not be <jk>null</jk>.
121    * @return The same content that would normally be rendered via <c>t.printStackTrace()</c>
122    */
123   public static String getStackTrace(Throwable t) {
124      var sw = new StringWriter();
125      try (var pw = new PrintWriter(sw)) {
126         t.printStackTrace(pw);
127      }
128      return sw.toString();
129   }
130
131   /**
132    * Prints a stack trace with a maximum depth limit.
133    *
134    * <p>
135    * This method is useful for {@link StackOverflowError} situations where printing the full stack trace
136    * can cause additional errors due to stack exhaustion. The stack trace will be limited to the specified
137    * maximum number of elements.
138    *
139    * <h5 class='section'>Example:</h5>
140    * <p class='bjava'>
141    *    <jk>try</jk> {
142    *       <jc>// Some code that might cause StackOverflowError</jc>
143    *    } <jk>catch</jk> (StackOverflowError <jv>e</jv>) {
144    *       <jc>// Print only first 100 stack trace elements</jc>
145    *       ThrowableUtils.<jsm>printStackTrace</jsm>(<jv>e</jv>, System.err, 100);
146    *    }
147    * </p>
148    *
149    * @param t The throwable to print the stack trace for.  Must not be <jk>null</jk>.
150    * @param pw The print writer to write to.  Must not be <jk>null</jk>.
151    * @param maxDepth The maximum number of stack trace elements to print. If <jk>null</jk> or negative, prints all elements.
152    */
153   public static void printStackTrace(Throwable t, PrintWriter pw, Integer maxDepth) {
154      try {
155         pw.println(t);
156         var stackTrace = t.getStackTrace();
157         var depth = maxDepth != null && maxDepth > 0 ? Math.min(maxDepth, stackTrace.length) : stackTrace.length;
158         for (var i = 0; i < depth; i++) {
159            pw.println("\tat " + stackTrace[i]);
160         }
161         if (maxDepth != null && maxDepth > 0 && stackTrace.length > maxDepth) {
162            pw.println("\t... (" + (stackTrace.length - maxDepth) + " more)");
163         }
164         var cause = t.getCause();
165         if (cause != null && cause != t) {
166            pw.print("Caused by: ");
167            printStackTrace(cause, pw, maxDepth);
168         }
169      } catch (Exception e) {
170         pw.println("Error printing stack trace: " + e.getMessage());
171      }
172   }
173
174   /**
175    * Prints a stack trace with a maximum depth limit to standard error.
176    *
177    * <p>
178    * This method is useful for {@link StackOverflowError} situations where printing the full stack trace
179    * can cause additional errors due to stack exhaustion. The stack trace will be limited to the specified
180    * maximum number of elements.
181    *
182    * <p>
183    * Same as {@link #printStackTrace(Throwable, PrintWriter, Integer)} but writes to {@link System#err}.
184    *
185    * <h5 class='section'>Example:</h5>
186    * <p class='bjava'>
187    *    <jk>try</jk> {
188    *       <jc>// Some code that might cause StackOverflowError</jc>
189    *    } <jk>catch</jk> (StackOverflowError <jv>e</jv>) {
190    *       <jc>// Print only first 100 stack trace elements to stderr</jc>
191    *       ThrowableUtils.<jsm>printStackTrace</jsm>(<jv>e</jv>, 100);
192    *    }
193    * </p>
194    *
195    * @param t The throwable to print the stack trace for.
196    * @param maxDepth The maximum number of stack trace elements to print. If <jk>null</jk> or negative, prints all elements.
197    */
198   public static void printStackTrace(Throwable t, Integer maxDepth) {
199      var sw = new StringWriter();
200      try (var pw = new PrintWriter(sw)) {
201         printStackTrace(t, pw, maxDepth);
202      }
203      LOG.warning(t, sw.toString());
204   }
205
206   /**
207    * Same as {@link Throwable#getCause()} but searches the throwable chain for an exception of the specified type.
208    *
209    * @param c The throwable type to search for.  Must not be <jk>null</jk>.
210    * @param <T> The throwable type to search for.
211    * @param t The throwable to search.  Can be <jk>null</jk> (returns <jk>null</jk>).
212    * @return The exception, or <jk>null</jk> if not found.
213    */
214   public static <T extends Throwable> T getThrowableCause(Class<T> c, Throwable t) {
215      while (isNotNull(t)) {
216         t = t.getCause();
217         if (c.isInstance(t))
218            return c.cast(t);
219      }
220      return null;
221   }
222
223   /**
224    * Calculates a 16-bit hash for the specified throwable based on it's stack trace.
225    *
226    * @param t The throwable to calculate the stack trace on.  Can be <jk>null</jk> (returns <c>0</c>).
227    * @param stopClass Optional stop class on which to stop calculation of a stack trace beyond when found.  Can be <jk>null</jk> (the entire stack trace, including causes, is used).
228    * @return A calculated hash.
229    */
230   public static int hash(Throwable t, String stopClass) {
231      var i = 0;
232      while (isNotNull(t)) {
233         for (var e : t.getStackTrace()) {
234            if (e.getClassName().equals(stopClass))
235               break;
236            if (e.getClassName().indexOf('$') == -1)
237               i ^= e.hashCode();
238         }
239         t = t.getCause();
240      }
241      return i;
242   }
243
244   /**
245    * Opt-in verbose logger for a throwable.
246    *
247    * <p>
248    * When the {@code juneau.enableVerboseExceptions} setting is {@code true}, logs the throwable's stack trace
249    * as a warning; otherwise does nothing.  Returns the same throwable to allow inline use (e.g.
250    * {@code throw log(iaex("..."))}).  This is never invoked automatically — call it explicitly at sites where
251    * verbose logging is desired.
252    *
253    * @param <T> The throwable type.
254    * @param exception The throwable to (optionally) log.  Must not be <jk>null</jk> when verbose exceptions are enabled; otherwise returned unchanged (may be <jk>null</jk>).
255    * @return The same throwable.
256    */
257   public static <T extends Throwable> T log(T exception) {
258      if (isTrue(VERBOSE.orElse(false))) LOG.warning(exception, getStackTrace(exception));
259      return exception;
260   }
261
262   /**
263    * Convenience method for calling {@link Throwable#getLocalizedMessage()}.
264    *
265    * <p>
266    * Long messages (>2000 characters) are truncated to prevent stack overflow issues
267    * when exception messages contain circular references. The format is:
268    * "first-1000-chars&lt;truncated-#-chars&gt;last-1000-chars"
269    *
270    * @param t The throwable.  Must not be <jk>null</jk>.
271    * @return The localized message of the throwable, truncated if necessary, or <jk>null</jk> if the throwable has no localized message.
272    */
273   public static String localizedMessage(Throwable t) {
274      String msg = t.getLocalizedMessage();
275      if (msg == null)
276         return null;
277      if (msg.length() <= 2000)
278         return msg;
279      int truncated = msg.length() - 2000;
280      return msg.substring(0, 1000) + "<truncated-" + truncated + "-chars>" + msg.substring(msg.length() - 1000);
281   }
282
283   /** Wraps or casts to RuntimeException.  <br>A <jk>null</jk> argument produces a {@link RuntimeException} with a <jk>null</jk> cause. */
284   public static RuntimeException toRuntimeException(Throwable cause) {
285      return castException(RuntimeException.class, cause);
286   }
287
288   /**
289    * Creates a new {@link RuntimeException} from the specified cause (or returns it if already one).
290    *
291    * @param cause The caused-by exception.  Can be <jk>null</jk> (produces a {@link RuntimeException} with a <jk>null</jk> cause).
292    * @return A new {@link RuntimeException}, or the same exception if it's already of that type.
293    */
294   public static RuntimeException toRex(Throwable cause) {
295      return toRuntimeException(cause);
296   }
297
298   /** Runs a snippet; wraps any checked throwable in {@link RuntimeException}. */
299   public static void safe(Snippet snippet) {
300      try {
301         snippet.run();
302      } catch (RuntimeException t) {
303         throw t;
304      } catch (Throwable t) {
305         throw toRuntimeException(t);
306      }
307   }
308
309   /** Runs a snippet; maps any checked throwable via the provided function. */
310   public static void safe(Snippet snippet, Function<Throwable,RuntimeException> exceptionMapper) {
311      try {
312         snippet.run();
313      } catch (RuntimeException t) {
314         throw t;
315      } catch (Throwable t) {
316         throw exceptionMapper.apply(t);
317      }
318   }
319
320   /** Executes a supplier; wraps any checked exception in {@link RuntimeException}. */
321   public static <T> T safe(ThrowingSupplier<T> s) {
322      try {
323         return s.get();
324      } catch (RuntimeException e) {
325         throw e;
326      } catch (Exception | Error e) {
327         throw rex(e);
328      }
329   }
330
331   /** Executes a supplier; maps any checked exception via the provided function. */
332   public static <T> T safe(ThrowingSupplier<T> s, Function<Throwable,RuntimeException> exceptionMapper) {
333      try {
334         return s.get();
335      } catch (RuntimeException e) {
336         throw e;
337      } catch (Exception | Error e) {
338         throw exceptionMapper.apply(e);
339      }
340   }
341
342   /** Runs a snippet, silently ignoring any exception. */
343   public static void runQuietly(Snippet snippet) {
344      try {
345         snippet.run();
346      } catch (@SuppressWarnings("unused") Throwable t) { /* Ignore */ }
347   }
348
349   /** Executes a supplier; returns the result or a fallback value produced by the exception function. */
350   public static <T> T safeCatch(ThrowingSupplier<T> s, Function<Throwable,T> exceptionFunction) {
351      try {
352         return s.get();
353      } catch (Exception e) {
354         return exceptionFunction.apply(e);
355      }
356   }
357
358   /** Executes a supplier; returns an {@link Optional} of the result, or empty if an exception is thrown. */
359   public static <T> Optional<T> safeOpt(ThrowingSupplier<T> s) {
360      try {
361         return Optional.ofNullable(s.get());
362      } catch (@SuppressWarnings("unused") Exception e) {
363         return Optional.empty();
364      }
365   }
366
367   /** Executes a supplier; returns an {@link Optional} of the result or the exception function's value. */
368   public static <T> Optional<T> safeOptCatch(ThrowingSupplier<T> s, Function<Throwable,T> exceptionFunction) {
369      try {
370         return Optional.ofNullable(s.get());
371      } catch (Exception e) {
372         return Optional.ofNullable(exceptionFunction.apply(e));
373      }
374   }
375
376   /** Executes a supplier; returns the result or {@code null} if an exception is thrown. */
377   public static <T> T safeOrNull(ThrowingSupplier<T> s) {
378      return safeOpt(s).orElse(null);
379   }
380
381   /** Executes a {@link SupplierWithThrowable}; wraps any checked throwable in {@link RuntimeException}. */
382   public static <T> T safeSupplier(SupplierWithThrowable<T> supplier) {
383      try {
384         return supplier.get();
385      } catch (RuntimeException t) {
386         throw t;
387      } catch (Throwable t) {
388         throw toRuntimeException(t);
389      }
390   }
391
392   /** Executes a {@link SupplierWithThrowable}; maps any checked throwable via the provided function. */
393   public static <T> T safeSupplier(SupplierWithThrowable<T> supplier, Function<Throwable,RuntimeException> exceptionMapper) {
394      try {
395         return supplier.get();
396      } catch (RuntimeException t) {
397         throw t;
398      } catch (Throwable t) {
399         throw exceptionMapper.apply(t);
400      }
401   }
402}