KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > nightlabs > notification > NotificationManager


1 /* ************************************************************************** *
2  * Copyright (C) 2004 NightLabs GmbH, Marco Schulze *
3  * All rights reserved. *
4  * http://www.NightLabs.de *
5  * *
6  * This program and the accompanying materials are free software; you can re- *
7  * distribute it and/or modify it under the terms of the GNU General Public *
8  * License as published by the Free Software Foundation; either ver 2 of the *
9  * License, or any later version. *
10  * *
11  * This module is distributed in the hope that it will be useful, but WITHOUT *
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FIT- *
13  * NESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more *
14  * details. *
15  * *
16  * You should have received a copy of the GNU General Public License along *
17  * with this module; if not, write to the Free Software Foundation, Inc.: *
18  * 59 Temple Place, Suite 330 *
19  * Boston MA 02111-1307 *
20  * USA *
21  * *
22  * Or get it online: *
23  * http://www.opensource.org/licenses/gpl-license.php *
24  * *
25  * In case, you want to use this module or parts of it in a proprietary pro- *
26  * ject, you can purchase it under the NightLabs Commercial License. Please *
27  * contact NightLabs GmbH under info AT nightlabs DOT com for more infos or *
28  * visit http://www.NightLabs.com *
29  * ************************************************************************** */

30
31 /*
32  * Created on Apr 14, 2005
33  */

34 package com.nightlabs.notification;
35
36 import java.lang.reflect.InvocationTargetException JavaDoc;
37 import java.util.Collection JavaDoc;
38 import java.util.Collections JavaDoc;
39 import java.util.HashMap JavaDoc;
40 import java.util.HashSet JavaDoc;
41 import java.util.Iterator JavaDoc;
42 import java.util.LinkedList JavaDoc;
43 import java.util.List JavaDoc;
44 import java.util.Map JavaDoc;
45 import java.util.Set JavaDoc;
46 import java.util.WeakHashMap JavaDoc;
47
48 import javax.swing.SwingUtilities JavaDoc;
49
50 import com.nightlabs.util.RWLock;
51
52
53 /**
54  * <b>Warning:</b> The notification framework works with weak references. Therefore,
55  * all listeners must be non-anonymous fields! They may be anonymous classes, but
56  * if you don't assign them to a member in your object, they'll be quickly
57  * released by the garbage collector and you'll probably never receive any events!
58  *
59  * @author Marco Schulze - marco at nightlabs dot de
60  */

