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