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
015package org.apache.tapestry.listener;
016
017import java.lang.reflect.Method;
018import java.lang.reflect.Modifier;
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.Comparator;
022import java.util.HashMap;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Map;
026
027import org.apache.hivemind.util.Defense;
028import org.apache.tapestry.IPage;
029import org.apache.tapestry.engine.ILink;
030import org.apache.tapestry.event.ResetEventListener;
031
032/**
033 * @author Howard M. Lewis Ship
034 * @since 4.0
035 */
036public 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}