61 public class NotificationManager
62 {
63     private static final boolean USE_WEAK_REFERENCES = true;
64
65     /**
66      * This class is intended to be extended and to be used with one shared instance
67      * per subclass. Hence, the constructor is protected.
68      */

69     protected NotificationManager()
70     {
71     }
72
73     private LinkedList JavaDoc interceptors = new LinkedList JavaDoc();
74     private RWLock interceptorsMutex = new RWLock("interceptorsMutex");
75
76     public void addInterceptor(Interceptor m)
77     {
78         interceptorsMutex.acquireWriteLock();
79         try {
80             interceptors.add(m);
81         } finally {
82             interceptorsMutex.releaseLock();
83         }
84     }
85
86     public void addInterceptorAsFirst(Interceptor m)
87     {
88         interceptorsMutex.acquireWriteLock();
89         try {
90             interceptors.addFirst(m);
91         } finally {
92             interceptorsMutex.releaseLock();
93         }
94     }
95
96     public void addInterceptorAsLast(Interceptor m)
97     {
98         interceptorsMutex.acquireWriteLock();
99         try {
100             interceptors.addLast(m);
101         } finally {
102             interceptorsMutex.releaseLock();
103         }
104     }
105
106     public void removeInterceptor(Interceptor m)
107     {
108         interceptorsMutex.acquireWriteLock();
109         try {
110             interceptors.remove(m);
111         } finally {
112             interceptorsMutex.releaseLock();
113         }
114     }
115
116     /**
117      * How (on which thread and whether to wait) to invoke the listener
118      * is defined by the implemented interface. This method must check, whether
119      * the used interface is supported (and throw a {@link ClassCastException}
120      * if it's not) and return a unique String defining the mode.
121      * <p>
122      * If you extend the <tt>NotificationManager</tt> to support additional
123      * notification modes, you must overwrite this method and check for your new
124      * interfaces. It's a good idea to simply return the fully qualified name
125      * of your listener interface.
126      */

127     protected String JavaDoc getNotificationModeForListener(NotificationListener listener)
128     {
129         if (listener == null)
130             throw new IllegalArgumentException JavaDoc("listener must not be null!");
131
132         if (listener instanceof NotificationListenerCallerThread)
133             return NotificationListenerCallerThread.class.getName();
134
135         if (listener instanceof NotificationListenerWorkerThreadAsync)
136             return NotificationListenerWorkerThreadAsync.class.getName();
137
138         if (listener instanceof NotificationListenerAWTThreadSync)
139             return NotificationListenerAWTThreadSync.class.getName();
140
141         if (listener instanceof NotificationListenerAWTThreadAsync)
142             return NotificationListenerAWTThreadAsync.class.getName();
143
144         throw new ClassCastException JavaDoc("Listener is not supported! It must implement one of the sub-interfaces of NotificationListener!");
145     }
146
147 // /**
148
// * Override this method in case you want to add new notification modes.
149
// *
150
// * @param notificationMode The notification mode which shall be checked for validity.
151
// * @throws IllegalArgumentException If the notificationMode is invalid.
152
// */
153
// protected void assertValidNotificationMode(String notificationMode)
154
// {
155
// if (!NOTIFICATION_MODE_CALLER_THREAD.equals(notificationMode) &&
156
// !NOTIFICATION_MODE_WORKER_THREAD_ASYNC.equals(notificationMode) &&
157
// !NOTIFICATION_MODE_AWT_THREAD_SYNC.equals(notificationMode) &&
158
// !NOTIFICATION_MODE_AWT_THREAD_ASYNC.equals(notificationMode))
159
// throw new IllegalArgumentException("Invalid notificationMode: " + notificationMode);
160
// }
161

162     /**
163      * key: String zone<br/>
164      * value: Map subjectClasses {<br/>
165      * key: Class subjectClass<br/>
166      * value: (Weak)HashMap listeners {<br/>
167      * NotificationListener listener<br/>
168      * HashMap modes {<br/>
169      * String notificationMode<br/>
170      * NotificationListenerMeta listenerMeta<br/>
171      * }<br/>
172      * }<br/>
173      * }
174      */

175     private Map JavaDoc notificationListenersByZone = new HashMap JavaDoc();
176     private RWLock notificationListenersByZoneMutex = new RWLock("notificationListenersByZoneMutex");
177
178     /**
179      * A cache for zones;
180      */

181     private Collection JavaDoc zones = null;
182
183     /**
184      * @return a <tt>Collection</tt> of <tt>String</tt>.
185      */

186     protected Collection JavaDoc getZones()
187     {
188         if (zones == null) {
189             notificationListenersByZoneMutex.acquireReadLock();
190             try {
191                 zones = Collections.unmodifiableSet(
192                         new HashSet JavaDoc(notificationListenersByZone.keySet()));
193             } finally {
194                 notificationListenersByZoneMutex.releaseLock();
195             }
196         }
197         return zones;
198     }
199
200     /**
201      * Convenience method calling {@link #removeNotificationListener(String, Class, NotificationListener, String)}
202      * with <tt>zone = null</tt>.
203      */

204     public void removeNotificationListener(
205             Class JavaDoc subjectClass, NotificationListener listener)
206     {
207         removeNotificationListener((String JavaDoc)null, subjectClass, listener);
208     }
209     public void removeNotificationListener(
210             String JavaDoc zone, Class JavaDoc subjectClass, NotificationListener listener)
211     {
212         notificationListenersByZoneMutex.acquireWriteLock();
213         try {
214             zones = null;
215
216             String JavaDoc notificationMode = getNotificationModeForListener(listener);
217     
218             Map JavaDoc notificationListenersBySubjectClass = (Map JavaDoc) notificationListenersByZone.get(zone);
219             if (notificationListenersBySubjectClass != null) {
220                 Map JavaDoc mapListeners = (Map JavaDoc) notificationListenersBySubjectClass.get(subjectClass);
221                 if (mapListeners != null) {
222     
223                     Map JavaDoc mapModes = (Map JavaDoc) mapListeners.get(listener);
224                     if (mapModes != null) {
225     
226                         NotificationListenerMeta meta = (NotificationListenerMeta) mapModes.get(notificationMode);
227                         if (meta != null) {
228                             if (meta.decCounter() < 1) {
229                                 mapModes.remove(notificationMode);
230                                 meta = null;
231                             }
232                         } // if (meta != null) {
233

234                         if (mapModes.isEmpty()) {
235                             mapListeners.remove(listener);
236                         }
237     
238                     } // if (mapModes != null) {
239

240                     if (mapListeners.isEmpty()) {
241                         notificationListenersBySubjectClass.remove(subjectClass);
242                     }
243                 } // if (mapListeners != null) {
244

245                 if (notificationListenersBySubjectClass.isEmpty()) {
246                     notificationListenersByZone.remove(zone);
247                 }
248             } // if (notificationListenersBySubjectClass != null) {
249

250         } finally {
251             notificationListenersByZoneMutex.releaseLock();
252         }
253     }
254
255     /**
256      * Convenience method calling {@link #addNotificationListener(String, Class, NotificationListener, String)}
257      * with <tt>zone = null</tt>.
258      */

259     public void addNotificationListener(
260             Class JavaDoc subjectClass, NotificationListener listener)
261     {
262         addNotificationListener((String JavaDoc)null, subjectClass, listener);
263     }
264
265     public void addNotificationListener(
266             String JavaDoc zone, Class JavaDoc subjectClass, NotificationListener listener)
267     {
268         notificationListenersByZoneMutex.acquireWriteLock();
269         try {
270             zones = null;
271
272             String JavaDoc notificationMode = getNotificationModeForListener(listener);
273     
274             Map JavaDoc notificationListenersBySubjectClass = (Map JavaDoc) notificationListenersByZone.get(zone);
275             if (notificationListenersBySubjectClass == null) {
276                 notificationListenersBySubjectClass = new HashMap JavaDoc();
277                 notificationListenersByZone.put(zone, notificationListenersBySubjectClass);
278             }
279     
280             Map JavaDoc mapListeners = (Map JavaDoc) notificationListenersBySubjectClass.get(subjectClass);
281             if (mapListeners == null) {
282                 if (USE_WEAK_REFERENCES)
283                     mapListeners = new WeakHashMap JavaDoc();
284                 else
285                     mapListeners = new HashMap JavaDoc();
286     
287                 notificationListenersBySubjectClass.put(subjectClass, mapListeners);
288             }
289     
290             Map JavaDoc mapModes = (Map JavaDoc) mapListeners.get(listener);
291             if (mapModes == null) {
292                 mapModes = new HashMap JavaDoc();
293                 mapListeners.put(listener, mapModes);
294             }
295     
296             NotificationListenerMeta meta = (NotificationListenerMeta) mapModes.get(notificationMode);
297             if (meta != null && meta.getNotificationListener() == null)
298                 meta = null;
299     
300             if (meta == null) {
301                 meta = new NotificationListenerMeta(zone, subjectClass, listener, notificationMode);
302                 mapModes.put(notificationMode, meta);
303             }
304             meta.incCounter();
305
306         } finally {
307             notificationListenersByZoneMutex.releaseLock();
308         }
309     }
310
311     public void addNotificationListener(
312             List JavaDoc subjectClasses, NotificationListener listener)
313     {
314         addNotificationListener((String JavaDoc)null, subjectClasses, listener);
315     }
316     public void addNotificationListener(
317             String JavaDoc zone, List JavaDoc subjectClasses, NotificationListener listener)
318     {
319         for (Iterator JavaDoc it = subjectClasses.iterator(); it.hasNext(); )
320             addNotificationListener(zone, (Class JavaDoc)it.next(), listener);
321     }
322
323     public void addNotificationListener(
324             Class JavaDoc[] subjectClasses, NotificationListener listener)
325     {
326         addNotificationListener((String JavaDoc)null, subjectClasses, listener);
327     }
328     public void addNotificationListener(
329             String JavaDoc zone, Class JavaDoc[] subjectClasses, NotificationListener listener)
330     {
331         for (int i = 0; i < subjectClasses.length; ++i)
332             addNotificationListener(zone, subjectClasses[i], listener);
333     }
334
335     public void removeNotificationListener(
336             Collection JavaDoc subjectClasses, NotificationListener listener)
337     {
338         removeNotificationListener((String JavaDoc)null, subjectClasses, listener);
339     }
340     public void removeNotificationListener(
341             String JavaDoc zone, Collection JavaDoc subjectClasses, NotificationListener listener)
342     {
343         for (Iterator JavaDoc it = subjectClasses.iterator(); it.hasNext(); )
344             removeNotificationListener(zone, (Class JavaDoc)it.next(), listener);
345     }
346
347     public void removeNotificationListener(
348             Class JavaDoc[] subjectClasses, NotificationListener listener)
349     {
350         removeNotificationListener((String JavaDoc)null, subjectClasses, listener);
351     }
352     public void removeNotificationListener(
353             String JavaDoc zone, Class JavaDoc[] subjectClasses, NotificationListener listener)
354     {
355         for (int i = 0; i < subjectClasses.length; ++i)
356             removeNotificationListener(zone, subjectClasses[i], listener);
357     }
358
359 // protected static class _SubjectCarrier {
360
// public _SubjectCarrier(Class subjectClass) {
361
// this.subjectClass = subjectClass;
362
// }
363
//
364
// public _SubjectCarrier(Object subject) {
365
// this.subject = subject;
366
// this.subjectClass = subject.getClass();
367
// }
368
//
369
// public Class subjectClass;
370
// public Object subject = null;
371
// }
372

373     protected static class SubjectBundle
374     {
375         public Set JavaDoc subjectCarriers = new HashSet JavaDoc();
376 // public List subjects = new ArrayList();
377
// public List subjectClassesToClear = new ArrayList();
378
}
379
380     public void notify(NotificationEvent event)
381     {
382         if (event == null)
383             throw new NullPointerException JavaDoc("Parameter 'event' must not be null!");
384
385         if (!interceptors.isEmpty())
386             intercept(event);
387
388         notify(event, null);
389     }
390
391     protected NotificationEvent intercept(NotificationEvent event)
392     {
393         interceptorsMutex.acquireReadLock();
394         try {
395             for (Iterator JavaDoc it = interceptors.iterator(); it.hasNext(); ) {
396                 Interceptor interceptor = (Interceptor) it.next();
397                 NotificationEvent newEvent = interceptor.intercept(event);
398                 if (newEvent != null)
399                     event = newEvent;
400             }
401         } finally {
402             interceptorsMutex.releaseLock();
403         }
404         return event;
405     }
406
407
408     /**
409      * @param event The event to fire.
410      * @param onlyThisNotificationListener This is a fiter. That means: If it is <tt>null</tt>
411      * all matching listeners will be triggered. If not
412      * <tt>null</tt>, only this listener will be triggered. This feature is needed for
413      * re-fireing of old events after a new listener has been
414      * registered. The new listener must already be registered when calling this method
415      * and it will only be triggered, if it would be triggered with this param being
416      * <tt>null</tt>.
417      */

418     protected void notify(NotificationEvent event, NotificationListener onlyThisNotificationListener)
419     {
420         if (event == null)
421             throw new NullPointerException JavaDoc("Parameter 'event' must not be null!");
422
423         notificationListenersByZoneMutex.acquireReadLock();
424         try {
425
426             Collection JavaDoc zones;
427             if (event.getZone() == null) {
428                 // if the event has zone=null, we send the event to all zones
429
zones = notificationListenersByZone.keySet();
430             }
431             else {
432                 // otherwise, we send the event to the given zone and
433
// the listeners that are registered with zone=null.
434
zones = new LinkedList JavaDoc();
435                 zones.add(event.getZone());
436                 zones.add(null);
437             }
438     
439 // List subjectCarriers = new LinkedList();
440
// for (Iterator it = event.getSubjects().iterator(); it.hasNext(); ) {
441
// Object subject = it.next();
442
// subjectCarriers.add(new _SubjectCarrier(subject));
443
// }
444
// for (Iterator it = event.getSubjectClassesToClear().iterator(); it.hasNext(); ) {
445
// Class subjectClass = (Class)it.next();
446
// subjectCarriers.add(new _SubjectCarrier(subjectClass));
447
// }
448

449             // key: NotificationListener listener
450
// value: Map {
451
// String notificationMode
452
// SubjectBundle subjects
453
// }
454
Map JavaDoc listenerSubjects = new HashMap JavaDoc();
455     
456             for (Iterator JavaDoc itZones = zones.iterator(); itZones.hasNext(); ) {
457                 String JavaDoc zone = (String JavaDoc) itZones.next();
458     
459                 Map JavaDoc notificationListenersBySubjectClass = (Map JavaDoc) notificationListenersByZone.get(zone);
460                 if (notificationListenersBySubjectClass == null)
461                     continue;
462     
463                 for (Iterator JavaDoc itSubjects = event.getSubjectCarriers().iterator(); itSubjects.hasNext(); ) {
464                     SubjectCarrier subjectCarrier = (SubjectCarrier) itSubjects.next();
465                     
466                     for (Iterator JavaDoc itSubjectClasses = subjectCarrier.getSubjectClasses().iterator(); itSubjectClasses.hasNext(); ) {
467                         Class JavaDoc subjectClass = (Class JavaDoc) itSubjectClasses.next();
468                         boolean inheritanceIgnored = subjectCarrier.isInheritanceIgnored();
469                         boolean interfacesIgnored = subjectCarrier.isInterfacesIgnored();
470                         boolean breakOnFirstFound = subjectCarrier.isBreakOnFirstFound();
471                         boolean doBreak = false;
472                         Class JavaDoc clazz = subjectClass;
473                         do {
474                             Map JavaDoc mapListeners = (Map JavaDoc) notificationListenersBySubjectClass.get(clazz);
475                             if (mapListeners == null && !interfacesIgnored) {
476                                 Class JavaDoc[] interfaces = clazz.getInterfaces();
477                                 for (int i = 0; i < interfaces.length; i++) {
478                                     mapListeners = (Map JavaDoc) notificationListenersBySubjectClass.get(interfaces[i]);
479                                     if ( mapListeners != null)
480                                         break;
481                                 }
482                             }
483                             if (mapListeners != null) {
484                                 for (Iterator JavaDoc itListeners = mapListeners.values().iterator(); itListeners.hasNext(); ) {
485                                     Map JavaDoc mapModes = (Map JavaDoc) itListeners.next();
486                                     if (doBreak)
487                                         break;
488                                     for (Iterator JavaDoc itModes = mapModes.values().iterator(); itModes.hasNext(); ) {
489                                         NotificationListenerMeta meta = (NotificationListenerMeta) itModes.next();
490                                         NotificationListener listener = meta.getNotificationListener();
491         
492                                         if (onlyThisNotificationListener != null &&
493                                                 onlyThisNotificationListener != listener)
494                                             listener = null;
495         
496                                         if (listener != null) {
497                                             Map JavaDoc m = (Map JavaDoc) listenerSubjects.get(listener);
498                                             if (m == null) {
499                                                 m = new HashMap JavaDoc();
500                                                 listenerSubjects.put(listener, m);
501                                             }
502                                             SubjectBundle subjectBundle = (SubjectBundle) m.get(meta.getNotificationMode());
503                                             if (subjectBundle == null) {
504                                                 subjectBundle = new SubjectBundle();
505                                                 m.put(meta.getNotificationMode(), subjectBundle);
506                                             }
507                                             subjectBundle.subjectCarriers.add(subjectCarrier);
508                                             if (breakOnFirstFound) {
509                                                 doBreak = true;
510                                                 break;
511                                             }
512 // if (subjectCarrier.getSubject() == null)
513
// subjectBundle.subjectClassesToClear.add(subjectClass);
514
// else
515
// subjectBundle.subjects.add(subjectCarrier.getSubject());
516
} // if (listener != null) {
517
} // for (Iterator itListeners = mapListeners.values().iterator(); itListeners.hasNext(); ) {
518
} // for (Iterator itModeListeners = mapModeListeners.values().iterator(); itModeListeners.hasNext(); ) {
519
} // if (mapModeListeners != null) {
520

521                             if (doBreak)
522                                 break;
523                             if (inheritanceIgnored)
524                                 clazz = null;
525                             else
526                                 clazz = clazz.getSuperclass();
527                         } while (clazz != null);
528
529                     } // for (Iterator itSubjectClasses = subjectCarrier.getSubjectClasses().iterator(); itSubjectClasses.hasNext()) {
530
} // for (Iterator itSubjects = event.getSubjects().iterator(); itSubjects.hasNext(); ) {
531

532                 for (Iterator JavaDoc itListeners = listenerSubjects.entrySet().iterator(); itListeners.hasNext(); ) {
533                     Map.Entry JavaDoc meListener = (Map.Entry JavaDoc)itListeners.next();
534                     NotificationListener listener = (NotificationListener) meListener.getKey();
535                     Map JavaDoc mapModeSubjects = (Map JavaDoc) meListener.getValue();
536                     for (Iterator JavaDoc itModes = mapModeSubjects.entrySet().iterator(); itModes.hasNext(); ) {
537                         Map.Entry JavaDoc meMode = (Map.Entry JavaDoc) itModes.next();
538                         String JavaDoc notificationMode = (String JavaDoc) meMode.getKey();
539                         SubjectBundle subjectBundle = (SubjectBundle) meMode.getValue();
540         
541                         NotificationEvent newEvent;
542                         if (subjectBundle.subjectCarriers.size() == event.getSubjectCarriers().size())
543                             newEvent = event;
544                         else
545                             newEvent = new NotificationEvent(
546                                     event.getSource(), event.getZone(),
547                                     null, null, subjectBundle.subjectCarriers);
548
549                         performNotification(notificationMode, listener, newEvent);
550                     }
551                 }
552             } // for (Iterator itZones = zones.iterator(); itZones.hasNext(); ) {
553

554         } finally {
555             notificationListenersByZoneMutex.releaseLock();
556         }
557     }
558
559     /**
560      * If you want to implement additional notification modes, you must override this method.
561      * It is supposed to call {@link #performNotification(NotificationListener, NotificationEvent)}
562      * on the thread which is defined by notification mode.
563      */

564     protected void performNotification(
565             String JavaDoc notificationMode,
566             final NotificationListener listener, final NotificationEvent event)
567     {
568         if (NotificationListenerCallerThread.class.getName().equals(notificationMode)) {
569             listener.notify(event);
570         }
571         else if (NotificationListenerWorkerThreadAsync.class.getName().equals(notificationMode)) {
572             Thread JavaDoc worker = new Thread JavaDoc() {
573                 public void run() {
574                     listener.notify(event);
575                 }
576             };
577             worker.start();
578         }
579         else if (NotificationListenerAWTThreadSync.class.getName().equals(notificationMode)) {
580             try {
581                 SwingUtilities.invokeAndWait(new Runnable JavaDoc() {
582                     public void run() {
583                         listener.notify(event);
584                     }
585                 });
586             } catch (InterruptedException JavaDoc e) {
587                 throw new RuntimeException JavaDoc(e);
588             } catch (InvocationTargetException JavaDoc e) {
589                 throw new RuntimeException JavaDoc(e);
590             }
591         }
592         else if (NotificationListenerAWTThreadAsync.class.getName().equals(notificationMode)) {
593             SwingUtilities.invokeLater(new Runnable JavaDoc() {
594                 public void run() {
595                     listener.notify(event);
596                 }
597             });
598         }
599         else
600             throw new IllegalArgumentException JavaDoc("unknown notificationMode: " + notificationMode);
601     }
602 }
603
Popular Tags