Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ public void packDouble(double v) {
*
* @param s the value to be written.
*/
public void packString(@Nullable String s) {
public void packString(@Nullable CharSequence s) {
assert !closed : "Packer is closed";

if (s == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.lang.invoke.WrongMethodTypeException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Objects;
Expand All @@ -58,283 +53,16 @@
*/
public final class ExceptionUtils {
/**
* The names of methods commonly used to access a wrapped exception.
*/
private static final String[] CAUSE_METHOD_NAMES = {
"getCause",
"getNextException",
"getTargetException",
"getException",
"getSourceException",
"getRootCause",
"getCausedByException",
"getNested",
"getLinkedException",
"getNestedException",
"getLinkedCause",
"getThrowable"
};

/**
* The Method object for Java 1.4 getCause.
*/
private static final Method THROWABLE_CAUSE_METHOD;

static {
Method causeMtd;

try {
causeMtd = Throwable.class.getMethod("getCause", (Class<?>) null);
} catch (Exception ignored) {
causeMtd = null;
}

THROWABLE_CAUSE_METHOD = causeMtd;
}

/**
* Introspects the {@code Throwable} to obtain the cause.
*
* @param throwable The exception to examine.
* @return The wrapped exception, or {@code null} if not found.
*/
@Nullable
private static Throwable getCauseUsingWellKnownTypes(Throwable throwable) {
if (throwable instanceof SQLException) {
return ((SQLException) throwable).getNextException();
}

if (throwable instanceof InvocationTargetException) {
return ((InvocationTargetException) throwable).getTargetException();
}

return null;
}

/**
* Finds a {@code Throwable} by method name.
*
* @param throwable The exception to examine.
* @param mtdName The name of the method to find and invoke.
* @return The wrapped exception, or {@code null} if not found.
*/
private static @Nullable Throwable getCauseUsingMethodName(Throwable throwable, String mtdName) {
Method mtd = null;

try {
mtd = throwable.getClass().getMethod(mtdName, (Class<?>) null);
} catch (NoSuchMethodException | SecurityException ignored) {
// exception ignored
}

if (mtd != null && Throwable.class.isAssignableFrom(mtd.getReturnType())) {
try {
return (Throwable) mtd.invoke(throwable, ArrayUtils.OBJECT_EMPTY_ARRAY);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ignored) {
// exception ignored
}
}

return null;
}

/**
* Finds a {@code Throwable} by field name.
*
* @param throwable The exception to examine.
* @param fieldName The name of the attribute to examine.
* @return The wrapped exception, or {@code null} if not found.
*/
private static @Nullable Throwable getCauseUsingFieldName(Throwable throwable, String fieldName) {
Field field = null;

try {
field = throwable.getClass().getField(fieldName);
} catch (NoSuchFieldException | SecurityException ignored) {
// exception ignored
}

if (field != null && Throwable.class.isAssignableFrom(field.getType())) {
try {
return (Throwable) field.get(throwable);
} catch (IllegalAccessException | IllegalArgumentException ignored) {
// exception ignored
}
}

return null;
}

/**
* Checks if the Throwable class has a {@code getCause} method.
*
* @return True if Throwable is nestable.
*/
public static boolean isThrowableNested() {
return THROWABLE_CAUSE_METHOD != null;
}

/**
* Checks whether this {@code Throwable} class can store a cause.
*
* @param throwable The {@code Throwable} to examine, may be null.
* @return Boolean {@code true} if nested otherwise {@code false}.
*/
public static boolean isNestedThrowable(Throwable throwable) {
if (throwable == null) {
return false;
}

if (throwable instanceof SQLException || throwable instanceof InvocationTargetException) {
return true;
}

if (isThrowableNested()) {
return true;
}

Class<?> cls = throwable.getClass();
for (String methodName : CAUSE_METHOD_NAMES) {
try {
Method mtd = cls.getMethod(methodName, (Class<?>) null);

if (Throwable.class.isAssignableFrom(mtd.getReturnType())) {
return true;
}
} catch (NoSuchMethodException | SecurityException ignored) {
// exception ignored
}
}

try {
cls.getField("detail");

return true;
} catch (NoSuchFieldException | SecurityException ignored) {
// exception ignored
}

return false;
}

/**
* Introspects the {@code Throwable} to obtain the cause.
*
* @param throwable The throwable to introspect for a cause, may be null.
* @return The cause of the {@code Throwable}, {@code null} if none found or null throwable input.
*/
public static @Nullable Throwable getCause(Throwable throwable) {
return getCause(throwable, CAUSE_METHOD_NAMES);
}

/**
* Introspects the {@code Throwable} to obtain the cause.
*
* @param throwable The throwable to introspect for a cause, may be null.
* @param mtdNames The method names, null treated as default set.
* @return The cause of the {@code Throwable}, {@code null} if none found or null throwable input.
*/
@Nullable
public static Throwable getCause(@Nullable Throwable throwable, String[] mtdNames) {
if (throwable == null) {
return null;
}

Throwable cause = getCauseUsingWellKnownTypes(throwable);

if (cause == null) {
if (mtdNames == null) {
mtdNames = CAUSE_METHOD_NAMES;
}

for (String mtdName : mtdNames) {
if (mtdName != null) {
cause = getCauseUsingMethodName(throwable, mtdName);

if (cause != null) {
break;
}
}
}

if (cause == null) {
cause = getCauseUsingFieldName(throwable, "detail");
}
}

return cause;
}

/**
* Returns the list of {@code Throwable} objects in the exception chain.
*
* <p>A throwable without cause will return a list containing one element - the input throwable. A throwable with one cause
* will return a list containing two elements - the input throwable and the cause throwable. A {@code null} throwable will return a list
* of size zero.
*
* <p>This method handles recursive cause structures that might otherwise cause infinite loops. The cause chain is processed until
* the end is reached, or until the next item in the chain is already in the result set.
*
* @param throwable The throwable to inspect, may be null.
* @return The list of throwables, never null.
*/
public static List<Throwable> getThrowableList(Throwable throwable) {
List<Throwable> list = new ArrayList<>();

// TODO: https://issues.apache.org/jira/browse/IGNITE-28026
while (throwable != null && !list.contains(throwable)) {
list.add(throwable);
throwable = getCause(throwable);
}

return list;
}

/**
* Collects suppressed exceptions from throwable and all it causes.
*
* @param t Throwable.
* @return List of suppressed throwables.
*/
public static List<Throwable> getSuppressedList(@Nullable Throwable t) {
List<Throwable> result = new ArrayList<>();

if (t == null) {
return result;
}

// TODO: https://issues.apache.org/jira/browse/IGNITE-28026
do {
for (Throwable suppressed : t.getSuppressed()) {
result.add(suppressed);

result.addAll(getSuppressedList(suppressed));
}
} while ((t = t.getCause()) != null);

return result;
}

/**
* A way to get the entire nested stack-trace of an throwable.
* Gets the stack trace as a char sequence.
*
* @param throwable The {@code Throwable} to be examined.
* @return The nested stack trace, with the root cause first.
* @return The stack trace.
*/
public static String getFullStackTrace(Throwable throwable) {
public static CharSequence getFullStackTrace(Throwable throwable) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw, true);
var ts = getThrowableList(throwable);

for (Throwable t : ts) {
t.printStackTrace(pw);

if (isNestedThrowable(t)) {
break;
}
}

return sw.getBuffer().toString();
throwable.printStackTrace(pw);
return sw.getBuffer();
Comment thread
ptupitsyn marked this conversation as resolved.
Comment thread
ptupitsyn marked this conversation as resolved.
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ protected void describeMismatchSafely(CompletableFuture<?> item, Description mis
private static void describeThrowable(Throwable throwable, Description mismatchDescription) {
mismatchDescription.appendValue(throwable)
.appendText(System.lineSeparator())
.appendText(ExceptionUtils.getFullStackTrace(throwable));
.appendText(ExceptionUtils.getFullStackTrace(throwable).toString());
}

private boolean matchesWithCause(Throwable e) {
Expand Down