Source code: org/objectstyle/cayenne/event/EventManager.java
1 /* ====================================================================
2 *
3 * The ObjectStyle Group Software License, Version 1.0
4 *
5 * Copyright (c) 2002-2003 The ObjectStyle Group
6 * and individual authors of the software. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
18 * distribution.
19 *
20 * 3. The end-user documentation included with the redistribution, if
21 * any, must include the following acknowlegement:
22 * "This product includes software developed by the
23 * ObjectStyle Group (http://objectstyle.org/)."
24 * Alternately, this acknowlegement may appear in the software itself,
25 * if and wherever such third-party acknowlegements normally appear.
26 *
27 * 4. The names "ObjectStyle Group" and "Cayenne"
28 * must not be used to endorse or promote products derived
29 * from this software without prior written permission. For written
30 * permission, please contact andrus@objectstyle.org.
31 *
32 * 5. Products derived from this software may not be called "ObjectStyle"
33 * nor may "ObjectStyle" appear in their names without prior written
34 * permission of the ObjectStyle Group.
35 *
36 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39 * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
40 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * SUCH DAMAGE.
48 * ====================================================================
49 *
50 * This software consists of voluntary contributions made by many
51 * individuals on behalf of the ObjectStyle Group. For more
52 * information on the ObjectStyle Group, please see
53 * <http://objectstyle.org/>.
54 *
55 */
56
57 package org.objectstyle.cayenne.event;
58
59 import java.util.ArrayList;
60 import java.util.EventListener;
61 import java.util.EventObject;
62 import java.util.HashSet;
63 import java.util.Iterator;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.Set;
67 import java.util.WeakHashMap;
68
69 import org.apache.commons.collections.iterators.SingletonIterator;
70 import org.objectstyle.cayenne.util.Invocation;
71
72 /**
73 * This class acts as bridge between an Object that wants to inform others about
74 * its current state or a change thereof (Publisher) and a list of objects
75 * interested in the Subject (Listeners).
76 *
77 * @author Dirk Olmes
78 * @author Holger Hoffstaette
79 */
80 public class EventManager extends Object {
81 private static final EventManager _defaultManager = new EventManager();
82
83 // keeps weak references to subjects
84 private Map _subjects;
85
86 /**
87 * This method will return the shared 'default' EventManager.
88 *
89 * @return EventManager the shared EventManager instance
90 */
91 public static EventManager getDefaultManager() {
92 return _defaultManager;
93 }
94
95 /**
96 * Default constructor for new EventManager instances, in case you need one.
97 */
98 public EventManager() {
99 super();
100 _subjects = new WeakHashMap();
101 }
102
103 /**
104 * Register an <code>EventListener</code> for events sent by any sender.
105 *
106 * @throws NoSuchMethodException if <code>methodName</code> is not found
107 * @see #addListener(EventListener, String, Class, EventSubject, Object)
108 */
109 synchronized public void addListener(EventListener listener,
110 String methodName,
111 Class eventParameterClass,
112 EventSubject subject)
113 throws NoSuchMethodException {
114 this.addListener(listener, methodName, eventParameterClass, subject, this);
115 }
116
117 /**
118 * Register an <code>EventListener</code> for events sent by a specific
119 * sender.
120 *
121 * @param listener the object to be notified about events
122 * @param methodName the name of the listener method to be invoked
123 * @param eventParameterClass the class of the single event argument passed
124 * to <code>methodName</code>
125 * @param subject the event subject that the listener is interested in
126 * @param sender the object whose events the listener is interested in;
127 * <code>null</code> means 'any sender'.
128 * @throws NoSuchMethodException if <code>methodName</code> is not found
129 */
130 synchronized public void addListener(EventListener listener,
131 String methodName,
132 Class eventParameterClass,
133 EventSubject subject,
134 Object sender)
135 throws NoSuchMethodException {
136 if (listener == null) {
137 throw new IllegalArgumentException("Listener must not be null.");
138 }
139
140 if (eventParameterClass == null) {
141 throw new IllegalArgumentException("Event class must not be null.");
142 }
143
144 if (subject == null) {
145 throw new IllegalArgumentException("Subject must not be null.");
146 }
147
148 Invocation inv = new Invocation(listener, methodName, eventParameterClass);
149
150 Map subjectQueues = this.invocationQueuesForSubject(subject);
151 if (subjectQueues == null) {
152 // make sure the subject can be associated with invocation queues
153 subjectQueues = new WeakHashMap();
154 _subjects.put(subject, subjectQueues);
155 }
156
157 Set queueForSender = this.invocationQueueForSubjectAndSender(subject, sender);
158 if (queueForSender == null) {
159 // create a new listener 'queue'; must keep strong references
160 queueForSender = new HashSet();
161 subjectQueues.put(sender, queueForSender);
162 }
163
164 queueForSender.add(inv);
165 }
166
167 /**
168 * Unregister the specified listener from all event subjects handled by this
169 * <code>EventManager</code> instance.
170 *
171 * @param listener the object to be unregistered
172 * @return <code>true</code> if <code>listener</code> could be removed for
173 * any existing subjects, else returns <code>false</code>.
174 */
175 synchronized public boolean removeListener(EventListener listener) {
176 boolean didRemove = false;
177
178 if ((_subjects.isEmpty() == false) && (listener != null)) {
179 Iterator subjectIter = _subjects.keySet().iterator();
180 while (subjectIter.hasNext()) {
181 didRemove |= this.removeListener(listener, (EventSubject)subjectIter.next());
182 }
183 }
184
185 return didRemove;
186 }
187
188 /**
189 * Unregister the specified listener for the events about the given subject.
190 *
191 * @param listener the object to be unregistered
192 * @param subject the subject from which the listener is to be unregistered
193 * @return <code>true</code> if <code>listener</code> could be removed for
194 * the given subject, else returns <code>false</code>.
195 */
196 synchronized public boolean removeListener(EventListener listener,
197 EventSubject subject) {
198 return this.removeListener(listener, subject, null);
199 }
200
201 /**
202 * Unregister the specified listener for the events about the given subject
203 * and the given sender.
204 *
205 * @param listener the object to be unregistered
206 * @param subject the subject from which the listener is to be unregistered
207 * @param sender the object whose events the listener was interested in;
208 * <code>null</code> means 'any sender'.
209 * @return <code>true</code> if <code>listener</code> could be removed for
210 * the given subject, else returns <code>false</code>.
211 */
212 synchronized public boolean removeListener(EventListener listener,
213 EventSubject subject,
214 Object sender) {
215 boolean didRemove = false;
216
217 if ((listener != null) && (subject != null)) {
218 Map subjectQueues = this.invocationQueuesForSubject(subject);
219 if (subjectQueues != null) {
220 Iterator queueIter;
221
222 // remove only listeners for sender?
223 if (sender != null) {
224 Set senderQueue = this.invocationQueueForSubjectAndSender(subject, sender);
225 queueIter = new SingletonIterator(senderQueue);
226 }
227 else {
228 queueIter = subjectQueues.values().iterator();
229 }
230
231 // iterate over all invocation queues for this subject
232 while (queueIter.hasNext()) {
233 Set invocations = (Set)queueIter.next();
234 if ((invocations != null) && (invocations.isEmpty() == false)) {
235 // remove all invocations with the given target
236 Iterator invIter = invocations.iterator();
237 while (invIter.hasNext()) {
238 Invocation inv = (Invocation)invIter.next();
239 if (inv.getTarget() == listener) {
240 invIter.remove();
241 didRemove = true;
242 }
243 }
244 }
245 }
246 }
247 }
248
249 return didRemove;
250 }
251
252 /**
253 * Sends an event to all registered objects about a particular subject.
254 *
255 * @param event the event to be posted to the observers
256 * @param subject the subject about which observers will be notified
257 * @throws IllegalArgumentException if event or subject are null
258 */
259 synchronized public void postEvent(EventObject event, EventSubject subject) {
260 if (event == null) {
261 throw new IllegalArgumentException("event must not be null");
262 }
263
264 if (subject == null) {
265 throw new IllegalArgumentException("subject must not be null");
266 }
267
268 // collect listener invocations for subject
269 Set specificInvocations = this.invocationQueueForSubjectAndSender(subject, event.getSource());
270 Set defaultInvocations = this.invocationQueueForSubjectAndSender(subject, this);
271 Set[] invocationQueues = new Set[]{specificInvocations, defaultInvocations};
272 Object[] eventArgument = new Object[]{event};
273
274 for (int i = 0; i < invocationQueues.length; i++) {
275 Set currentQueue = invocationQueues[i];
276 if ((currentQueue != null) && (currentQueue.isEmpty() == false)) {
277 // used to collect all invalid invocations in order to remove
278 // them at the end of this posting cycle
279 List invalidInvocations = null;
280 Iterator iter = currentQueue.iterator();
281 while (iter.hasNext()) {
282 Invocation inv = (Invocation)iter.next();
283 Class[] invParamTypes = inv.getParameterTypes();
284
285 // we only process event listeners which take exactly
286 // one argument in their registered methods: the passed
287 // event or a valid subclass thereof
288 if ((invParamTypes != null)
289 && (invParamTypes.length == 1)
290 && (invParamTypes[0].isAssignableFrom(event.getClass()))) {
291 // fire invocation, detect if anything went wrong
292 // (e.g. GC'ed invocation targets)
293 if (inv.fire(eventArgument) == false) {
294 if (invalidInvocations == null) {
295 invalidInvocations = new ArrayList();
296 }
297
298 invalidInvocations.add(inv);
299 }
300 }
301 }
302
303 // clear out all invalid invocations
304 if (invalidInvocations != null) {
305 currentQueue.removeAll(invalidInvocations);
306 }
307 }
308 }
309 }
310
311 // returns a subject's mapping from senders to registered listener invocations
312 private Map invocationQueuesForSubject(EventSubject subject) {
313 return (Map)_subjects.get(subject);
314 }
315
316 // returns the registered listener invocations for a particular sender;
317 // the owning event manager instance is used as default sender
318 private Set invocationQueueForSubjectAndSender(EventSubject subject, Object sender) {
319 if (sender == null) {
320 sender = this;
321 }
322
323 Map subjectEntries = this.invocationQueuesForSubject(subject);
324 Set queue = null;
325
326 if (subjectEntries != null) {
327 queue = (Set)subjectEntries.get(sender);
328 }
329
330 return queue;
331 }
332
333 }
334