ExException.java

package net.dapete.exceptional;

import java.io.Serial;

/**
 * Exception that wraps exceptions returned by the functional interfaces in the {@link net.dapete.exceptional.function} package.
 */
public final class ExException extends RuntimeException {

    @Serial
    private static final long serialVersionUID = 2311138051728963821L;

    /**
     * Create a new instance. The {@code cause} must not be a {@link RuntimeException}.
     *
     * @param cause the cause of this exception. Must not be a {@code RuntimeException}.
     * @throws IllegalArgumentException if {@code cause} is a {@code RuntimeException}.
     */
    ExException(Exception cause) {
        super(cause);
        if (cause instanceof RuntimeException) {
            throw new IllegalArgumentException("The cause of an ExException must not be a RuntimeException");
        }
    }

    /**
     * Override the default {@link Throwable#getCause()} to return an {@code Exception}, not just a {@code Throwable}.
     * The cause of an {@code ExException} is guaranteed to be an {@code Exception}.
     *
     * @return the cause of this exception.
     */
    @Override
    public synchronized Exception getCause() {
        if (super.getCause() instanceof Exception exception) {
            return exception;
        } else {
            // this should never happen, this is a last resort
            return new Exception(super.getCause());
        }
    }

    /**
     * Unwraps this exception, throwing its {@link #getCause() cause}.
     *
     * @throws Exception the cause of this exception.
     */
    @SuppressWarnings("DoNotCallSuggester")
    public void unwrap() throws Exception {
        throw getCause();
    }

    /**
     * Unwraps this exception, throwing its {@link #getCause() cause} if it is an instance of {@code exceptionClass}.
     * Otherwise, throws this exception.
     *
     * @param exceptionClass the class of the possible cause of this exception
     * @param <E>            the type of the first possible cause of this exception
     * @throws E           the cause of this exception, if it is an instance of {@code exceptionClass}.
     * @throws ExException if the cause of this exception is not an instance of {@code exceptionClass}.
     */
    public <E extends Exception> void unwrap(Class<E> exceptionClass) throws E {
        final Exception cause = getCause();
        ExUtils.throwIfInstance(exceptionClass, cause);
        throw this;
    }

    /**
     * Unwraps this exception, throwing its {@link #getCause() cause} if it is an instance of {@code exceptionClass1} or {@code exceptionClass2}.
     * Otherwise, throws this exception.
     *
     * @param exceptionClass1 the class of the first possible cause of this exception
     * @param exceptionClass2 the class of the second possible cause of this exception
     * @param <E1>            the type of the first possible cause of this exception
     * @param <E2>            the type of the second possible cause of this exception
     * @throws E1          the cause of this exception, if it is an instance of {@code exceptionClass1}.
     * @throws E2          the cause of this exception, if it is an instance of {@code exceptionClass2}.
     * @throws ExException if the cause of this exception is not an instance of {@code exceptionClass1} or {@code exceptionClass2}.
     */
    public <E1 extends Exception, E2 extends Exception> void unwrap(Class<E1> exceptionClass1, Class<E2> exceptionClass2) throws E1, E2 {
        final Exception cause = getCause();
        ExUtils.throwIfInstance(exceptionClass1, cause);
        ExUtils.throwIfInstance(exceptionClass2, cause);
        throw this;
    }

    /**
     * Unwraps this exception, throwing its {@link #getCause() cause} if it is an instance of {@code exceptionClass1}, {@code exceptionClass2} or
     * {@code exceptionClass3}.
     * Otherwise, throws this exception.
     *
     * @param exceptionClass1 the class of the first possible cause of this exception
     * @param exceptionClass2 the class of the second possible cause of this exception
     * @param exceptionClass3 the class of the third possible cause of this exception
     * @param <E1>            the type of the first possible cause of this exception
     * @param <E2>            the type of the second possible cause of this exception
     * @param <E3>            the type of the third possible cause of this exception
     * @throws E1          the cause of this exception, if it is an instance of {@code exceptionClass1}.
     * @throws E2          the cause of this exception, if it is an instance of {@code exceptionClass2}.
     * @throws E3          the cause of this exception, if it is an instance of {@code exceptionClass3}.
     * @throws ExException if the cause of this exception is not an instance of {@code exceptionClass1}, {@code exceptionClass2} or
     *                     {@code exceptionClass3}.
     */
    public <E1 extends Exception, E2 extends Exception, E3 extends Exception> void unwrap(
            Class<E1> exceptionClass1, Class<E2> exceptionClass2, Class<E3> exceptionClass3) throws E1, E2, E3 {
        final Exception cause = getCause();
        ExUtils.throwIfInstance(exceptionClass1, cause);
        ExUtils.throwIfInstance(exceptionClass2, cause);
        ExUtils.throwIfInstance(exceptionClass3, cause);
        throw this;
    }

}