summaryrefslogtreecommitdiff
path: root/dicore3/core/src/main/java/io/dico/dicore/exceptions/ExceptionHandler.java
blob: e95b65e9ef4bcdbb43a72d27ac2be8455b77b444 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
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 <b>never</b> 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.
     * <p>
     * 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<? extends Throwable> 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.
     * <p>
     * Any exceptions thrown by this handler are delegated to the caller.
     *
     * @param action The supplier whose result to compute
     * @param <T>    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> T supplySafe(CheckedSupplier<T, ? extends Throwable> 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<? extends Throwable> 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 <T> Supplier<T> safeSupplier(CheckedSupplier<T, ? extends Throwable> supplier) {
        return () -> supplySafe(supplier);
    }
    
    /**
     * Logs the given exception as an error to {@code out}
     * <p>
     * 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<String> 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<String> 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();
        }
    }
    
}