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 }