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 }