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 }