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}