1 /*
2 * SPDX-FileCopyrightText: none
3 * SPDX-License-Identifier: CC0-1.0
4 */
5
6 package gov.nist.secauto.metaschema.core.util;
7
8 import java.util.Arrays;
9 import java.util.Objects;
10
11 import edu.umd.cs.findbugs.annotations.NonNull;
12
13 /**
14 * Provides a means for throwing important checked exceptions over non-checked
15 * methods, e.g. lambda invocations.
16 * <p>
17 * This capability should be used with care, and generally in limited
18 * circumstances.
19 */
20 public final class ExceptionUtils {
21 /**
22 * Wrap a checked exception in an unchecked {@link WrappedException}.
23 *
24 * @param ex
25 * the exception to wrap
26 * @return a new wrapped exception containing the provided exception
27 */
28 @NonNull
29 public static WrappedException wrap(@NonNull Throwable ex) {
30 return new WrappedException(Objects.requireNonNull(ex, "ex"));
31 }
32
33 /**
34 * Wrap a checked exception in an unchecked {@link WrappedException}.
35 * <p>
36 * This method is identical to {@link #wrap(Throwable)} but named to indicate
37 * intent when used in throw statements.
38 *
39 * @param ex
40 * the exception to wrap
41 * @return a new wrapped exception containing the provided exception
42 */
43 @NonNull
44 public static WrappedException wrapAndThrow(@NonNull Throwable ex) {
45 return wrap(ex);
46 }
47
48 /**
49 * Unwrap a previously wrapped exception.
50 *
51 * @param ex
52 * the wrapped exception to unwrap
53 * @return the original exception that was wrapped
54 */
55 @NonNull
56 public static Throwable unwrap(
57 @NonNull WrappedException ex) {
58 return ex.unwrap();
59 }
60
61 /**
62 * Unwrap a previously wrapped exception, casting it to the expected type.
63 *
64 * @param <E>
65 * the expected exception type
66 * @param ex
67 * the wrapped exception to unwrap
68 * @param wrappedExceptionClass
69 * the class of the expected exception type
70 * @return the original exception cast to the expected type
71 * @throws IllegalArgumentException
72 * if the wrapped exception is not of the expected type
73 */
74 @NonNull
75 public static <E extends Throwable> E unwrap(
76 @NonNull WrappedException ex,
77 @NonNull Class<E> wrappedExceptionClass) {
78 return ex.unwrap(wrappedExceptionClass);
79 }
80
81 /**
82 * A runtime exception that wraps a checked exception, allowing it to be thrown
83 * from contexts that do not allow checked exceptions (such as lambda
84 * expressions).
85 */
86 public static final class WrappedException
87 extends RuntimeException {
88
89 /**
90 * the serial version UID.
91 */
92 private static final long serialVersionUID = 2L;
93
94 /**
95 * Construct a new wrapped exception.
96 *
97 * @param cause
98 * the exception to wrap
99 */
100 public WrappedException(@NonNull Throwable cause) {
101 super(Objects.requireNonNull(cause, "cause"));
102 }
103
104 @Override
105 public synchronized Throwable initCause(Throwable cause) {
106 throw new UnsupportedOperationException("must set cause in constructor");
107 }
108
109 /**
110 * Get the wrapped exception.
111 *
112 * @return the original exception that was wrapped
113 */
114 @NonNull
115 public Throwable unwrap() {
116 return ObjectUtils.notNull(getCause());
117 }
118
119 /**
120 * Get the wrapped exception, casting it to the expected type.
121 *
122 * @param <E>
123 * the expected exception type
124 * @param wrappedExceptionClass
125 * the class of the expected exception type
126 * @return the original exception cast to the expected type
127 * @throws IllegalArgumentException
128 * if the wrapped exception is not of the expected type
129 */
130 @NonNull
131 public <E extends Throwable> E unwrap(@NonNull Class<E> wrappedExceptionClass) {
132 Throwable cause = unwrap();
133 if (wrappedExceptionClass.isInstance(cause)) {
134 E unwrappedEx = wrappedExceptionClass.cast(cause);
135 // Avoid adding duplicate suppressed exceptions on repeated unwraps
136 boolean alreadySuppressed = Arrays.stream(unwrappedEx.getSuppressed())
137 .anyMatch(s -> s == this);
138 if (!alreadySuppressed) {
139 unwrappedEx.addSuppressed(this);
140 }
141 return unwrappedEx;
142 }
143 throw new IllegalArgumentException(
144 String.format("Wrapped exception '%s' did not match expected type '%s'.",
145 cause.getClass().getName(),
146 wrappedExceptionClass.getName()));
147 }
148
149 /**
150 * Unwrap and throw the original exception.
151 *
152 * @throws Throwable
153 * the original wrapped exception
154 */
155 public void unwrapAndThrow() throws Throwable {
156 throw unwrap();
157 }
158
159 /**
160 * Unwrap and throw the original exception, cast to the expected type.
161 *
162 * @param <E>
163 * the expected exception type
164 * @param wrappedExceptionClass
165 * the class of the expected exception type
166 * @throws E
167 * the original wrapped exception cast to the expected type
168 */
169 public <E extends Throwable> void unwrapAndThrow(@NonNull Class<E> wrappedExceptionClass) throws E {
170 throw unwrap(wrappedExceptionClass);
171 }
172 }
173
174 private ExceptionUtils() {
175 // prevent construction
176 }
177 }