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.internal;
017    
018    import java.lang.reflect.InvocationTargetException;
019    import java.lang.reflect.Method;
020    
021    import 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     */
036    class 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    }