/** * Copyright (C) 2011 rwoo@gmx.de * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.googlecode.catchexception.throwable; import org.assertj.core.internal.cglib.proxy.MethodInterceptor; import com.googlecode.catchexception.throwable.internal.DelegatingInterceptor; import com.googlecode.catchexception.throwable.internal.InterfaceOnlyProxyFactory; import com.googlecode.catchexception.throwable.internal.SubclassProxyFactory; import com.googlecode.catchexception.throwable.internal.ThrowableHolder; import com.googlecode.catchexception.throwable.internal.ThrowableProcessingInterceptor; /** * * @author rwoo * @since 1.2.0 * */ public class CatchThrowable { /** * Returns the throwable caught during the last call on the proxied object in the current thread. * * @param <E> * This type parameter makes some type casts redundant. * @return Returns the throwable caught during the last call on the proxied object in the current thread - if the * call was made through a proxy that has been created via {@link #verifyThrowable(Object, Class) * verifyThrowable()} or {@link #catchThrowable(Object, Class) catchThrowable()}. Returns null the proxy has * not caught an throwable. Returns null if the caught throwable belongs to a class that is no longer * {@link ClassLoader loaded}. */ public static <E extends Throwable> E caughtThrowable() { return ThrowableHolder.get(); } /** * Use it to verify that an throwable is thrown and to get access to the thrown throwable (for further * verifications). * <p> * The following example verifies that obj.doX() throws a Throwable: * <code><pre class="prettyprint lang-java">verifyThrowable(obj).doX(); // catch and verify assert "foobar".equals(caughtThrowable().getMessage()); // further analysis </pre></code> * <p> * If <code>doX()</code> does not throw a <code>Throwable</code>, then a {@link ThrowableNotThrownAssertionError} is * thrown. Otherwise the thrown throwable can be retrieved via {@link #caughtThrowable()}. * <p> * * @param <T> * The type of the given <code>obj</code>. * * @param obj * The instance that shall be proxied. Must not be <code>null</code>. * @return Returns an object that verifies that each invocation on the underlying object throws an throwable. */ public static <T> T verifyThrowable(T obj) { return verifyThrowable(obj, Throwable.class); } /** * Use it to verify that an throwable of specific type is thrown and to get access to the thrown throwable (for * further verifications). * <p> * The following example verifies that obj.doX() throws a MyThrowable: * <code><pre class="prettyprint lang-java">verifyThrowable(obj, MyThrowable.class).doX(); // catch and verify assert "foobar".equals(caughtThrowable().getMessage()); // further analysis </pre></code> * <p> * If <code>doX()</code> does not throw a <code>MyThrowable</code>, then a {@link ThrowableNotThrownAssertionError} * is thrown. Otherwise the thrown throwable can be retrieved via {@link #caughtThrowable()}. * <p> * * @param <T> * The type of the given <code>obj</code>. * * @param <E> * The type of the throwable that shall be caught. * @param obj * The instance that shall be proxied. Must not be <code>null</code>. * @param clazz * The type of the throwable that shall be thrown by the underlying object. Must not be <code>null</code> * . * @return Returns an object that verifies that each invocation on the underlying object throws an throwable of the * given type. */ public static <T, E extends Throwable> T verifyThrowable(T obj, Class<E> clazz) { return processThrowable(obj, clazz, true); } /** * Use it to catch an throwable and to get access to the thrown throwable (for further verifications). * <p> * In the following example you catch throwables that are thrown by obj.doX(): * <code><pre class="prettyprint lang-java">catchThrowable(obj).doX(); // catch if (caughtThrowable() != null) { assert "foobar".equals(caughtThrowable().getMessage()); // further analysis }</pre></code> If <code>doX()</code> * throws a throwable, then {@link #caughtThrowable()} will return the caught throwable. If <code>doX()</code> does * not throw a throwable, then {@link #caughtThrowable()} will return <code>null</code>. * <p> * * @param <T> * The type of the given <code>obj</code>. * * @param obj * The instance that shall be proxied. Must not be <code>null</code>. * @return Returns a proxy for the given object. The proxy catches throwables of the given type when a method on the * proxy is called. */ public static <T> T catchThrowable(T obj) { return processThrowable(obj, Throwable.class, false); } /** * Use it to catch an throwable of a specific type and to get access to the thrown throwable (for further * verifications). * <p> * In the following example you catch throwables of type MyThrowable that are thrown by obj.doX(): * <code><pre class="prettyprint lang-java">catchThrowable(obj, MyThrowable.class).doX(); // catch if (caughtThrowable() != null) { assert "foobar".equals(caughtThrowable().getMessage()); // further analysis }</pre></code> If <code>doX()</code> * throws a <code>MyThrowable</code>, then {@link #caughtThrowable()} will return the caught throwable. If * <code>doX()</code> does not throw a <code>MyThrowable</code>, then {@link #caughtThrowable()} will return * <code>null</code>. If <code>doX()</code> throws an throwable of another type, i.e. not a subclass but another * class, then this throwable is not thrown and {@link #caughtThrowable()} will return <code>null</code>. * <p> * * @param <T> * The type of the given <code>obj</code>. * * @param <E> * The type of the throwable that shall be caught. * @param obj * The instance that shall be proxied. Must not be <code>null</code>. * @param clazz * The type of the throwable that shall be caught. Must not be <code>null</code>. * @return Returns a proxy for the given object. The proxy catches throwables of the given type when a method on the * proxy is called. */ public static <T, E extends Throwable> T catchThrowable(T obj, Class<E> clazz) { return processThrowable(obj, clazz, false); } /** * Creates a proxy that processes throwables thrown by the underlying object. * <p> * Delegates to {@link SubclassProxyFactory#createProxy(Class, MethodInterceptor)} which itself might delegate to * {@link InterfaceOnlyProxyFactory#createProxy(Class, MethodInterceptor)}. */ @SuppressWarnings("javadoc") private static <T, E extends Throwable> T processThrowable(T obj, Class<E> throwableClazz, boolean assertThrowable) { if (obj == null) { throw new IllegalArgumentException("obj must not be null"); } return new SubclassProxyFactory().<T> createProxy(obj.getClass(), new ThrowableProcessingInterceptor<E>(obj, throwableClazz, assertThrowable)); } /** * Returns a proxy that implements all interfaces of the underlying object. * * @param <T> * must be an interface the object implements * @param obj * the object that created proxy will delegate all calls to * @return Returns a proxy that implements all interfaces of the underlying object and delegates all calls to that * underlying object. */ public static <T> T interfaces(T obj) { if (obj == null) { throw new IllegalArgumentException("obj must not be null"); } return new InterfaceOnlyProxyFactory().<T> createProxy(obj.getClass(), new DelegatingInterceptor(obj)); } /** * Sets the {@link #caughtThrowable() caught throwable} to null. This does not affect throwables saved at threads * other than the current one. * <p> * Actually you probably never need to call this method because each method call on a proxied object in the current * thread resets the caught throwable. But if you want to improve test isolation or if you want to 'clean up' after * testing (to avoid memory leaks), call the method before or after testing. */ public static void resetCaughtThrowable() { ThrowableHolder.set(null); } }