View Javadoc
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 }