001/**
002 * Copyright (C) 2011 rwoo@gmx.de
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *         http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package com.googlecode.catchexception.throwable;
017
018/**
019 * @author rwoo
020 * @since 1.2.0
021 */
022public class CatchThrowable {
023
024    /**
025     * Returns the throwable caught during the last call on the proxied object in the current thread.
026     *
027     * @return Returns the throwable caught during the last call on the proxied object in the current thread - if the
028     * call was made through a proxy that has been created via {@link #verifyThrowable(ThrowingCallable, Class)}
029     * verifyThrowable()} or {@link #catchThrowable(ThrowingCallable)}. Returns null the proxy has
030     * not caught an throwable. Returns null if the caught throwable belongs to a class that is no longer
031     * {@link ClassLoader loaded}.
032     *
033     * @param <T> throwable caught during the last call on the proxied object
034     */
035    public static <T extends Throwable> T caughtThrowable() {
036        return ThrowableHolder.get();
037    }
038
039    public static <T extends Throwable> T caughtThrowable(Class<T> caughtThrowableType) {
040        return ThrowableHolder.get();
041    }
042
043    /**
044     * Use it to verify that an throwable is thrown and to get access to the thrown throwable (for further
045     * verifications).
046     *
047     * The following example verifies that obj.doX() throws a Throwable:
048     * <code>verifyThrowable(obj).doX(); // catch and verify
049     * assert "foobar".equals(caughtThrowable().getMessage()); // further analysis
050     * </code>
051     *
052     * If <code>doX()</code> does not throw a <code>Throwable</code>, then a {@link ThrowableNotThrownAssertionError} is
053     * thrown. Otherwise the thrown throwable can be retrieved via {@link #caughtThrowable()}.
054     *
055     *
056     * @param actor The instance that shall be proxied. Must not be <code>null</code>.
057     */
058    public static void verifyThrowable(ThrowingCallable actor) {
059        verifyThrowable(actor, Throwable.class);
060    }
061
062    /**
063     * Use it to verify that an throwable of specific type is thrown and to get access to the thrown throwable (for
064     * further verifications).
065     *
066     * The following example verifies that obj.doX() throws a MyThrowable:
067     * <code>verifyThrowable(obj, MyThrowable.class).doX(); // catch and verify
068     * assert "foobar".equals(caughtThrowable().getMessage()); // further analysis
069     * </code>
070     *
071     * If <code>doX()</code> does not throw a <code>MyThrowable</code>, then a {@link ThrowableNotThrownAssertionError}
072     * is thrown. Otherwise the thrown throwable can be retrieved via {@link #caughtThrowable()}.
073     *
074     *
075     * @param actor   The instance that shall be proxied. Must not be <code>null</code>.
076     * @param clazz The type of the throwable that shall be thrown by the underlying object. Must not be
077     *              <code>null</code>
078     */
079    public static void verifyThrowable(ThrowingCallable actor, Class<? extends Throwable> clazz) {
080        validateArguments(actor, clazz);
081        catchThrowable(actor, clazz, true);
082    }
083
084    /**
085     * Use it to catch an throwable and to get access to the thrown throwable (for further verifications).
086     *
087     * In the following example you catch throwables that are thrown by obj.doX():
088     * <code>catchThrowable(obj).doX(); // catch
089     * if (caughtThrowable() != null) {
090     * assert "foobar".equals(caughtThrowable().getMessage()); // further analysis
091     * }</code> If <code>doX()</code>
092     * throws a throwable, then {@link #caughtThrowable()} will return the caught throwable. If <code>doX()</code> does
093     * not throw a throwable, then {@link #caughtThrowable()} will return <code>null</code>.
094     *
095     *
096     * @param actor The instance that shall be proxied. Must not be <code>null</code>.
097     */
098    public static void catchThrowable(ThrowingCallable actor) {
099        validateArguments(actor, Throwable.class);
100        catchThrowable(actor, Throwable.class, false);
101    }
102
103    /**
104     * Use it to catch an throwable of a specific type and to get access to the thrown throwable (for further
105     * verifications).
106     *
107     * In the following example you catch throwables of type MyThrowable that are thrown by obj.doX():
108     * <code>catchThrowable(obj, MyThrowable.class).doX(); // catch
109     * if (caughtThrowable() != null) {
110     * assert "foobar".equals(caughtThrowable().getMessage()); // further analysis
111     * }</code> If <code>doX()</code>
112     * throws a <code>MyThrowable</code>, then {@link #caughtThrowable()} will return the caught throwable. If
113     * <code>doX()</code> does not throw a <code>MyThrowable</code>, then {@link #caughtThrowable()} will return
114     * <code>null</code>. If <code>doX()</code> throws an throwable of another type, i.e. not a subclass but another
115     * class, then this throwable is not thrown and {@link #caughtThrowable()} will return <code>null</code>.
116     *
117     *
118     * @param actor   The instance that shall be proxied. Must not be <code>null</code>.
119     * @param clazz The type of the throwable that shall be caught. Must not be <code>null</code>.
120     */
121    public static void catchThrowable(ThrowingCallable actor, Class<? extends Throwable> clazz) {
122        validateArguments(actor, clazz);
123        catchThrowable(actor, clazz, false);
124    }
125
126    private static void catchThrowable(ThrowingCallable actor,
127                                       Class<? extends Throwable> clazz, boolean assertException) {
128        resetCaughtThrowable();
129        Throwable throwable = ThrowableCaptor.captureThrowable(actor);
130        if (throwable == null) {
131            if (!assertException) {
132                return;
133            } else {
134                throw new ThrowableNotThrownAssertionError(clazz);
135            }
136        }
137        // is the thrown exception of the expected type?
138        if (clazz.isAssignableFrom(throwable.getClass())) {
139            ThrowableHolder.set(throwable);
140        } else {
141            if (assertException) {
142                throw new ThrowableNotThrownAssertionError(clazz, throwable);
143            } else {
144                ExceptionUtil.sneakyThrow(throwable);
145            }
146        }
147    }
148
149    private static void validateArguments(ThrowingCallable actor, Class<? extends Throwable> clazz) {
150        if (actor == null) throw new IllegalArgumentException("obj must not be null");
151        if (clazz == null) throw new IllegalArgumentException("throwableClazz must not be null");
152    }
153
154    /**
155     * Sets the {@link #caughtThrowable() caught throwable} to null. This does not affect throwables saved at threads
156     * other than the current one.
157     *
158     * Actually you probably never need to call this method because each method call on a proxied object in the current
159     * thread resets the caught throwable. But if you want to improve test isolation or if you want to 'clean up' after
160     * testing (to avoid memory leaks), call the method before or after testing.
161     */
162    public static void resetCaughtThrowable() {
163        ThrowableHolder.set(null);
164    }
165
166}