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.internal;
017
018import java.lang.reflect.InvocationTargetException;
019import java.lang.reflect.Method;
020
021import com.googlecode.catchexception.ExceptionNotThrownAssertionError;
022
023/**
024 * This abstract method invocation interceptor
025 * <ul>
026 * <li>delegates all method calls to the 'underlying object',
027 * <li>catches exceptions of a given type, and
028 * <li>(optionally) asserts that an exception of a specific type is thrown.
029 * </ul>
030 * 
031 * @author rwoo
032 * @param <E>
033 *            The type of the exception that is caught and ,optionally,
034 *            verified.
035 * @since 16.09.2011
036 */
037class AbstractExceptionProcessingInvocationHandler<E extends Exception> {
038
039    /**
040     * See
041     * {@link #AbstractExceptionProcessingInvocationHandler(Object, Class, boolean)}
042     * .
043     */
044    protected Class<E> clazz;
045
046    /**
047     * See
048     * {@link #AbstractExceptionProcessingInvocationHandler(Object, Class, boolean)}
049     * .
050     */
051    protected Object target;
052
053    /**
054     * See
055     * {@link #AbstractExceptionProcessingInvocationHandler(Object, Class, boolean)}
056     * .
057     */
058    protected boolean assertException;
059
060    /**
061     * @param target
062     *            The object all method calls are delegated to. Must not be
063     *            <code>null</code>.
064     * @param clazz
065     *            the type of the exception that is to catch. Must not be
066     *            <code>null</code>.
067     * @param assertException
068     *            True if the interceptor shall throw an
069     *            {@link ExceptionNotThrownAssertionError} if the method has not
070     *            thrown an exception of the expected type.
071     */
072    public AbstractExceptionProcessingInvocationHandler(Object target,
073            Class<E> clazz, boolean assertException) {
074
075        if (clazz == null) {
076            throw new IllegalArgumentException(
077                    "exceptionClazz must not be null");
078        }
079        if (target == null) {
080            throw new IllegalArgumentException("target must not be null");
081        }
082
083        this.clazz = clazz;
084        this.target = target;
085        this.assertException = assertException;
086    }
087
088    /**
089     * Must be called by the subclass before the intercepted method invocation.
090     */
091    protected void beforeInvocation() {
092
093        // clear exception cache
094        ExceptionHolder.set(null);
095
096    }
097
098    /**
099     * Must be called by the subclass after the method invocation that has not
100     * thrown a exception.
101     * 
102     * @param retval
103     *            The value returned by the intercepted method invocation.
104     * @return The value that shall be returned by the proxy.
105     * @throws Error
106     *             (optionally) Thrown if the verification failed.
107     */
108    protected Object afterInvocation(Object retval) throws Error {
109
110        if (assertException) {
111
112            throw new ExceptionNotThrownAssertionError(clazz);
113
114        } else {
115
116            // return anything. the value does not matter
117            return retval;
118
119        }
120
121    }
122
123    /**
124     * Must be called by the subclass after the intercepted method invocation
125     * that has thrown a exception.
126     * 
127     * @param e
128     *            the exception thrown by the intercepted method invocation.
129     * @param method
130     *            the method that was proxied
131     * @return Always returns null.
132     * @throws Error
133     *             Thrown if the verification failed.
134     * @throws Exception
135     *             The given exception. (Re-)thrown if the thrown exception
136     *             shall not be caught and verification is not required.
137     */
138    protected Object afterInvocationThrowsException(Exception e, Method method)
139            throws Error, Exception {
140
141        if (e instanceof InvocationTargetException) {
142            Throwable targetThrowable = ((InvocationTargetException) e)
143                    .getTargetException();
144            if (targetThrowable instanceof Exception) {
145                e = (Exception) targetThrowable;
146            } else {
147                throw (Error) targetThrowable;
148            }
149        }
150
151        // is the thrown exception of the expected type?
152        if (!clazz.isAssignableFrom(e.getClass())) {
153
154            if (assertException) {
155                throw new ExceptionNotThrownAssertionError(clazz, e);
156            } else {
157                throw e;
158            }
159
160        } else {
161
162            // save the caught exception for further analysis
163            ExceptionHolder.set(e);
164
165            // return anything. the value does not matter but null could cause a
166            // NullPointerException while un-boxing
167            return safeReturnValue(method.getReturnType());
168        }
169    }
170
171    /**
172     * @param type
173     *            a type
174     * @return Returns a value of the given type
175     */
176    Object safeReturnValue(Class<?> type) {
177        if (!type.isPrimitive()) {
178            return null;
179        } else if (Void.TYPE.equals(type)) {
180            return null; // any value
181        } else if (Byte.TYPE.equals(type)) {
182            return (byte) 0;
183        } else if (Short.TYPE.equals(type)) {
184            return (short) 0;
185        } else if (Integer.TYPE.equals(type)) {
186            return (int) 0;
187        } else if (Long.TYPE.equals(type)) {
188            return (long) 0;
189        } else if (Float.TYPE.equals(type)) {
190            return (float) 0.0;
191        } else if (Double.TYPE.equals(type)) {
192            return (double) 0.0;
193        } else if (Boolean.TYPE.equals(type)) {
194            return (boolean) false;
195        } else if (Character.TYPE.equals(type)) {
196            return (char) ' ';
197        } else {
198            throw new IllegalArgumentException("Type " + type
199                    + " is not supported at the moment. Please file a bug.");
200        }
201    }
202
203}