001 // Copyright 2005 The Apache Software Foundation 002 // 003 // Licensed under the Apache License, Version 2.0 (the "License"); 004 // you may not use this file except in compliance with the License. 005 // You may obtain a copy of the License at 006 // 007 // http://www.apache.org/licenses/LICENSE-2.0 008 // 009 // Unless required by applicable law or agreed to in writing, software 010 // distributed under the License is distributed on an "AS IS" BASIS, 011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 // See the License for the specific language governing permissions and 013 // limitations under the License. 014 015 package org.apache.tapestry.listener; 016 017 import org.apache.hivemind.ApplicationRuntimeException; 018 import org.apache.hivemind.util.Defense; 019 import org.apache.tapestry.IPage; 020 import org.apache.tapestry.IRequestCycle; 021 import org.apache.tapestry.engine.ILink; 022 import org.apache.tapestry.event.BrowserEvent; 023 024 import java.lang.reflect.InvocationTargetException; 025 import java.lang.reflect.Method; 026 import java.util.ArrayList; 027 import java.util.List; 028 029 /** 030 * Logic for mapping a listener method name to an actual method invocation; this 031 * may require a little searching to find the correct version of the method, 032 * based on the number of parameters to the method (there's a lot of flexibility 033 * in terms of what methods may be considered a listener method). 034 * 035 * @author Howard M. Lewis Ship 036 * @since 4.0 037 */ 038 public class ListenerMethodInvokerImpl implements ListenerMethodInvoker 039 { 040 041 /** 042 * Used as default byte value in null method parameters for native types 043 */ 044 private static final byte DEFAULT_BYTE = -1; 045 046 /** 047 * Used as default short value in null method parameters for native types 048 */ 049 private static final short DEFAULT_SHORT = -1; 050 051 /** 052 * Methods with a name appropriate for this class, sorted into descending 053 * order by number of parameters. 054 */ 055 056 private final Method[] _methods; 057 058 /** 059 * The listener method name, used in some error messages. 060 */ 061 062 private final String _name; 063 064 public ListenerMethodInvokerImpl(String name, Method[] methods) 065 { 066 Defense.notNull(name, "name"); 067 Defense.notNull(methods, "methods"); 068 069 _name = name; 070 _methods = methods; 071 } 072 073 public void invokeListenerMethod(Object target, IRequestCycle cycle) 074 { 075 Object[] listenerParameters = cycle.getListenerParameters(); 076 077 if (listenerParameters == null) 078 listenerParameters = new Object[0]; 079 080 if (searchAndInvoke(target, cycle, listenerParameters)) 081 return; 082 083 throw new ApplicationRuntimeException(ListenerMessages.noListenerMethodFound(_name, listenerParameters, target), 084 target, null, null); 085 } 086 087 private boolean searchAndInvoke(Object target, IRequestCycle cycle, Object[] listenerParameters) 088 { 089 BrowserEvent event = null; 090 if (listenerParameters.length > 0 091 && BrowserEvent.class.isInstance(listenerParameters[listenerParameters.length - 1])) 092 event = (BrowserEvent)listenerParameters[listenerParameters.length - 1]; 093 094 List invokeParms = new ArrayList(); 095 096 Method possibleMethod = null; 097 098 methods: 099 for (int i = 0; i < _methods.length; i++, invokeParms.clear()) { 100 101 if (!_methods[i].getName().equals(_name)) 102 continue; 103 104 Class[] parms = _methods[i].getParameterTypes(); 105 106 // impossible to call this 107 108 if (parms.length > (listenerParameters.length + 1) ) { 109 110 if (possibleMethod == null) 111 possibleMethod = _methods[i]; 112 else if (parms.length < possibleMethod.getParameterTypes().length) 113 possibleMethod = _methods[i]; 114 115 continue; 116 } 117 118 int listenerIndex = 0; 119 for (int p = 0; p < parms.length && listenerIndex < (listenerParameters.length + 1); p++) { 120 121 // special case for BrowserEvent 122 if (BrowserEvent.class.isAssignableFrom(parms[p])) { 123 if (event == null) 124 continue methods; 125 126 if (!invokeParms.contains(event)) 127 invokeParms.add(event); 128 129 continue; 130 } 131 132 // special case for request cycle 133 if (IRequestCycle.class.isAssignableFrom(parms[p])) { 134 invokeParms.add(cycle); 135 continue; 136 } 137 138 if (event != null && listenerIndex < (listenerParameters.length + 1) 139 || listenerIndex < listenerParameters.length) { 140 invokeParms.add(listenerParameters[listenerIndex]); 141 listenerIndex++; 142 } 143 } 144 145 if (invokeParms.size() != parms.length) { 146 147 // set possible method just in case 148 149 if (possibleMethod == null) 150 possibleMethod = _methods[i]; 151 else if (parms.length < possibleMethod.getParameterTypes().length) 152 possibleMethod = _methods[i]; 153 154 continue; 155 } 156 157 invokeListenerMethod(_methods[i], target, cycle, invokeParms.toArray(new Object[invokeParms.size()])); 158 159 return true; 160 } 161 162 // if we didn't have enough parameters but still found a matching method name go ahead 163 // and do your best to fill in the parameters and invoke it 164 165 if (possibleMethod != null) { 166 167 Class[] parms = possibleMethod.getParameterTypes(); 168 Object[] args = new Object[parms.length]; 169 170 for (int p=0; p < parms.length; p++) { 171 172 // setup primitive defaults 173 174 if (parms[p].isPrimitive()) { 175 176 if (parms[p] == Boolean.TYPE) { 177 178 args[p] = Boolean.FALSE; 179 } else if (parms[p] == Byte.TYPE) { 180 181 args[p] = new Byte(DEFAULT_BYTE); 182 } else if (parms[p] == Short.TYPE) { 183 184 args[p] = new Short(DEFAULT_SHORT); 185 } else if (parms[p] == Integer.TYPE) { 186 187 args[p] = new Integer(-1); 188 } else if (parms[p] == Long.TYPE) { 189 190 args[p] = new Long(-1); 191 } else if (parms[p] == Float.TYPE) { 192 193 args[p] = new Float(-1); 194 } else if (parms[p] == Double.TYPE) { 195 196 args[p] = new Double(-1); 197 } 198 } 199 200 if (IRequestCycle.class.isAssignableFrom(parms[p])) { 201 args[p] = cycle; 202 } 203 } 204 205 invokeListenerMethod(possibleMethod, target, cycle, args); 206 207 return true; 208 } 209 210 return false; 211 } 212 213 private void invokeListenerMethod(Method listenerMethod, Object target, 214 IRequestCycle cycle, Object[] parameters) 215 { 216 217 Object methodResult = null; 218 219 try 220 { 221 methodResult = invokeTargetMethod(target, listenerMethod, parameters); 222 } 223 catch (InvocationTargetException ex) 224 { 225 Throwable targetException = ex.getTargetException(); 226 227 if (targetException instanceof ApplicationRuntimeException) 228 throw (ApplicationRuntimeException) targetException; 229 230 throw new ApplicationRuntimeException(ListenerMessages.listenerMethodFailure(listenerMethod, target, 231 targetException), target, null, targetException); 232 } 233 catch (Exception ex) 234 { 235 throw new ApplicationRuntimeException(ListenerMessages.listenerMethodFailure(listenerMethod, target, ex), target, 236 null, ex); 237 238 } 239 240 // void methods return null 241 242 if (methodResult == null) return; 243 244 // The method scanner, inside ListenerMapSourceImpl, 245 // ensures that only methods that return void, String, 246 // or assignable to ILink or IPage are considered. 247 248 if (methodResult instanceof String) 249 { 250 cycle.activate((String) methodResult); 251 return; 252 } 253 254 if (methodResult instanceof ILink) 255 { 256 ILink link = (ILink) methodResult; 257 258 String url = link.getAbsoluteURL(); 259 260 cycle.sendRedirect(url); 261 return; 262 } 263 264 cycle.activate((IPage) methodResult); 265 } 266 267 /** 268 * Provided as a hook so that subclasses can perform any additional work 269 * before or after invoking the listener method. 270 */ 271 272 protected Object invokeTargetMethod(Object target, Method listenerMethod, 273 Object[] parameters) 274 throws IllegalAccessException, InvocationTargetException 275 { 276 return listenerMethod.invoke(target, parameters); 277 } 278 279 280 public String getMethodName() 281 { 282 return _name; 283 } 284 285 public String toString() 286 { 287 return "ListenerMethodInvokerImpl[" + 288 "_name='" + _name + '\'' + 289 ']'; 290 } 291 }