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 }