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