KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectstyle > cayenne > event > EventManager


1 /* ====================================================================
2  *
3  * The ObjectStyle Group Software License, version 1.1
4  * ObjectStyle Group - http://objectstyle.org/
5  *
6  * Copyright (c) 2002-2005, Andrei (Andrus) Adamchik and individual authors
7  * of the software. All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  * notice, this list of conditions and the following disclaimer in
18  * the documentation and/or other materials provided with the
19  * distribution.
20  *
21  * 3. The end-user documentation included with the redistribution, if any,
22  * must include the following acknowlegement:
23  * "This product includes software developed by independent contributors
24  * and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
25  * Alternately, this acknowlegement may appear in the software itself,
26  * if and wherever such third-party acknowlegements normally appear.
27  *
28  * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
29  * or promote products derived from this software without prior written
30  * permission. For written permission, email
31  * "andrus at objectstyle dot org".
32  *
33  * 5. Products derived from this software may not be called "ObjectStyle"
34  * or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
35  * names without prior written permission.
36  *
37  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40  * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
41  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48  * SUCH DAMAGE.
49  * ====================================================================
50  *
51  * This software consists of voluntary contributions made by many
52  * individuals and hosted on ObjectStyle Group web site. For more
53  * information on the ObjectStyle Group, please see
54  * <http://objectstyle.org/>.
55  */

56
57 package org.objectstyle.cayenne.event;
58
59 import java.util.ArrayList JavaDoc;
60 import java.util.Collections JavaDoc;
61 import java.util.EventObject JavaDoc;
62 import java.util.Iterator JavaDoc;
63 import java.util.LinkedList JavaDoc;
64 import java.util.List JavaDoc;
65 import java.util.Map JavaDoc;
66 import java.util.WeakHashMap JavaDoc;
67
68 import org.apache.log4j.Logger;
69 import org.objectstyle.cayenne.CayenneRuntimeException;
70 import org.objectstyle.cayenne.util.Invocation;
71 import org.objectstyle.cayenne.util.Util;
72
73 /**
74  * This class acts as bridge between an Object that wants to inform others about
75  * its current state or a change thereof (Publisher) and a list of objects
76  * interested in the Subject (Listeners).
77  *
78  * @author Dirk Olmes
79  * @author Holger Hoffstaette
80  * @author Andrei Adamchik
81  */

