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}