text: /*
   * Copyright (c) 1999-2000, Eric Moon.
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions, and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions, and the following disclaimer in the
   *    documentation and/or other materials provided with the distribution.
   *
   * 3. The name of the author may not be used to endorse or promote products
   *    derived from this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
   * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
   * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
   * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  
  // ObservableLooper.cpp
  
  #include "ObservableLooper.h"
  
  #include <Debug.h>
  #include <MessageRunner.h>
  
  __USE_CORTEX_NAMESPACE
  
  
  // ---------------------------------------------------------------- //
  // *** deletion
  // ---------------------------------------------------------------- //
  
  // clients must call release() rather than deleting,
  // to ensure that all observers are notified of the
  // object's demise.  if the object has already been
  // released, return an error.
  
  status_t ObservableLooper::release() {
  
  	// +++++ what if I'm not running?
  	// +++++ is a lock necessary?
  	
  	if(isReleased())
  		return B_NOT_ALLOWED;
  	
  	// send request through proper channels
  	BMessenger(this).SendMessage(B_QUIT_REQUESTED);
  
  	return B_OK;
  }
  
  // ---------------------------------------------------------------- //
  // *** ctor/dtor
  // ---------------------------------------------------------------- //
  
  ObservableLooper::~ObservableLooper() {
  	if(CountTargets()) {
  		PRINT((
  			"*** ~ObservableLooper() '%s': %" B_PRId32 " observers remain\n",
  			Name(), CountTargets()));
  	}
  	if(m_executioner)
  		delete m_executioner;
  }
  
  ObservableLooper::ObservableLooper(
  	const char*							name,
  	int32										priority,
  	int32										portCapacity,
  	bigtime_t								quitTimeout) :
  	BLooper(name, priority, portCapacity),
  	m_quitTimeout(quitTimeout),
  	m_executioner(0),
  	m_quitting(false) {}
  
  ObservableLooper::ObservableLooper(
  	BMessage*								archive) :
  	BLooper(archive),
  	m_quitTimeout(B_INFINITE_TIMEOUT),
  	m_executioner(0),
  	m_quitting(false) {
  
  	archive->FindInt64("quitTimeout", (int64*)&m_quitTimeout);
  }
  
  // ---------------------------------------------------------------- //
  // *** accessors
  // ---------------------------------------------------------------- //
  
  bool ObservableLooper::isReleased() const {
  	return m_quitting;
  }
  
  // ---------------------------------------------------------------- //
  // *** hooks
  // ---------------------------------------------------------------- //
  
  // sends M_OBSERVER_ADDED to the newly-added observer
  void ObservableLooper::observerAdded(
  	const BMessenger&				observer) {
  	
  	BMessage m(M_OBSERVER_ADDED);
  	m.AddMessenger("target", BMessenger(this));
  	observer.SendMessage(&m);
  }
  		
  // sends M_OBSERVER_REMOVED to the newly-removed observer		
  void ObservableLooper::observerRemoved(
  	const BMessenger&				observer) {
  
  	BMessage m(M_OBSERVER_REMOVED);
  	m.AddMessenger("target", BMessenger(this));
  	observer.SendMessage(&m);
  }
  
  // ---------------------------------------------------------------- //
  // *** internal operations
  // ---------------------------------------------------------------- //
  
  status_t ObservableLooper::notify(
  	BMessage*								message) {
  	ASSERT(IsLocked());
  
  	return Invoke(message);
  }
  
  // sends M_RELEASE_OBSERVABLE
  void ObservableLooper::notifyRelease() {
  	BMessage m(M_RELEASE_OBSERVABLE);
  	m.AddMessenger("target", BMessenger(this));
  	notify(&m);
  }
  
  // ---------------------------------------------------------------- //
  // *** BLooper
  // ---------------------------------------------------------------- //
  
  void ObservableLooper::Quit() {
  	ASSERT(IsLocked());
  	
  	if(QuitRequested()) {
  		releaseComplete();
  		_inherited::Quit();
  	}
  	else
  		Unlock();
  }
  
  bool ObservableLooper::QuitRequested() {
  
  	if(CountTargets()) {
  		if(!m_quitting) {
  			m_quitting = true;
  			
  			// no release request yet sent
  			notifyRelease();
  
  			if(m_quitTimeout != B_INFINITE_TIMEOUT) {
  				// Initiate a timer to force quit -- if an observer
  				// has died, it shouldn't take me down with it.
  				ASSERT(!m_executioner);
  				m_executioner = new BMessageRunner(
  					BMessenger(this),
  					new BMessage(M_KILL_OBSERVABLE),
  					m_quitTimeout,
  					1);
  			}			
  		}
  
  		// targets remain, so don't quit.
  		return false;
  	}
  	
  	// okay to quit
  	return true;
  }
  	
  // ---------------------------------------------------------------- //
  // *** BHandler
  // ---------------------------------------------------------------- //
  
  void ObservableLooper::MessageReceived(
  	BMessage*								message) {
  	
  //	PRINT((
  //		"### ObservableLooper::MessageReceived()\n"));
  //	message->PrintToStream();
  
  	switch(message->what) {
  		case M_ADD_OBSERVER:
  			_handleAddObserver(message);
  			break;
  
  		case M_REMOVE_OBSERVER:
  			_handleRemoveObserver(message);
  			break;
  		
  		case M_KILL_OBSERVABLE:
  			releaseComplete();
  			BLooper::Quit();
  			break;
  
  		default:
  			_inherited::MessageReceived(message);
  	}
  }
  
  // ---------------------------------------------------------------- //
  // *** BArchivable
  // ---------------------------------------------------------------- //
  
  status_t ObservableLooper::Archive(
  	BMessage*								archive,
  	bool										deep) const {
  	
  	ASSERT(IsLocked());
  
  	// can't archive an object in limbo
  	if(m_quitting)
  		return B_NOT_ALLOWED;
  	
  	status_t err = _inherited::Archive(archive, deep);
  	if(err < B_OK)
  		return err;
  	
  	archive->AddInt64("quitTimeout", m_quitTimeout);
  	return B_OK;
  }		
  
  // ---------------------------------------------------------------- //
  // implementation
  // ---------------------------------------------------------------- //
  
  void ObservableLooper::_handleAddObserver(
  	BMessage*								message) {
  
  	BMessage reply;
  
  	BMessenger observer;
  	status_t err = message->FindMessenger(
  		"observer", &observer);
  	if(err < B_OK) {
  		PRINT((
  			"* ObservableLooper::_handleAddObserver(): no observer specified!\n"));
  		// send reply? +++++
  		return;
  	}
  
  	// at this point, a reply of some sort will be sent		
  	reply.AddMessenger("target", BMessenger(this));
  	reply.AddMessenger("observer", observer);
  
  	if(m_quitting) {
  		// already quitting
  		reply.what = M_BAD_TARGET;
  	}
  	else if(IndexOfTarget(observer.Target(0)) != -1) {
  		// observer already added
  		reply.what = M_BAD_OBSERVER;
  	}	
  	else {
  		// add it
  		err = AddTarget(observer.Target(0));
  		ASSERT(err == B_OK);
  		reply.what = M_OBSERVER_ADDED;
  	}
  	
  	// send reply
  	message->SendReply(&reply);
  	
  	// call hook
  	observerAdded(observer);
  }
  		
  void ObservableLooper::_handleRemoveObserver(
  	BMessage*								message) {
  
  //	PRINT(("ObservableLooper::_handleRemoveObserver():\n"
  //		"  %ld targets\n", CountTargets()));
  	BMessage reply;
  
  	BMessenger observer;
  	status_t err = message->FindMessenger(
  		"observer", &observer);
  	if(err < B_OK) {
  		PRINT((
  			"* ObservableLooper::_handleRemoveObserver(): no observer specified!\n"));
  		// send reply? +++++
  		return;
  	}
  
  	// at this point, a reply of some sort will be sent		
  	reply.AddMessenger("target", BMessenger(this));
  	reply.AddMessenger("observer", observer);
  	
  	int32 index = IndexOfTarget(observer.Target(0));
  	if(index == -1) {
  		reply.what = M_BAD_OBSERVER;
  	}
  	else {
  		RemoveTarget(index);
  		reply.what = M_OBSERVER_REMOVED;
  	}
  	
  	message->SendReply(&reply);
  	
  	// call hook
  	observerRemoved(observer);
  	
  	// time to shut down?
  	if(m_quitting && !CountTargets()) {
  		releaseComplete();
  		BLooper::Quit();
  	}
  }
  
  // END -- ObservableLooper.cpp --
  