82 public class EventManager extends Object JavaDoc {
83     private static Logger logObj = Logger.getLogger(EventManager.class);
84
85     private static final EventManager defaultManager = new EventManager();
86
87     public static final int DEFAULT_DISPATCH_THREAD_COUNT = 5;
88
89     // keeps weak references to subjects
90
protected Map JavaDoc subjects;
91     protected List JavaDoc eventQueue;
92     protected boolean singleThread;
93
94     /**
95      * This method will return the shared 'default' EventManager.
96      *
97      * @return EventManager the shared EventManager instance
98      */

99     public static EventManager getDefaultManager() {
100         return defaultManager;
101     }
102
103     public EventManager() {
104         this(DEFAULT_DISPATCH_THREAD_COUNT);
105     }
106
107     /**
108      * Default constructor for new EventManager instances, in case you need one.
109      */

110     public EventManager(int dispatchThreadCount) {
111         super();
112         this.subjects = Collections.synchronizedMap(new WeakHashMap JavaDoc());
113         this.eventQueue = Collections.synchronizedList(new LinkedList JavaDoc());
114         this.singleThread = dispatchThreadCount <= 0;
115
116         // start dispatch threads
117
for (int i = 0; i < dispatchThreadCount; i++) {
118             new DispatchThread("EventDispatchThread-" + i).start();
119         }
120     }
121     
122     /**
123      * Returns a list of currently queued events. Queue is returned by copy.
124      * This method is useful for inspecting the state of the event queue at
125      * any particular moment, but doesn't allow callers to alter the queue state.
126      *
127      * @since 1.1
128      */

129     public List JavaDoc getEventQueue() {
130         synchronized(eventQueue) {
131             return new ArrayList JavaDoc(eventQueue);
132         }
133     }
134
135     /**
136      * Register an <code>EventListener</code> for events sent by any sender.
137      *
138      * @throws RuntimeException if <code>methodName</code> is not found
139      * @see #addListener(Object, String, Class, EventSubject, Object)
140      */

141     public void addListener(
142         Object JavaDoc listener,
143         String JavaDoc methodName,
144         Class JavaDoc eventParameterClass,
145         EventSubject subject) {
146         this.addListener(listener, methodName, eventParameterClass, subject, null, true);
147     }
148
149     public void addNonBlockingListener(
150         Object JavaDoc listener,
151         String JavaDoc methodName,
152         Class JavaDoc eventParameterClass,
153         EventSubject subject) {
154
155         if (singleThread) {
156             throw new IllegalStateException JavaDoc("EventManager is configured to be single-threaded.");
157         }
158
159         this.addListener(listener, methodName, eventParameterClass, subject, null, false);
160     }
161
162     /**
163      * Register an <code>EventListener</code> for events sent by a specific
164      * sender.
165      *
166      * @param listener the object to be notified about events
167      * @param methodName the name of the listener method to be invoked
168      * @param eventParameterClass the class of the single event argument passed
169      * to <code>methodName</code>
170      * @param subject the event subject that the listener is interested in
171      * @param sender the object whose events the listener is interested in;
172      * <code>null</code> means 'any sender'.
173      * @throws RuntimeException if <code>methodName</code> is not found
174      */

175     public void addListener(
176         Object JavaDoc listener,
177         String JavaDoc methodName,
178         Class JavaDoc eventParameterClass,
179         EventSubject subject,
180         Object JavaDoc sender) {
181         addListener(listener, methodName, eventParameterClass, subject, sender, true);
182     }
183
184     public void addNonBlockingListener(
185         Object JavaDoc listener,
186         String JavaDoc methodName,
187         Class JavaDoc eventParameterClass,
188         EventSubject subject,
189         Object JavaDoc sender) {
190
191         if (singleThread) {
192             throw new IllegalStateException JavaDoc("EventManager is configured to be single-threaded.");
193         }
194
195         addListener(listener, methodName, eventParameterClass, subject, sender, false);
196     }
197
198     protected void addListener(
199         Object JavaDoc listener,
200         String JavaDoc methodName,
201         Class JavaDoc eventParameterClass,
202         EventSubject subject,
203         Object JavaDoc sender,
204         boolean blocking) {
205
206         if (listener == null) {
207             throw new IllegalArgumentException JavaDoc("Listener must not be null.");
208         }
209
210         if (eventParameterClass == null) {
211             throw new IllegalArgumentException JavaDoc("Event class must not be null.");
212         }
213
214         if (subject == null) {
215             throw new IllegalArgumentException JavaDoc("Subject must not be null.");
216         }
217
218        /* if (logObj.isDebugEnabled()) {
219             String label =
220                 (blocking) ? "adding listener: " : "adding non-blocking listener: ";
221             String object = new ToStringBuilder(listener).toString();
222             logObj.debug(label + object + "." + methodName);
223         } */

224
225         try {
226             Invocation invocation =
227                 (blocking)
228                     ? new Invocation(listener, methodName, eventParameterClass)
229                     : new NonBlockingInvocation(listener, methodName, eventParameterClass);
230             dispatchQueueForSubject(subject, true).addInvocation(invocation, sender);
231         }
232         catch (NoSuchMethodException JavaDoc nsm) {
233             throw new CayenneRuntimeException("Error adding listener, method name: " + methodName, nsm);
234         }
235     }
236
237     /**
238      * Unregister the specified listener from all event subjects handled by this
239      * <code>EventManager</code> instance.
240      *
241      * @param listener the object to be unregistered
242      * @return <code>true</code> if <code>listener</code> could be removed for
243      * any existing subjects, else returns <code>false</code>.
244      */

245     public boolean removeListener(Object JavaDoc listener) {
246         if (listener == null) {
247             return false;
248         }
249
250         boolean didRemove = false;
251
252         synchronized (subjects) {
253             if (!subjects.isEmpty()) {
254                 Iterator JavaDoc subjectIter = subjects.keySet().iterator();
255                 while (subjectIter.hasNext()) {
256                     didRemove
257                         |= this.removeListener(listener, (EventSubject) subjectIter.next());
258                 }
259             }
260         }
261
262         return didRemove;
263     }
264
265     /**
266      * Removes all listeners for a given subject.
267      */

268     public boolean removeAllListeners(EventSubject subject) {
269         if (subject != null) {
270             synchronized (subjects) {
271                 return subjects.remove(subject) != null;
272             }
273         }
274
275         return false;
276     }
277
278     /**
279      * Unregister the specified listener for the events about the given subject.
280      *
281      * @param listener the object to be unregistered
282      * @param subject the subject from which the listener is to be unregistered
283      * @return <code>true</code> if <code>listener</code> could be removed for
284      * the given subject, else returns <code>false</code>.
285      */

286     public boolean removeListener(Object JavaDoc listener, EventSubject subject) {
287         return this.removeListener(listener, subject, null);
288     }
289
290     /**
291      * Unregister the specified listener for the events about the given subject
292      * and the given sender.
293      *
294      * @param listener the object to be unregistered
295      * @param subject the subject from which the listener is to be unregistered
296      * @param sender the object whose events the listener was interested in;
297      * <code>null</code> means 'any sender'.
298      * @return <code>true</code> if <code>listener</code> could be removed for
299      * the given subject, else returns <code>false</code>.
300      */

301     public boolean removeListener(Object JavaDoc listener, EventSubject subject, Object JavaDoc sender) {
302         if (listener == null || subject == null) {
303             return false;
304         }
305
306         DispatchQueue subjectQueue = dispatchQueueForSubject(subject, false);
307         if (subjectQueue == null) {
308             return false;
309         }
310
311         return subjectQueue.removeInvocations(listener, sender);
312     }
313
314     /**
315      * Sends an event to all registered objects about a particular subject.
316      * Event is sent synchronously, so the sender thread is blocked until all
317      * the listeners finish processing the event.
318      *
319      * @param event the event to be posted to the observers
320      * @param subject the subject about which observers will be notified
321      * @throws IllegalArgumentException if event or subject are null
322      */

323     public void postEvent(EventObject JavaDoc event, EventSubject subject) {
324         dispatchEvent(new Dispatch(event, subject));
325     }
326
327     /**
328      * Sends an event to all registered objects about a particular subject.
329      * Event is queued by EventManager, releasing the sender thread, and is
330      * later dispatched in a separate thread.
331      *
332      * @param event the event to be posted to the observers
333      * @param subject the subject about which observers will be notified
334      *
335      * @throws IllegalArgumentException if event or subject are null
336      * @since 1.1
337      */

338     public void postNonBlockingEvent(EventObject JavaDoc event, EventSubject subject) {
339         if (singleThread) {
340             throw new IllegalStateException JavaDoc("EventManager is configured to be single-threaded.");
341         }
342
343         // add dispatch to the queue and return
344
synchronized (eventQueue) {
345             eventQueue.add(new Dispatch(event, subject));
346             eventQueue.notifyAll();
347         }
348     }
349
350     private void dispatchEvent(Dispatch dispatch) {
351         DispatchQueue dispatchQueue = dispatchQueueForSubject(dispatch.subject, false);
352         if (dispatchQueue != null) {
353             dispatchQueue.dispatchEvent(dispatch);
354         }
355     }
356
357     // returns a subject's mapping from senders to registered listener invocations
358
private DispatchQueue dispatchQueueForSubject(EventSubject subject, boolean create) {
359         synchronized (subjects) {
360             DispatchQueue listenersStore = (DispatchQueue) subjects.get(subject);
361             if (create && listenersStore == null) {
362                 listenersStore = new DispatchQueue();
363                 subjects.put(subject, listenersStore);
364             }
365             return listenersStore;
366         }
367     }
368
369     // represents a posted event
370
class Dispatch {
371         EventObject JavaDoc[] eventArgument;
372         EventSubject subject;
373
374         Dispatch(EventObject JavaDoc event, EventSubject subject) {
375             this(new EventObject JavaDoc[] { event }, subject);
376         }
377
378         Dispatch(EventObject JavaDoc[] eventArgument, EventSubject subject) {
379             this.eventArgument = eventArgument;
380             this.subject = subject;
381         }
382
383         Object JavaDoc getSender() {
384             return eventArgument[0].getSource();
385         }
386
387         void fire() {
388             EventManager.this.dispatchEvent(Dispatch.this);
389         }
390
391         boolean fire(Invocation invocation) {
392             if (invocation instanceof NonBlockingInvocation) {
393
394                 // do minimal checks first...
395
if (invocation.getTarget() == null) {
396                     return false;
397                 }
398
399                 // inject single invocation dispatch into the queue
400
synchronized (eventQueue) {
401                     eventQueue.add(
402                         new InvocationDispatch(eventArgument, subject, invocation));
403                     eventQueue.notifyAll();
404                 }
405
406                 return true;
407             }
408             else {
409                 return invocation.fire(eventArgument);
410             }
411         }
412     }
413
414     // represents a posted event that should be sent to a single known listener
415
class InvocationDispatch extends Dispatch {
416         Invocation target;
417
418         InvocationDispatch(
419             EventObject JavaDoc[] eventArgument,
420             EventSubject subject,
421             Invocation target) {
422             super(eventArgument, subject);
423             this.target = target;
424         }
425
426         void fire() {
427             // there is no way to kill the invocation if it is bad...
428
// so don't check for status
429
target.fire(eventArgument);
430         }
431     }
432
433     // subclass exists only to tag invocations that should be
434
// dispatched in a separate thread
435
final class NonBlockingInvocation extends Invocation {
436
437         public NonBlockingInvocation(
438             Object JavaDoc target,
439             String JavaDoc methodName,
440             Class JavaDoc parameterType)
441             throws NoSuchMethodException JavaDoc {
442             super(target, methodName, parameterType);
443         }
444     }
445
446     final class DispatchThread extends Thread JavaDoc {
447         DispatchThread(String JavaDoc name) {
448             super(name);
449             setDaemon(true);
450         }
451
452         public void run() {
453             while (true) {
454
455                 // get event from the queue, if the queue
456
// is empty, just wait
457
Dispatch dispatch = null;
458
459                 synchronized (EventManager.this.eventQueue) {
460                     if (EventManager.this.eventQueue.size() > 0) {
461                         dispatch = (Dispatch) EventManager.this.eventQueue.remove(0);
462                     }
463                     else {
464                         try {
465                             EventManager.this.eventQueue.wait();
466                         }
467                         catch (InterruptedException JavaDoc e) {
468                             // ignore interrupts...
469
}
470                     }
471                 }
472
473                 // dispatch outside of synchronized block
474
if (dispatch != null) {
475                     // this try/catch is needed to prevent DispatchThread
476
// from dying on dispatch errors
477
try {
478                         dispatch.fire();
479                     }
480                     catch (Throwable JavaDoc th) {
481                         // ignoring exception
482
logObj.debug(
483                             "Event dispatch error, ignoring.",
484                             Util.unwindException(th));
485                     }
486                 }
487             }
488         }
489     }
490 }
491
Popular Tags