package io.dico.dicore.exceptions; import io.dico.dicore.exceptions.checkedfunctions.CheckedRunnable; import io.dico.dicore.exceptions.checkedfunctions.CheckedSupplier; import io.dico.dicore.exceptions.checkedfunctions.CheckedFunctionalObject; import java.io.PrintWriter; import java.io.StringWriter; import java.sql.SQLException; import java.util.Objects; import java.util.function.Consumer; import java.util.function.Supplier; @FunctionalInterface public interface ExceptionHandler { /** * Handle the given exception according to this handler's implementation * * @param ex The exception to be handled * @throws NullPointerException if ex is null, unless the implementation specifies otherwise * @throws Error ex if ex is an instance of Error, unless the implementation specifies otherwise */ void handle(Throwable ex); /** * Handle the given exception according to this handler's implementation * This method is intended for use by {@link CheckedFunctionalObject} and subinterfaces. * It supplies exception handlers the option to acquire more information, by overriding this method and calling it from {@link #handle(Throwable)} * * @param ex The exception to be handled * @param args Any arguments passed, this is used by {@link CheckedFunctionalObject} and subinterfaces. * @return {@code null} (unless specified otherwise by the implementation) * @throws NullPointerException if ex is null, unless the implementation specifies otherwise * @throws Error ex if ex is an instance of Error, unless the implementation specifies otherwise */ default Object handleGenericException(Throwable ex, Object... args) { handle(ex); return null; } /** * @return true if this {@link ExceptionHandler}'s {@link #handleGenericException(Throwable, Object...)} method is never expected to throw * an unchecked exception other than {@link Error} */ default boolean isSafe() { return true; } /** * Runs the given checked action, handling any thrown exceptions using this exception handler. *

* Any exceptions thrown by this handler are delegated to the caller. * * @param action The action to run * @throws NullPointerException if action is null */ default void runSafe(CheckedRunnable action) { Objects.requireNonNull(action); try { action.checkedRun(); } catch (Throwable ex) { handle(ex); } } /** * Computes the result of the given checked supplier, handling any thrown exceptions using this exception handler. *

* Any exceptions thrown by this handler are delegated to the caller. * * @param action The supplier whose result to compute * @param generic type parameter for the supplier and the result type of this method * @return The result of this computation, or null if an error occurred * @throws NullPointerException if action is null */ default T supplySafe(CheckedSupplier action) { Objects.requireNonNull(action); try { return action.checkedGet(); } catch (Throwable ex) { handle(ex); return null; } } /** * @param action The action to wrap * @return A runnable that wraps the given action using this handler's {@link #runSafe(CheckedRunnable)} method. * @see #runSafe(CheckedRunnable) */ default Runnable safeRunnable(CheckedRunnable action) { return () -> runSafe(action); } /** * @param supplier The computation to wrap * @return A supplier that wraps the given computation using this handler's {@link #supplySafe(CheckedSupplier)} method. * @see #supplySafe(CheckedSupplier) */ default Supplier safeSupplier(CheckedSupplier supplier) { return () -> supplySafe(supplier); } /** * Logs the given exception as an error to {@code out} *

* Format: Error occurred while {@code failedActivityDescription}, followed by additional details and a stack trace * * @param out The consumer to accept the error message, for instance {@code {@link java.util.logging.Logger logger}::warning}. * @param failedActivityDescription A description of the activity that was being executed when the exception was thrown * @param ex The exception that was thrown * @throws NullPointerException if any argument is null */ static void log(Consumer out, String failedActivityDescription, Throwable ex) { if (out == null || failedActivityDescription == null || ex == null) { throw new NullPointerException(); } StringWriter msg = new StringWriter(1024); msg.append("Error occurred while ").append(failedActivityDescription).append(':'); if (ex instanceof SQLException) { SQLException sqlex = (SQLException) ex; msg.append('\n').append("Error code: ").append(Integer.toString(sqlex.getErrorCode())); msg.append('\n').append("SQL State: ").append(sqlex.getSQLState()); } msg.append('\n').append("=======START STACK======="); try (PrintWriter pw = new PrintWriter(msg)) { ex.printStackTrace(pw); } msg.append('\n').append("========END STACK========"); out.accept(msg.toString()); } /** * @param activityDescription The activity description * @return An ExceptionHandler that prints to {@link System#out} * @see #log(Consumer, String) */ static ExceptionHandler log(String activityDescription) { // A method reference would cache the current value in System.out // This would update if the value in System.out is changed (by for example, applying a PrintStream that handles colours). return log(msg -> System.out.println(msg), activityDescription); } /** * @param out The Consumer to be passed to {@link #log(Consumer, String, Throwable)} * @param activityDescription The activity description to be passed to {@link #log(Consumer, String, Throwable)} * @return An ExceptionHandler that passes exceptions with given arguments to {@link #log(Consumer, String, Throwable)} * @see #log(Consumer, String, Throwable) */ static ExceptionHandler log(Consumer out, String activityDescription) { return ex -> log(out, activityDescription, ex); } /** * This ExceptionHandler turns any Throwable into an unchecked exception, then throws it again. */ ExceptionHandler UNCHECKED = new ExceptionHandler() { @Override public void handle(Throwable ex) { errorFilter(ex); if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } throw new RuntimeException(ex); } @Override public boolean isSafe() { return false; } }; /** * This ExceptionHandler suppresses all exceptions, * apart from {@link Error} because that is not usually supposed to be caught */ ExceptionHandler SUPPRESS = ExceptionHandler::errorFilter; /** * This ExceptionHandler calls {@link Throwable#printStackTrace()} unless it is a {@link NullPointerException} or {@link Error}. */ ExceptionHandler PRINT_UNLESS_NP = ex -> { errorFilter(ex); if (!(ex instanceof NullPointerException)) { ex.printStackTrace(); } }; static void errorFilter(Throwable ex) { if (ex instanceof Error) { throw (Error) ex; } else if (ex == null) { throw new NullPointerException(); } } }