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 }