|
|||||||||||||||||||
30 day Evaluation Version distributed via the Maven Jar Repository. Clover is not free. You have 30 days to evaluate it. Please visit http://www.thecortex.net/clover to obtain a licensed version of Clover | |||||||||||||||||||
Source file | Conditionals | Statements | Methods | TOTAL | |||||||||||||||
ListenerMap.java | 83.3% | 91.4% | 100% | 89.7% |
|
1 |
// Copyright 2004, 2005 The Apache Software Foundation
|
|
2 |
//
|
|
3 |
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4 |
// you may not use this file except in compliance with the License.
|
|
5 |
// You may obtain a copy of the License at
|
|
6 |
//
|
|
7 |
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8 |
//
|
|
9 |
// Unless required by applicable law or agreed to in writing, software
|
|
10 |
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11 |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12 |
// See the License for the specific language governing permissions and
|
|
13 |
// limitations under the License.
|
|
14 |
|
|
15 |
package org.apache.tapestry.listener;
|
|
16 |
|
|
17 |
import java.lang.reflect.InvocationTargetException;
|
|
18 |
import java.lang.reflect.Method;
|
|
19 |
import java.lang.reflect.Modifier;
|
|
20 |
import java.util.Collection;
|
|
21 |
import java.util.Collections;
|
|
22 |
import java.util.HashMap;
|
|
23 |
import java.util.Map;
|
|
24 |
|
|
25 |
import org.apache.commons.logging.Log;
|
|
26 |
import org.apache.commons.logging.LogFactory;
|
|
27 |
import org.apache.hivemind.ApplicationRuntimeException;
|
|
28 |
import org.apache.tapestry.IActionListener;
|
|
29 |
import org.apache.tapestry.IRequestCycle;
|
|
30 |
|
|
31 |
/**
|
|
32 |
* Maps a class to a set of listeners based on the public methods of the class.
|
|
33 |
* {@link org.apache.tapestry.listener.ListenerMapPropertyAccessor}is setup to provide these
|
|
34 |
* methods as named properties of the ListenerMap.
|
|
35 |
*
|
|
36 |
* @author Howard Ship
|
|
37 |
* @since 1.0.2
|
|
38 |
*/
|
|
39 |
|
|
40 |
public class ListenerMap |
|
41 |
{ |
|
42 |
private static final Log LOG = LogFactory.getLog(ListenerMap.class); |
|
43 |
|
|
44 |
private Object _target;
|
|
45 |
|
|
46 |
/**
|
|
47 |
* A {@link Map}of relevant {@link Method}s, keyed on method name. This is just the public
|
|
48 |
* void methods that take an {@link IRequestCycle}and throw nothing or just
|
|
49 |
* {@link ApplicationRuntimeException}.
|
|
50 |
*/
|
|
51 |
|
|
52 |
private Map _methodMap;
|
|
53 |
|
|
54 |
/**
|
|
55 |
* A {@link Map}of cached listener instances, keyed on method name
|
|
56 |
*/
|
|
57 |
|
|
58 |
private Map _listenerCache = new HashMap(); |
|
59 |
|
|
60 |
/**
|
|
61 |
* A {@link Map}, keyed on Class, of {@link Map}... the method map for any particular instance
|
|
62 |
* of the given class.
|
|
63 |
*/
|
|
64 |
|
|
65 |
private static Map _classMap = new HashMap(); |
|
66 |
|
|
67 | 39 |
public ListenerMap(Object target)
|
68 |
{ |
|
69 | 39 |
_target = target; |
70 |
} |
|
71 |
|
|
72 |
/**
|
|
73 |
* Gets a listener for the given name (which is both a property name and a method name). The
|
|
74 |
* listener is created as needed, but is also cached for later use.
|
|
75 |
*
|
|
76 |
* @throws ApplicationRuntimeException
|
|
77 |
* if the listener can not be created.
|
|
78 |
*/
|
|
79 |
|
|
80 | 52 |
public synchronized Object getListener(String name) |
81 |
{ |
|
82 | 52 |
Object listener = null;
|
83 |
|
|
84 | 52 |
listener = _listenerCache.get(name); |
85 |
|
|
86 | 52 |
if (listener == null) |
87 |
{ |
|
88 | 48 |
listener = createListener(name); |
89 |
|
|
90 | 48 |
_listenerCache.put(name, listener); |
91 |
} |
|
92 |
|
|
93 | 52 |
return listener;
|
94 |
} |
|
95 |
|
|
96 |
/**
|
|
97 |
* Returns an object that implements {@link IActionListener}. This involves looking up the
|
|
98 |
* method by name and determining which inner class to create.
|
|
99 |
*/
|
|
100 |
|
|
101 | 48 |
private synchronized Object createListener(String name) |
102 |
{ |
|
103 | 48 |
if (_methodMap == null) |
104 | 32 |
getMethodMap(); |
105 |
|
|
106 | 48 |
Method method = (Method) _methodMap.get(name); |
107 |
|
|
108 | 48 |
if (method == null) |
109 | 0 |
throw new ApplicationRuntimeException(ListenerMessages.objectMissingMethod( |
110 |
_target, |
|
111 |
name)); |
|
112 |
|
|
113 | 48 |
return new SyntheticListener(_target, method); |
114 |
} |
|
115 |
|
|
116 |
/**
|
|
117 |
* Gets the method map for the current instance. If necessary, it is constructed and cached (for
|
|
118 |
* other instances of the same class).
|
|
119 |
*/
|
|
120 |
|
|
121 | 41 |
private synchronized Map getMethodMap() |
122 |
{ |
|
123 | 41 |
if (_methodMap != null) |
124 | 3 |
return _methodMap;
|
125 |
|
|
126 | 38 |
Class beanClass = _target.getClass(); |
127 |
|
|
128 | 38 |
synchronized (_classMap)
|
129 |
{ |
|
130 | 38 |
_methodMap = (Map) _classMap.get(beanClass); |
131 |
|
|
132 | 38 |
if (_methodMap == null) |
133 |
{ |
|
134 | 29 |
_methodMap = buildMethodMap(beanClass); |
135 |
|
|
136 | 29 |
_classMap.put(beanClass, _methodMap); |
137 |
} |
|
138 |
} |
|
139 |
|
|
140 | 38 |
return _methodMap;
|
141 |
} |
|
142 |
|
|
143 | 29 |
private static Map buildMethodMap(Class beanClass) |
144 |
{ |
|
145 | 29 |
if (LOG.isDebugEnabled())
|
146 | 0 |
LOG.debug("Building method map for class " + beanClass.getName());
|
147 |
|
|
148 | 29 |
Map result = new HashMap();
|
149 | 29 |
Method[] methods = beanClass.getMethods(); |
150 |
|
|
151 | 29 |
for (int i = 0; i < methods.length; i++) |
152 |
{ |
|
153 | 2292 |
Method m = methods[i]; |
154 | 2292 |
int mods = m.getModifiers();
|
155 |
|
|
156 | 2292 |
if (Modifier.isStatic(mods))
|
157 | 2 |
continue;
|
158 |
|
|
159 |
// Probably not necessary, getMethods() returns only public
|
|
160 |
// methods.
|
|
161 |
|
|
162 | 2290 |
if (!Modifier.isPublic(mods))
|
163 | 0 |
continue;
|
164 |
|
|
165 |
// Must return void
|
|
166 |
|
|
167 | 2290 |
if (m.getReturnType() != Void.TYPE)
|
168 | 1027 |
continue;
|
169 |
|
|
170 | 1263 |
Class[] parmTypes = m.getParameterTypes(); |
171 |
|
|
172 | 1263 |
if (parmTypes.length != 1)
|
173 | 463 |
continue;
|
174 |
|
|
175 |
// parm must be IRequestCycle
|
|
176 |
|
|
177 | 800 |
if (!parmTypes[0].equals(IRequestCycle.class)) |
178 | 727 |
continue;
|
179 |
|
|
180 |
// Ha! Passed all tests.
|
|
181 |
|
|
182 | 73 |
result.put(m.getName(), m); |
183 |
} |
|
184 |
|
|
185 | 29 |
return result;
|
186 |
|
|
187 |
} |
|
188 |
|
|
189 |
/**
|
|
190 |
* Invoked by the inner listener/adaptor classes to invoke the method.
|
|
191 |
*/
|
|
192 |
|
|
193 | 65 |
static void invokeTargetMethod(Object target, Method method, Object[] args) |
194 |
{ |
|
195 | 65 |
if (LOG.isDebugEnabled())
|
196 | 0 |
LOG.debug("Invoking listener method " + method + " on " + target); |
197 |
|
|
198 | 65 |
try
|
199 |
{ |
|
200 | 65 |
try
|
201 |
{ |
|
202 | 65 |
method.invoke(target, args); |
203 |
} |
|
204 |
catch (InvocationTargetException ex)
|
|
205 |
{ |
|
206 | 11 |
Throwable inner = ex.getTargetException(); |
207 |
|
|
208 | 11 |
if (inner instanceof ApplicationRuntimeException) |
209 | 10 |
throw (ApplicationRuntimeException) inner;
|
210 |
|
|
211 |
// Edit out the InvocationTargetException, if possible.
|
|
212 |
|
|
213 | 1 |
if (inner instanceof RuntimeException) |
214 | 1 |
throw (RuntimeException) inner;
|
215 |
|
|
216 | 0 |
throw ex;
|
217 |
} |
|
218 |
} |
|
219 |
catch (ApplicationRuntimeException ex)
|
|
220 |
{ |
|
221 | 10 |
throw ex;
|
222 |
} |
|
223 |
catch (Exception ex)
|
|
224 |
{ |
|
225 |
// Catch InvocationTargetException or, preferrably,
|
|
226 |
// the inner exception here (if its a runtime exception).
|
|
227 |
|
|
228 | 1 |
throw new ApplicationRuntimeException(ListenerMessages.unableToInvokeMethod( |
229 |
method, |
|
230 |
target, |
|
231 |
ex), ex); |
|
232 |
} |
|
233 |
} |
|
234 |
|
|
235 |
/**
|
|
236 |
* Returns an unmodifiable collection of the names of the listeners implemented by the target
|
|
237 |
* class.
|
|
238 |
*
|
|
239 |
* @since 1.0.6
|
|
240 |
*/
|
|
241 |
|
|
242 | 2 |
public synchronized Collection getListenerNames() |
243 |
{ |
|
244 | 2 |
return Collections.unmodifiableCollection(getMethodMap().keySet());
|
245 |
} |
|
246 |
|
|
247 |
/**
|
|
248 |
* Returns true if this ListenerMap can provide a listener with the given name.
|
|
249 |
*
|
|
250 |
* @since 2.2
|
|
251 |
*/
|
|
252 |
|
|
253 | 7 |
public synchronized boolean canProvideListener(String name) |
254 |
{ |
|
255 | 7 |
return getMethodMap().containsKey(name);
|
256 |
} |
|
257 |
|
|
258 | 1 |
public String toString()
|
259 |
{ |
|
260 | 1 |
return "ListenerMap[" + _target + "]"; |
261 |
} |
|
262 |
} |
|