1 /*
2 * Copyright 2011-2024 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.googlecode.catchexception.throwable;
17
18 /**
19 * The Class CatchThrowable.
20 */
21 public final class CatchThrowable {
22
23 /**
24 * Prevent Instantiation of a new catch exception.
25 */
26 private CatchThrowable() {
27 // Preventing instantiation
28 }
29
30 /**
31 * Returns the throwable caught during the last call on the proxied object in the current thread.
32 *
33 * @param <T>
34 * throwable caught during the last call on the proxied object
35 *
36 * @return Returns the throwable caught during the last call on the proxied object in the current thread - if the
37 * call was made through a proxy that has been created via {@link #verifyThrowable(ThrowingCallable, Class)}
38 * verifyThrowable()} or {@link #catchThrowable(ThrowingCallable)}. Returns null the proxy has not caught an
39 * throwable. Returns null if the caught throwable belongs to a class that is no longer {@link ClassLoader
40 * loaded}.
41 */
42 public static <T extends Throwable> T caughtThrowable() {
43 return ThrowableHolder.get();
44 }
45
46 /**
47 * Caught throwable.
48 *
49 * @param <T>
50 * the generic type
51 * @param caughtThrowableType
52 * the caught throwable type
53 *
54 * @return the t
55 *
56 * @deprecated Use caghtThrowable() instead as the passed argument does nothing
57 */
58 @Deprecated(since = "2.3.0", forRemoval = true)
59 public static <T extends Throwable> T caughtThrowable(Class<T> caughtThrowableType) {
60 return ThrowableHolder.get();
61 }
62
63 /**
64 * Use it to verify that an throwable is thrown and to get access to the thrown throwable (for further
65 * verifications). The following example verifies that obj.doX() throws a Throwable:
66 * <code>verifyThrowable(obj).doX(); // catch and verify
67 * assert "foobar".equals(caughtThrowable().getMessage()); // further analysis
68 * </code> If <code>doX()</code> does not throw a <code>Throwable</code>, then a
69 * {@link ThrowableNotThrownAssertionError} is thrown. Otherwise the thrown throwable can be retrieved via
70 * {@link #caughtThrowable()}.
71 *
72 * @param actor
73 * The instance that shall be proxied. Must not be <code>null</code>.
74 */
75 public static void verifyThrowable(ThrowingCallable actor) {
76 verifyThrowable(actor, Throwable.class);
77 }
78
79 /**
80 * Use it to verify that an throwable of specific type is thrown and to get access to the thrown throwable (for
81 * further verifications). The following example verifies that obj.doX() throws a MyThrowable:
82 * <code>verifyThrowable(obj, MyThrowable.class).doX(); // catch and verify
83 * assert "foobar".equals(caughtThrowable().getMessage()); // further analysis
84 * </code> If <code>doX()</code> does not throw a <code>MyThrowable</code>, then a
85 * {@link ThrowableNotThrownAssertionError} is thrown. Otherwise the thrown throwable can be retrieved via
86 * {@link #caughtThrowable()}.
87 *
88 * @param actor
89 * The instance that shall be proxied. Must not be <code>null</code>.
90 * @param clazz
91 * The type of the throwable that shall be thrown by the underlying object. Must not be <code>null</code>
92 */
93 public static void verifyThrowable(ThrowingCallable actor, Class<? extends Throwable> clazz) {
94 validateArguments(actor, clazz);
95 catchThrowable(actor, clazz, true);
96 }
97
98 /**
99 * Use it to catch an throwable and to get access to the thrown throwable (for further verifications). In the
100 * following example you catch throwables that are thrown by obj.doX(): <code>catchThrowable(obj).doX(); // catch
101 * if (caughtThrowable() != null) {
102 * assert "foobar".equals(caughtThrowable().getMessage()); // further analysis
103 * }</code> If <code>doX()</code> throws a throwable, then {@link #caughtThrowable()} will return the caught
104 * throwable. If <code>doX()</code> does not throw a throwable, then {@link #caughtThrowable()} will return
105 * <code>null</code>.
106 *
107 * @param actor
108 * The instance that shall be proxied. Must not be <code>null</code>.
109 */
110 public static void catchThrowable(ThrowingCallable actor) {
111 validateArguments(actor, Throwable.class);
112 catchThrowable(actor, Throwable.class, false);
113 }
114
115 /**
116 * Use it to catch an throwable of a specific type and to get access to the thrown throwable (for further
117 * verifications). In the following example you catch throwables of type MyThrowable that are thrown by obj.doX():
118 * <code>catchThrowable(obj, MyThrowable.class).doX(); // catch
119 * if (caughtThrowable() != null) {
120 * assert "foobar".equals(caughtThrowable().getMessage()); // further analysis
121 * }</code> If <code>doX()</code> throws a <code>MyThrowable</code>, then {@link #caughtThrowable()} will return the
122 * caught throwable. If <code>doX()</code> does not throw a <code>MyThrowable</code>, then
123 * {@link #caughtThrowable()} will return <code>null</code>. If <code>doX()</code> throws an throwable of another
124 * type, i.e. not a subclass but another class, then this throwable is not thrown and {@link #caughtThrowable()}
125 * will return <code>null</code>.
126 *
127 * @param actor
128 * The instance that shall be proxied. Must not be <code>null</code>.
129 * @param clazz
130 * The type of the throwable that shall be caught. Must not be <code>null</code>.
131 */
132 public static void catchThrowable(ThrowingCallable actor, Class<? extends Throwable> clazz) {
133 validateArguments(actor, clazz);
134 catchThrowable(actor, clazz, false);
135 }
136
137 /**
138 * Catch throwable.
139 *
140 * @param actor
141 * the actor
142 * @param clazz
143 * the clazz
144 * @param assertException
145 * the assert exception
146 */
147 private static void catchThrowable(ThrowingCallable actor, Class<? extends Throwable> clazz,
148 boolean assertException) {
149 resetCaughtThrowable();
150 var throwable = ThrowableCaptor.captureThrowable(actor);
151 if (throwable == null) {
152 if (!assertException) {
153 return;
154 }
155 throw new ThrowableNotThrownAssertionError(clazz);
156 }
157 // is the thrown exception of the expected type?
158 if (clazz.isAssignableFrom(throwable.getClass())) {
159 ThrowableHolder.set(throwable);
160 } else if (assertException) {
161 throw new ThrowableNotThrownAssertionError(clazz, throwable);
162 } else {
163 ExceptionUtil.sneakyThrow(throwable);
164 }
165 }
166
167 /**
168 * Validate arguments.
169 *
170 * @param actor
171 * the actor
172 * @param clazz
173 * the clazz
174 */
175 private static void validateArguments(ThrowingCallable actor, Class<? extends Throwable> clazz) {
176 if (actor == null) {
177 throw new IllegalArgumentException("obj must not be null");
178 }
179 if (clazz == null) {
180 throw new IllegalArgumentException("throwableClazz must not be null");
181 }
182 }
183
184 /**
185 * Sets the {@link #caughtThrowable() caught throwable} to null. This does not affect throwables saved at threads
186 * other than the current one. Actually you probably never need to call this method because each method call on a
187 * proxied object in the current thread resets the caught throwable. But if you want to improve test isolation or if
188 * you want to 'clean up' after testing (to avoid memory leaks), call the method before or after testing.
189 */
190 public static void resetCaughtThrowable() {
191 ThrowableHolder.set(null);
192 }
193
194 }