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    }