ExceptionUtil.java

package tools.jackson.databind.util;

import tools.jackson.core.JacksonException;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.DeserializationFeature;

/**
 * Utility methods for dealing with exceptions/throwables
 *
 * @since 2.15
 */
public class ExceptionUtil {
    private ExceptionUtil() {}

    /**
     * It is important never to catch all <code>Throwable</code>s. Some like
     * {@link InterruptedException} should be rethrown. Based on
     * <a href="https://www.scala-lang.org/api/2.13.10/scala/util/control/NonFatal$.html">scala.util.control.NonFatal</a>.
     *
     * This method should be used with care.
     * <p>
     *     If the <code>Throwable</code> is fatal, it is rethrown, otherwise, this method just returns.
     *     The input throwable is thrown if it is an <code>Error</code> or a <code>RuntimeException</code>.
     *     Otherwise, the method wraps the throwable in a RuntimeException and throws that.
     * </p>
     *
     * @param throwable to check
     * @throws Error the input throwable if it is fatal
     * @throws RuntimeException the input throwable if it is fatal - throws the original throwable
     * if is a <code>RuntimeException</code>. Otherwise, wraps the throwable in a RuntimeException.
     */
    public static void rethrowIfFatal(Throwable throwable) throws Error, RuntimeException {
        if (isFatal(throwable)) {
            if (throwable instanceof Error error) {
                throw error;
            }
            if (throwable instanceof RuntimeException runtimeException) {
                throw runtimeException;
            }
            throw new RuntimeException(throwable);
        }
    }

    /**
     * Helper method that will either throw given throwable -- if (and only if)
     * one of following is true:
     *
     * 1. It is an {@link Error}
     * 2. It is a {@link JacksonException}
     * 3. {@code DeserializationFeature.WRAP_EXCEPTIONS} is NOT enabled AND
     *    exception is a {@link RuntimeException}
     *
     * -- or (otherwise) returns throwable as-is.
     *
     * @param ctxt Current deserialization context
     * @param e Exception caught to possibly re-throw
     *
     * @return Exception passed in for call chaining
     *
     * @since 3.1
     */
    public static <ERR extends Throwable> ERR rethrowIfNoWrap(DeserializationContext ctxt, ERR e)
    {
        if (e instanceof Error err) {
            throw err;
        }
        if (e instanceof JacksonException je) {
            throw je;
        }
        if ((ctxt != null)
                && !ctxt.isEnabled(DeserializationFeature.WRAP_EXCEPTIONS)
                && e instanceof RuntimeException re) {
            throw re;
        }
        return e;
    }

    /**
     * It is important never to catch all <code>Throwable</code>s. Some like
     * {@link InterruptedException} should be rethrown. Based on
     * <a href="https://www.scala-lang.org/api/2.13.10/scala/util/control/NonFatal$.html">scala.util.control.NonFatal</a>.
     *
     * @param throwable to check
     * @return whether the <code>Throwable</code> is a fatal error
     */
    @SuppressWarnings("removal")
    private static boolean isFatal(Throwable throwable) {
        return (throwable instanceof VirtualMachineError
                || throwable instanceof ThreadDeath
                || throwable instanceof InterruptedException
                || throwable instanceof ClassCircularityError
                || throwable instanceof ClassFormatError
                || throwable instanceof IncompatibleClassChangeError
                || throwable instanceof BootstrapMethodError
                || throwable instanceof VerifyError
        );
    }
}