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     */
016    package com.googlecode.catchexception.throwable;
017    
018    import org.assertj.core.internal.cglib.proxy.MethodInterceptor;
019    
020    import com.googlecode.catchexception.throwable.internal.DelegatingInterceptor;
021    import com.googlecode.catchexception.throwable.internal.InterfaceOnlyProxyFactory;
022    import com.googlecode.catchexception.throwable.internal.SubclassProxyFactory;
023    import com.googlecode.catchexception.throwable.internal.ThrowableHolder;
024    import com.googlecode.catchexception.throwable.internal.ThrowableProcessingInterceptor;
025    
026    /**
027     * 
028     * @author rwoo
029     * @since 1.2.0
030     * 
031     */
032    public class CatchThrowable {
033    
034        /**
035         * Returns the throwable caught during the last call on the proxied object in the current thread.
036         * 
037         * @param <E>
038         *            This type parameter makes some type casts redundant.
039         * @return Returns the throwable caught during the last call on the proxied object in the current thread - if the
040         *         call was made through a proxy that has been created via {@link #verifyThrowable(Object, Class)
041         *         verifyThrowable()} or {@link #catchThrowable(Object, Class) catchThrowable()}. Returns null the proxy has
042         *         not caught an throwable. Returns null if the caught throwable belongs to a class that is no longer
043         *         {@link ClassLoader loaded}.
044         */
045        public static <E extends Throwable> E  caughtThrowable() {
046            return ThrowableHolder.get();
047        }
048    
049        /**
050         * Use it to verify that an throwable is thrown and to get access to the thrown throwable (for further
051         * verifications).
052         * <p>
053         * The following example verifies that obj.doX() throws a Throwable:
054         * <code><pre class="prettyprint lang-java">verifyThrowable(obj).doX(); // catch and verify
055    assert "foobar".equals(caughtThrowable().getMessage()); // further analysis
056    </pre></code>
057         * <p>
058         * If <code>doX()</code> does not throw a <code>Throwable</code>, then a {@link ThrowableNotThrownAssertionError} is
059         * thrown. Otherwise the thrown throwable can be retrieved via {@link #caughtThrowable()}.
060         * <p>
061         * 
062         * @param <T>
063         *            The type of the given <code>obj</code>.
064         * 
065         * @param obj
066         *            The instance that shall be proxied. Must not be <code>null</code>.
067         * @return Returns an object that verifies that each invocation on the underlying object throws an throwable.
068         */
069        public static <T> T verifyThrowable(T obj) {
070            return verifyThrowable(obj, Throwable.class);
071        }
072    
073        /**
074         * Use it to verify that an throwable of specific type is thrown and to get access to the thrown throwable (for
075         * further verifications).
076         * <p>
077         * The following example verifies that obj.doX() throws a MyThrowable:
078         * <code><pre class="prettyprint lang-java">verifyThrowable(obj, MyThrowable.class).doX(); // catch and verify
079    assert "foobar".equals(caughtThrowable().getMessage()); // further analysis
080    </pre></code>
081         * <p>
082         * If <code>doX()</code> does not throw a <code>MyThrowable</code>, then a {@link ThrowableNotThrownAssertionError}
083         * is thrown. Otherwise the thrown throwable can be retrieved via {@link #caughtThrowable()}.
084         * <p>
085         * 
086         * @param <T>
087         *            The type of the given <code>obj</code>.
088         * 
089         * @param <E>
090         *            The type of the throwable that shall be caught.
091         * @param obj
092         *            The instance that shall be proxied. Must not be <code>null</code>.
093         * @param clazz
094         *            The type of the throwable that shall be thrown by the underlying object. Must not be <code>null</code>
095         *            .
096         * @return Returns an object that verifies that each invocation on the underlying object throws an throwable of the
097         *         given type.
098         */
099        public static <T, E extends Throwable> T verifyThrowable(T obj, Class<E> clazz) {
100    
101            return processThrowable(obj, clazz, true);
102        }
103    
104        /**
105         * Use it to catch an throwable and to get access to the thrown throwable (for further verifications).
106         * <p>
107         * In the following example you catch throwables that are thrown by obj.doX():
108         * <code><pre class="prettyprint lang-java">catchThrowable(obj).doX(); // catch
109    if (caughtThrowable() != null) {
110        assert "foobar".equals(caughtThrowable().getMessage()); // further analysis
111    }</pre></code> If <code>doX()</code>
112         * throws a throwable, then {@link #caughtThrowable()} will return the caught throwable. If <code>doX()</code> does
113         * not throw a throwable, then {@link #caughtThrowable()} will return <code>null</code>.
114         * <p>
115         * 
116         * @param <T>
117         *            The type of the given <code>obj</code>.
118         * 
119         * @param obj
120         *            The instance that shall be proxied. Must not be <code>null</code>.
121         * @return Returns a proxy for the given object. The proxy catches throwables of the given type when a method on the
122         *         proxy is called.
123         */
124        public static <T> T catchThrowable(T obj) {
125    
126            return processThrowable(obj, Throwable.class, false);
127        }
128    
129        /**
130         * Use it to catch an throwable of a specific type and to get access to the thrown throwable (for further
131         * verifications).
132         * <p>
133         * In the following example you catch throwables of type MyThrowable that are thrown by obj.doX():
134         * <code><pre class="prettyprint lang-java">catchThrowable(obj, MyThrowable.class).doX(); // catch
135    if (caughtThrowable() != null) {
136        assert "foobar".equals(caughtThrowable().getMessage()); // further analysis
137    }</pre></code> If <code>doX()</code>
138         * throws a <code>MyThrowable</code>, then {@link #caughtThrowable()} will return the caught throwable. If
139         * <code>doX()</code> does not throw a <code>MyThrowable</code>, then {@link #caughtThrowable()} will return
140         * <code>null</code>. If <code>doX()</code> throws an throwable of another type, i.e. not a subclass but another
141         * class, then this throwable is not thrown and {@link #caughtThrowable()} will return <code>null</code>.
142         * <p>
143         * 
144         * @param <T>
145         *            The type of the given <code>obj</code>.
146         * 
147         * @param <E>
148         *            The type of the throwable that shall be caught.
149         * @param obj
150         *            The instance that shall be proxied. Must not be <code>null</code>.
151         * @param clazz
152         *            The type of the throwable that shall be caught. Must not be <code>null</code>.
153         * @return Returns a proxy for the given object. The proxy catches throwables of the given type when a method on the
154         *         proxy is called.
155         */
156        public static <T, E extends Throwable> T catchThrowable(T obj, Class<E> clazz) {
157    
158            return processThrowable(obj, clazz, false);
159        }
160    
161        /**
162         * Creates a proxy that processes throwables thrown by the underlying object.
163         * <p>
164         * Delegates to {@link SubclassProxyFactory#createProxy(Class, MethodInterceptor)} which itself might delegate to
165         * {@link InterfaceOnlyProxyFactory#createProxy(Class, MethodInterceptor)}.
166         */
167        @SuppressWarnings("javadoc")
168        private static <T, E extends Throwable> T processThrowable(T obj, Class<E> throwableClazz, boolean assertThrowable) {
169    
170            if (obj == null) {
171                throw new IllegalArgumentException("obj must not be null");
172            }
173    
174            return new SubclassProxyFactory().<T> createProxy(obj.getClass(), new ThrowableProcessingInterceptor<E>(obj,
175                    throwableClazz, assertThrowable));
176    
177        }
178    
179        /**
180         * Returns a proxy that implements all interfaces of the underlying object.
181         * 
182         * @param <T>
183         *            must be an interface the object implements
184         * @param obj
185         *            the object that created proxy will delegate all calls to
186         * @return Returns a proxy that implements all interfaces of the underlying object and delegates all calls to that
187         *         underlying object.
188         */
189        public static <T> T interfaces(T obj) {
190    
191            if (obj == null) {
192                throw new IllegalArgumentException("obj must not be null");
193            }
194    
195            return new InterfaceOnlyProxyFactory().<T> createProxy(obj.getClass(), new DelegatingInterceptor(obj));
196        }
197    
198        /**
199         * Sets the {@link #caughtThrowable() caught throwable} to null. This does not affect throwables saved at threads
200         * other than the current one.
201         * <p>
202         * Actually you probably never need to call this method because each method call on a proxied object in the current
203         * thread resets the caught throwable. But if you want to improve test isolation or if you want to 'clean up' after
204         * testing (to avoid memory leaks), call the method before or after testing.
205         */
206        public static void resetCaughtThrowable() {
207            ThrowableHolder.set(null);
208        }
209    
210    }