/* Wotonomy: OpenStep design patterns for pure Java applications. Copyright (C) 2001 Intersect Software Corporation This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see http://www.gnu.org */ package net.wotonomy.foundation; import java.util.Iterator; import java.util.LinkedList; import java.util.List; /** * NSNotificationQueue coalesces notifications to be * posted to the NSNotificationCenter and can post them * asynchronously. While calling postNotification on * the notification center does not return until all * receivers have been notified, calling enqueueNotification * can return immediately. Use this class when you want * to coalesce notifications or notify asynchronously, or * both, which is the typical case. * * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 893 $ */ public class NSNotificationQueue { private static NSNotificationQueue defaultQueue = null; /** * Posting style specifying that the notification should * be posted on the next available event loop. */ public static final int PostASAP = 4; /** * Posting style specifying that the notification should * be posted on the next available idle loop. */ public static final int PostWhenIdle = 8; /** * Posting style specifying that the notification should * be posted immediately. The enqueue method will not * return until all receivers have been notified. */ public static final int PostNow = 16; /** * Used to indicate that this notification should not * be coalesced with other notifications. * Ignored if combined with other coalesce flags. */ public static final int NotificationNoCoalescing = 0; /** * Used to indicate that this notification should * be coalesced with other notifications with the * same name. * May be combined with NotificationCoalescingOnSender. */ public static final int NotificationCoalescingOnName = 1; /** * Used to indicate that this notification should * be coalesced with other notifications with the * same object argument (which is typically the sender). * May be combined with NotificationCoalescingOnName. */ public static final int NotificationCoalescingOnSender = 2; /** * The ASAP queue, which should probably use a LinkedList. */ private List queue; /** * The idle queue, which should probably use a LinkedList. */ private List idleQueue; /** * The notification center we will be using. */ private NSNotificationCenter center; /** * Our private ASAP notifier. */ private Notifier notifier; /** * Our private idle notifier. */ private Notifier idleNotifier; /** * Default constructor creates a new notification queue * that uses the default notification center. */ public NSNotificationQueue() { this( NSNotificationCenter.defaultCenter() ); } /** * Creates a new notification queue that uses the * specified notification center. */ public NSNotificationQueue( NSNotificationCenter aCenter ) { queue = new LinkedList(); idleQueue = new LinkedList(); center = aCenter; notifier = new Notifier( this, queue ); idleNotifier = new Notifier( this, idleQueue ); } /** * Returns the system default queue, creating one * if it has not yet been created. The system default * queue uses the system default notification center. */ static public NSNotificationQueue defaultQueue() { if ( defaultQueue == null ) { defaultQueue = new NSNotificationQueue(); } return defaultQueue; } /** * Removes notifications from the queue that match * the specified notification, considering the * specified coalesce mask. */ public void dequeueMatchingNotifications( NSNotification aNotification, int aCoalesceMask) { if ( aCoalesceMask == NotificationNoCoalescing ) return; dequeueFromQueue( aNotification, aCoalesceMask, queue ); dequeueFromQueue( aNotification, aCoalesceMask, idleQueue ); } private void dequeueFromQueue( NSNotification aNotification, int aCoalesceMask, List aQueue ) { synchronized ( aQueue ) { int flag; NSNotification notification; Object name = aNotification.name(); Object object = aNotification.object(); Iterator it = aQueue.iterator(); while ( it.hasNext() ) { flag = 0; notification = (NSNotification) it.next(); // if NotificationCoalescingOnName if ( ( aCoalesceMask == 1 ) || ( aCoalesceMask == 3 ) ) { if ( name == null ) { if ( notification.name() != null ) { flag += NotificationCoalescingOnName; } } else { // compare by value if ( name.equals( notification.name() ) ) { flag += NotificationCoalescingOnName; } } } // if NotificationCoalescingOnSender if ( aCoalesceMask >= 2 ) { // compare by reference if ( object == notification.object() ) { flag += NotificationCoalescingOnSender; } } if ( flag == aCoalesceMask ) { it.remove(); } } } } /** * Adds the notification to the queue to be run at * the time specified by the posting style. The * notification will be coalesced with other notifications * that match the same name and object (sender) argument. */ public void enqueueNotification( NSNotification aNotification, int aPostingStyle) { enqueueNotificationWithCoalesceMaskForModes( aNotification, aPostingStyle, NotificationCoalescingOnName + NotificationCoalescingOnSender, null ); } /** * Adds the notification to the queue to be run at * the time specified by the posting style and coelesced * as the specified mask indicates. * aModeList is currently ignored and may be null. */ public void enqueueNotificationWithCoalesceMaskForModes( NSNotification aNotification, int aPostingStyle, int aCoalesceMask, List aModeList) { dequeueMatchingNotifications( aNotification, aCoalesceMask ); if ( aPostingStyle == PostNow ) { center.postNotification( aNotification ); return; } if ( aPostingStyle == PostASAP ) { synchronized ( queue ) { queue.add( aNotification ); if ( ! notifier.willRun ) { // asap runs at the very first run loop ordering, plus one just in case NSRunLoop.invokeLaterWithOrder( notifier, 1 ); notifier.willRun = true; } } return; } if ( aPostingStyle == PostWhenIdle ) { synchronized ( idleQueue ) { idleQueue.add( aNotification ); if ( ! idleNotifier.willRun ) { // when idle runs at the very last run loop ordering, minus one just in case NSRunLoop.invokeLaterWithOrder( idleNotifier, Integer.MAX_VALUE - 1 ); idleNotifier.willRun = true; } } return; } } private class Notifier implements Runnable { public boolean willRun; NSNotificationQueue parent; List queue; public Notifier( NSNotificationQueue aParent, List aQueue ) { willRun = false; parent = aParent; queue = aQueue; } public void run() { Iterator it = new LinkedList( queue ).iterator(); synchronized ( queue ) { queue.clear(); } willRun = false; // queue must already be cleared and willRun reset // because this loop might queue more notifications while ( it.hasNext() ) { parent.center.postNotification( (NSNotification) it.next() ); } } } } /* * $Log$ * Revision 1.2 2006/02/16 13:15:00 cgruber * Check in all sources in eclipse-friendly maven-enabled packages. * * Revision 1.6 2003/08/11 18:18:08 chochos * improved encoding of strings, removed warnings * * Revision 1.5 2003/08/06 23:07:52 chochos * general code cleanup (mostly, removing unused imports) * * Revision 1.4 2001/11/01 15:49:26 mpowers * With NSRunLoop, we can now correctly implement PostASAP and PostWhenIdle. * * Revision 1.3 2001/06/25 14:47:24 mpowers * Fixed serious error where some notifications were not being posted at all. * A simple change to Notifier class fixed the problem. Thanks to glista. * * Revision 1.2 2001/02/26 15:53:22 mpowers * Fine-tuning notification firing. * Child display groups now update properly after parent save or invalidate. * * Revision 1.1 2001/02/24 17:03:22 mpowers * Implemented the notification queue, and changed editing context to use it. * * */