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}