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<truncated-#-chars>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}