package org.threadly.util;
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
/**
* Utilities for doing basic operations with exceptions.
*
* @since 1.0.0
*/
public class ExceptionUtils {
protected static final short INITIAL_BUFFER_PAD_AMOUNT_PER_TRACE_LINE = 16;
protected static final short INITIAL_BUFFER_PAD_AMOUNT_FOR_STACK = 64;
protected static final ThreadLocal<ExceptionHandler> THREAD_LOCAL_EXCEPTION_HANDLER;
protected static final InheritableThreadLocal<ExceptionHandler> INHERITED_EXCEPTION_HANDLER;
protected static volatile ExceptionHandler defaultExceptionHandler = null;
static {
THREAD_LOCAL_EXCEPTION_HANDLER = new ThreadLocal<>();
INHERITED_EXCEPTION_HANDLER = new InheritableThreadLocal<>();
}
/**
* Sets the {@link ExceptionHandler} for this thread. This exception handler will be
* called if this thread calls to {@link ExceptionUtils#handleException(Throwable)}.
*
* @param exceptionHandler Exception handler instance, or {@code null} to remove any handler
*/
public static void setThreadExceptionHandler(ExceptionHandler exceptionHandler) {
THREAD_LOCAL_EXCEPTION_HANDLER.set(exceptionHandler);
}
/**
* Sets the {@link ExceptionHandler} for this thread, and any threads that spawn off of this
* thread. If this thread, or any children threads (that do not override their
* {@link ExceptionHandler}), calls {@link ExceptionUtils#handleException(Throwable)},
* the provided interface will be called.
*
* @param exceptionHandler Exception handler instance, or {@code null} to remove any handler
*/
public static void setInheritableExceptionHandler(ExceptionHandler exceptionHandler) {
INHERITED_EXCEPTION_HANDLER.set(exceptionHandler);
}
/**
* Sets the default {@link ExceptionHandler} to be used by all threads. Assuming a threads
* local, or inheritable {@link ExceptionHandler} has not been set, this default instance will
* be relied on.
*
* @param exceptionHandler Exception handler instance, or {@code null} to remove any handler
*/
public static void setDefaultExceptionHandler(ExceptionHandler exceptionHandler) {
defaultExceptionHandler = exceptionHandler;
}
/**
* Gets the thread local {@link ExceptionHandler} if one is set, or {@code null} if none
* is set. Since {@link #getExceptionHandler()} prioritizes to the thread local handler, this can
* be used to get a reference to the current handler before changing the thread local handler to
* ensure that {@link #getExceptionHandler()} (and down stream use like
* {@link #handleException(Throwable)}) invoke a handler of your choosing. Once done you can then
* choose to reset the original handler with the one returned from this invocation.
*
* @return Thread local ExceptionHandler, or {@code null} if none is set
*/
public static ExceptionHandler getThreadLocalExceptionHandler() {
return THREAD_LOCAL_EXCEPTION_HANDLER.get();
}
/**
* Gets the set {@link ExceptionHandler} if one is set, or {@code null} if none is set.
* This prioritizes to the threads locally set handler, with the second priority being an inherited
* handler, with the final option being the default handler. If none of those are set, a
* {@code null} is returned.
*
* @return Handling instance for this thread, or {@code null} if none are available
*/
public static ExceptionHandler getExceptionHandler() {
ExceptionHandler eh = THREAD_LOCAL_EXCEPTION_HANDLER.get();
if (eh != null) {
return eh;
}
eh = INHERITED_EXCEPTION_HANDLER.get();
if (eh != null) {
return eh;
}
return defaultExceptionHandler;
}
/**
* Invokes {@link Runnable#run()} on the provided runnable on this thread, ensuring that no
* throwables are thrown out of this invocation. If any throwable's are thrown, they will be
* provided to {@link #handleException(Throwable)}.
*
* @param r Runnable to invoke, can not be null
*/
public static void runRunnable(Runnable r) {
try {
r.run();
} catch (Throwable t) {
handleException(t);
}
}
/**
* This call handles an uncaught throwable. If a default uncaught exception handler is set,
* then that will be called to handle the uncaught exception. If none is set, then the
* exception will be printed out to standard error.
*
* @param t throwable to handle
*/
public static void handleException(Throwable t) {
if (t == null) {
return;
}
try {
ExceptionHandler ehi = getExceptionHandler();
if (ehi != null) {
ehi.handleException(t);
} else {
Thread currentThread = Thread.currentThread();
UncaughtExceptionHandler ueHandler = currentThread.getUncaughtExceptionHandler();
ueHandler.uncaughtException(currentThread, t);
}
} catch (Throwable handlerThrown) {
try {
System.err.println("Error handling exception: ");
t.printStackTrace();
System.err.println("Error thrown when handling exception: ");
handlerThrown.printStackTrace();
} catch (Throwable ignored) {
// sigh...I give up
}
}
}
/**
* Remove the top functions from the stack of the provided throwable.
* <p>
* If you invoke this with the quantity of methods to remove being greater than or equal to the
* length of the stack of the provided throwable then an IllegalStateException will be thrown.
*
* @param t Throwable to modify
* @param methodsToRemove Number of top method calls to remove
*/
protected static void trimStack(Throwable t, int methodsToRemove) {
StackTraceElement[] originalstack = t.getStackTrace();
if (originalstack.length <= methodsToRemove) {
throw new IllegalArgumentException("Can not remove entire stack");
}
StackTraceElement[] newStack = new StackTraceElement[originalstack.length - methodsToRemove];
System.arraycopy(originalstack, methodsToRemove, newStack, 0, newStack.length);
t.setStackTrace(newStack);
}
/**
* Makes a r{@link RuntimeException} if necessary. If provided exception is already a
* {@link RuntimeException} then it is just directly returned. If it has to produce a new
* exception the stack is updated to omit this call.
* <p>
* If the point of wrapping the stack is not useful in debugging consider providing a
* {@code true} into. {@link #makeRuntime(Throwable, boolean)}.
*
* @param t {@link Throwable} which may or may not be a runtimeException
* @return a {@link RuntimeException} based on provided {@link Throwable}
*/
public static RuntimeException makeRuntime(Throwable t) {
if (t instanceof RuntimeException) {
return (RuntimeException)t;
} else {
// code duplicated for simpler logic around modifying the stack
TransformedException result = new TransformedException(t);
trimStack(result, 1);
return result;
}
}
/**
* Makes a r{@link RuntimeException} if necessary. If provided exception is already a
* {@link RuntimeException} then it is just directly returned. If it has to produce a new
* exception, you can control if a stack is generated by providing a {@code true} to suppress the
* generation (which in java can be fairly expensive). If stack generation is not suppressed (ie
* {@code false} is specified), then the stack will be modified to omit this call.
*
* @param t {@link Throwable} which may or may not be a runtimeException
* @param suppressWrappedStack {@code true} to avoid generating a stack trace
* @return A {@link RuntimeException} based on provided {@link Throwable}
*/
public static RuntimeException makeRuntime(Throwable t, boolean suppressWrappedStack) {
if (t instanceof RuntimeException) {
return (RuntimeException)t;
} else {
RuntimeException result;
if (suppressWrappedStack) {
result = new TransformedSuppressedStackException(t);
} else {
// code duplicated for simpler logic around modifying the stack
result = new TransformedException(t);
trimStack(result, 1);
}
return result;
}
}
/**
* Gets the root cause of a provided {@link Throwable}. If there is no cause for the
* {@link Throwable} provided into this function, the original {@link Throwable} is returned.
*
* @param throwable starting {@link Throwable}
* @return root cause {@link Throwable}
*/
public static Throwable getRootCause(Throwable throwable) {
ArgumentVerifier.assertNotNull(throwable, "throwable");
Throwable result = throwable;
while (result.getCause() != null) {
result = result.getCause();
}
return result;
}
/**
* Checks to see if the provided error, or any causes in the provided error matching the
* provided type. This can be useful when trying to truncate an exception chain to only the
* relevant information. If the goal is only to determine if it exists or not consider using
* {@link #hasCauseOfTypes(Throwable, Iterable)}. If you are only comparing against one exception
* type {@link #getCauseOfType(Throwable, Class)} is a better option (and will return without the
* need to cast, type thanks to generics).
*
* @param rootError Throwable to start search from
* @param types Types of throwable classes looking to match against
* @return Throwable that matches one of the provided types, or {@code null} if none was found
*/
public static Throwable getCauseOfTypes(Throwable rootError,
Iterable<? extends Class<? extends Throwable>> types) {
ArgumentVerifier.assertNotNull(types, "types");
Throwable t = rootError;
while (t != null) {
for (Class<?> c : types) {
if (c.isInstance(t)) {
return t;
}
}
t = t.getCause();
}
return null;
}
/**
* Checks to see if the provided error, or any causes in the provided error match the provided
* type. This can be useful when trying to detect conditions where the actual condition may not
* be the head cause, nor the root cause (but buried somewhere in the chain). If the actual
* exception is needed consider using {@link #getCauseOfTypes(Throwable, Iterable)}. If you are
* only comparing against one exception type {@link #hasCauseOfType(Throwable, Class)} is a
* better option.
*
* @param rootError Throwable to start search from
* @param types Types of throwable classes looking to match against
* @return {@code true} if a match was found, false if no exception cause matches any provided types
*/
public static boolean hasCauseOfTypes(Throwable rootError,
Iterable<? extends Class<? extends Throwable>> types) {
return getCauseOfTypes(rootError, types) != null;
}
/**
* Checks to see if the provided error, or any causes in the provided error matching the
* provided type. This can be useful when trying to truncate an exception chain to only the
* relevant information. If the goal is only to determine if it exists or not consider using
* {@link #hasCauseOfType(Throwable, Class)}.
*
* @param rootError Throwable to start search from
* @param type Type of throwable classes looking to match against
* @param <T> Type of throwable to return (must equal or be super type of generic class provided)
* @return Throwable that matches one of the provided types, or {@code null} if none was found
*/
@SuppressWarnings("unchecked") // verified by generics
public static <T extends Throwable> T getCauseOfType(Throwable rootError,
Class<? extends T> type) {
ArgumentVerifier.assertNotNull(type, "type");
Throwable t = rootError;
while (t != null) {
if (type.isInstance(t)) {
return (T)t;
}
t = t.getCause();
}
return null;
}
/**
* Checks to see if the provided error, or any causes in the provided error match the provided
* type. This can be useful when trying to detect conditions where the actual condition may not
* be the head cause, nor the root cause (but buried somewhere in the chain). If the actual
* exception is needed consider using {@link #getCauseOfType(Throwable, Class)}.
*
* @param rootError Throwable to start search from
* @param type Type of throwable classes looking to match against
* @return {@code true} if a match was found, false if no exception cause matches any provided types
*/
public static boolean hasCauseOfType(Throwable rootError, Class<? extends Throwable> type) {
return getCauseOfType(rootError, type) != null;
}
/**
* Convert throwable's stack and message into a simple string.
*
* @param t throwable which contains stack
* @return string which contains the throwable stack trace
*/
public static String stackToString(Throwable t) {
if (t == null) {
return "";
}
String msg = t.getMessage();
int msgLength = (msg == null ? 0 : msg.length());
StringBuilder sb = new StringBuilder(msgLength + INITIAL_BUFFER_PAD_AMOUNT_FOR_STACK);
writeStackTo(t, sb);
return sb.toString();
}
/**
* Formats and writes a throwable's stack trace to a provided {@link StringBuilder}.
*
* @param t {@link Throwable} which contains stack
* @param sb StringBuilder to write output to
*/
@SuppressWarnings("resource")
public static void writeStackTo(Throwable t, StringBuilder sb) {
writeStackTo(t, new StringBuilderWriter(sb));
}
/**
* Formats and writes a throwable's stack trace to a provided {@link StringBuffer}.
*
* @param t {@link Throwable} which contains stack
* @param sb StringBuffer to write output to
*/
@SuppressWarnings("resource")
public static void writeStackTo(Throwable t, StringBuffer sb) {
writeStackTo(t, new StringBufferWriter(sb));
}
/**
* Formats and writes a throwable's stack trace to a provided {@link Writer}.
*
* @param t {@link Throwable} which contains stack
* @param w Writer to write output to
*/
public static void writeStackTo(Throwable t, Writer w) {
if (t == null) {
return;
}
PrintWriter pw = new PrintWriter(w);
try {
t.printStackTrace(pw);
pw.flush();
} finally {
pw.close();
}
}
/**
* Writes the stack trace array out to a string. This produces a stack trace string in a very
* similar way as the {@link #stackToString(Throwable)} from a throwable would.
*
* @param stack Array of stack elements to build the string off of
* @return String which is the stack in a human readable format
*/
public static String stackToString(StackTraceElement[] stack) {
if (stack == null) {
return "";
}
StringBuilder sb = new StringBuilder(stack.length * INITIAL_BUFFER_PAD_AMOUNT_PER_TRACE_LINE);
writeStackTo(stack, sb);
return sb.toString();
}
/**
* Writes the stack to the provided StringBuilder. This produces a stack trace string in
* a very similar way as the {@link #writeStackTo(Throwable, StringBuilder)} would.
*
* @param stack Array of stack elements to build the string off of
* @param stringBuilder StringBuilder to write the stack out to
*/
public static void writeStackTo(StackTraceElement[] stack, StringBuilder stringBuilder) {
if (stack == null) {
return;
}
ArgumentVerifier.assertNotNull(stringBuilder, "stringBuilder");
for (StackTraceElement ste : stack) {
stringBuilder.append("\t at ").append(ste.toString()).append(System.lineSeparator());
}
}
/**
* Exception which is constructed from {@link #makeRuntime(Throwable)} when the exception was
* not a runtime exception.
*
* @since 1.0.0
*/
public static class TransformedException extends RuntimeException {
private static final long serialVersionUID = 4524467217814731188L;
/**
* Constructs a new TransformedException.
*
* @param message message for exception
* @param t throwable cause
*/
protected TransformedException(Throwable t) {
super(t == null ? null : t.getMessage(), t);
}
}
/**
* Exception which is constructed from {@link #makeRuntime(Throwable, boolean)} when the
* exception was not a runtime exception, and stack is being suppressed.
*
* @since 4.8.0
*/
public static class TransformedSuppressedStackException extends SuppressedStackRuntimeException {
private static final long serialVersionUID = 6501962264714125183L;
/**
* Constructs a new TransformedSuppressedStackException.
*
* @param message message for exception
* @param t throwable cause
*/
protected TransformedSuppressedStackException(Throwable t) {
super(t == null ? null : t.getMessage(), t);
}
}
}