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.util.Defense;
018    import org.apache.tapestry.IPage;
019    import org.apache.tapestry.engine.ILink;
020    import org.apache.tapestry.event.ResetEventListener;
021    
022    import java.lang.reflect.Method;
023    import java.lang.reflect.Modifier;
024    import java.util.*;
025    
026    /**
027     * @author Howard M. Lewis Ship
028     * @since 4.0
029     */
030    public class ListenerMapSourceImpl implements ListenerMapSource, ResetEventListener
031    {
032    
033        /**
034         * Sorts {@link Method}s into descending order by parameter count.
035         */
036    
037        private static class ParameterCountComparator implements Comparator
038        {
039    
040            public int compare(Object o1, Object o2)
041            {
042                Method m1 = (Method) o1;
043                Method m2 = (Method) o2;
044    
045                return m2.getParameterTypes().length
046                        - m1.getParameterTypes().length;
047            }
048    
049        }
050    
051        /**
052         * Keyed on Class, value is a Map. The inner Map is an invoker map ... keyed
053         * on listener method name, value is
054         * {@link org.apache.tapestry.listener.ListenerMethodInvoker}.
055         */
056    
057        private final Map _classToInvokerMap = new HashMap();
058    
059        public ListenerMap getListenerMapForObject(Object object)
060        {
061            Defense.notNull(object, "object");
062    
063            Class objectClass = object.getClass();
064    
065            Map invokerMap = findInvokerMap(objectClass);
066    
067            return new ListenerMapImpl(object, invokerMap);
068        }
069    
070        public synchronized void resetEventDidOccur()
071        {
072            _classToInvokerMap.clear();
073        }
074    
075        private synchronized Map findInvokerMap(Class targetClass)
076        {
077            Map result = (Map) _classToInvokerMap.get(targetClass);
078    
079            if (result == null)
080            {
081                result = buildInvokerMapForClass(targetClass);
082                _classToInvokerMap.put(targetClass, result);
083            }
084    
085            return result;
086        }
087    
088        private Map buildInvokerMapForClass(Class targetClass)
089        {
090            // map, keyed on method name, value is List of Method
091            // only methods that return void, return String, or return
092            // something assignable to IPage are kept.
093    
094            Map map = new HashMap();
095    
096            Method[] methods = targetClass.getMethods();
097    
098            // Sort all the arrays, just once, and the methods will be
099            // added to the individual lists in the correct order
100            // (descending by parameter count).
101    
102            Arrays.sort(methods, new ParameterCountComparator());
103    
104            for(int i = 0; i < methods.length; i++)
105            {
106                Method m = methods[i];
107    
108                if (!isAcceptibleListenerMethodReturnType(m)) continue;
109    
110                if (Modifier.isStatic(m.getModifiers())) continue;
111    
112                String name = m.getName();
113    
114                addMethodToMappedList(map, m, name);
115            }
116    
117            return convertMethodListMapToInvokerMap(map);
118        }
119    
120        boolean isAcceptibleListenerMethodReturnType(Method m)
121        {
122            Class returnType = m.getReturnType();
123    
124            if (returnType == void.class || returnType == String.class)
125                return true;
126    
127            return IPage.class.isAssignableFrom(returnType)
128                    || ILink.class.isAssignableFrom(returnType);
129        }
130    
131        private Map convertMethodListMapToInvokerMap(Map map)
132        {
133            Map result = new HashMap();
134    
135            Iterator i = map.entrySet().iterator();
136            while(i.hasNext())
137            {
138                Map.Entry e = (Map.Entry) i.next();
139    
140                String name = (String) e.getKey();
141                List methodList = (List) e.getValue();
142    
143                Method[] methods = convertMethodListToArray(methodList);
144    
145                ListenerMethodInvoker invoker = createListenerMethodInvoker(name,
146                        methods);
147    
148                result.put(name, invoker);
149            }
150    
151            return result;
152        }
153    
154        /**
155         * This implementation returns a new {@link ListenerMethodInvoker}.
156         * Subclasses can override to provide their own implementation.
157         */
158    
159        protected ListenerMethodInvoker createListenerMethodInvoker(String name,
160                Method[] methods)
161        {
162            return new ListenerMethodInvokerImpl(name, methods);
163        }
164    
165        private Method[] convertMethodListToArray(List methodList)
166        {
167            int size = methodList.size();
168            Method[] result = new Method[size];
169    
170            return (Method[]) methodList.toArray(result);
171        }
172    
173        private void addMethodToMappedList(Map map, Method m, String name)
174        {
175            List l = (List) map.get(name);
176    
177            if (l == null)
178            {
179                l = new ArrayList();
180                map.put(name, l);
181            }
182    
183            l.add(m);
184        }
185    }