
org.refcodes.observer.impls.AbstractObservable Maven / Gradle / Ivy
Show all versions of refcodes-observer Show documentation
// /////////////////////////////////////////////////////////////////////////////
// REFCODES.ORG
// =============================================================================
// This code is copyright (c) by Siegfried Steiner, Munich, Germany and licensed
// under the following (see "http://en.wikipedia.org/wiki/Multi-licensing")
// licenses:
// =============================================================================
// GNU General Public License, v3.0 ("http://www.gnu.org/licenses/gpl-3.0.html")
// together with the GPL linking exception applied; as being applied by the GNU
// Classpath ("http://www.gnu.org/software/classpath/license.html")
// =============================================================================
// Apache License, v2.0 ("http://www.apache.org/licenses/LICENSE-2.0")
// =============================================================================
// Please contact the copyright holding author(s) of the software artifacts in
// question for licensing issues not being covered by the above listed licenses,
// also regarding commercial licensing models or regarding the compatibility
// with other open source licenses.
// /////////////////////////////////////////////////////////////////////////////
package org.refcodes.observer.impls;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import org.refcodes.collection.Container;
import org.refcodes.controlflow.ControlFlowUtility;
import org.refcodes.controlflow.ExecutionStrategy;
import org.refcodes.exception.ExceptionUtility;
import org.refcodes.exception.VetoException;
import org.refcodes.exception.VetoException.VetoRuntimeException;
import org.refcodes.logger.RuntimeLogger;
import org.refcodes.logger.impls.RuntimeLoggerFactorySingleton;
import org.refcodes.mixin.Disposable;
import org.refcodes.observer.ActionEvent;
import org.refcodes.observer.Observable;
/**
* This abstract class provides functionality to implement default refcodes
* {@link Observable} behavior.
*
* The {@link #fireEvent(Object, Object, ExecutionStrategy)} is to be
* overwritten to invoke the according event lister'#s method for a given event
* to be distributed.
*
* The {@link #doHandleEventListenerException(Exception, Object, Object)} method
* can be overwritten to handle any exceptions thrown by the
* {@link #fireEvent(Object, Object, ExecutionStrategy)} method.
*
* Depending on the {@link ExecutionStrategy}, distribution of the events is
* handled differently:
*
* {@link ExecutionStrategy#SEQUENTIAL}:
*
* You determine that each event listener is signaled one after the other and
* not in parallel. In case you want to restrict thread generation, this
* sequential event distribution is the one to go for: Each event listener is
* invoked after the other one after the other.
*
* The execution chain of invoking the event listeners can be aborted by a
* boolean flag returned by an invoked event listener method or by a
* {@link VetoException} thrown by an invoked event listener method.
*
* In sequential event distribution, you can also take care of exceptions thrown
* or return vales passed to you by the event listeners to affect the execution
* of the chain of event listeners.
*
* As soon as the execution chain of invoking event listeners terminates, then
* this method terminates.
*
* {@link ExecutionStrategy#PARALLEL}:
*
* You determine that each event listener is signaled in parallel. In case you
* want to prevent that one event listener can cause the succeeding event
* listeners to be delayed for signaling, then the parallel mode is the one to
* go for.
*
* As soon as all event listeners have their own thread, this method exits. You
* cannot affect the execution of event listeners as all of them are invoked
* ignoring any exceptions or return values of the other invoked event
* listeners.
*
* Here as we do not collect any exceptions or any results from each invoked
* event listener, there are no means provided to evaluate them during or after
* event distribution.
*
* {@link ExecutionStrategy#JOIN}:
*
* Similar to {@link ExecutionStrategy#PARALLEL} with the difference, that all
* threads are joined and the method terminates as soon as the latest thread
* terminates.
*
* As of this, a {@link VetoException} as well as a return value can be
* evaluated though still no influence can be taken upon the execution of the
* invocation of the event listeners.
*
* In case any of the listeners returns false, then this event distribution mode
* will cause to return false, in case any event listener throws a
* {@link VetoException}, then the first detected {@link VetoException} is
* thrown when executing with this event distribution mode.
*
* No matter of the exceptions or return values, as of the
* {@link ExecutionStrategy#PARALLEL} event distribution mode, all event
* listeners are invoked.
*
* @param The observer's (event listeners) to be managed by the
* {@link Observable}.
*
* @param The base event to be fired by this {@link Observable}.
*/
public abstract class AbstractObservable implements Observable, Disposable {
private static RuntimeLogger LOGGER = RuntimeLoggerFactorySingleton.createRuntimeLogger();
// /////////////////////////////////////////////////////////////////////////
// VARIABLES:
// /////////////////////////////////////////////////////////////////////////
private List _observers = new ArrayList();
private int _threadPriority = Thread.NORM_PRIORITY;
private ExecutorService _executorService;
private boolean _isDisposed = false;
// /////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS:
// /////////////////////////////////////////////////////////////////////////
/**
* Constructs the {@link AbstractObservable} with a default
* {@link ExecutorService} pool.
*/
public AbstractObservable() {
this( null );
}
/**
* Constructs the {@link AbstractObservable} with a provided
* {@link ExecutorService} pool.
*
* @param aExecutorService The {@link ExecutorService} to be used when
* firing {@link ActionEvent} instances in
* {@link ExecutionStrategy#PARALLEL} or
* {@link ExecutionStrategy#JOIN}
*/
public AbstractObservable( ExecutorService aExecutorService ) {
if ( aExecutorService == null ) {
_executorService = ControlFlowUtility.createDaemonExecutorService();
// _executorService = ControlFlowUtility.getExecutorService();
}
else {
_executorService = ControlFlowUtility.toManagedExecutorService( aExecutorService );
}
}
// /////////////////////////////////////////////////////////////////////////
// METHODS:
// /////////////////////////////////////////////////////////////////////////
/**
* To be used by the implementing class when firing an event to it's
* listeners. Use one of the event distribution modes as defined in
* {@link ExecutionStrategy}.
*
* @param aEvent The event to be fired.
*
* @param aExecutionStrategy The event
*
* @return True in case all event listeners were invoked, false in case one
* invoked event listener returned false ("stop continuing") upon
* invocation.
*
* @exception VetoException in case one of the invoked event listeners
* signaled a veto by throwing that according method. The
* {@link VetoException} can only reliably be evaluated in case
* the event listeners are executed in sequence.
*/
protected boolean fireEvent( EV aEvent, ExecutionStrategy aExecutionStrategy ) throws VetoException {
ControlFlowUtility.throwIllegalStateException( _isDisposed );
if ( !_observers.isEmpty() ) {
switch ( aExecutionStrategy ) {
case PARALLEL:
fireParallelEvent( aEvent );
return true;
case JOIN:
return fireJoinEvent( aEvent );
case SEQUENTIAL:
return fireSequentialEvent( aEvent );
}
}
return true;
}
@Override
public boolean hasObserverSubscription( O aObserver ) {
ControlFlowUtility.throwIllegalStateException( _isDisposed );
return _observers.contains( aObserver );
}
@Override
public boolean subscribeObserver( O aObserver ) {
ControlFlowUtility.throwIllegalStateException( _isDisposed );
if ( !_observers.contains( aObserver ) ) {
synchronized ( this ) {
if ( !_observers.contains( aObserver ) ) { return _observers.add( aObserver ); }
}
}
return false;
}
@Override
public boolean unsubscribeObserver( O aObserver ) {
ControlFlowUtility.throwIllegalStateException( _isDisposed );
return _observers.remove( aObserver );
}
// /////////////////////////////////////////////////////////////////////////
// HOOKS:
// /////////////////////////////////////////////////////////////////////////
/**
* This hook method is to be implemented by the implementing class. Here you
* decide which method of the event listener is to be invoked with the
* provided event and what actions apply upon invoking the event listener's
* methods. E.g. your event listener's methods might veto an event by
* throwing an according exception which you can pass down the stack here,
* or you might want to ignore any exceptions being thrown or you proceed
* according the the return value of some listener's method.
*
* Distribution of events to succeeding event listeners, in case of the
* SEQUENTIAL {@link ExecutionStrategy}, is prevented, when false is
* returned ("stop invoking succeeding event listeners"), succeeding event
* listeners are invoked in case true is returned ("continue invoking
* succeeding event listeners"). In CONCURRENT {@link ExecutionStrategy} the
* return value does not have an effect.
*
* Throwing a {@link VetoException} has a similar effect in case of the
* SEQUENTIAL {@link ExecutionStrategy}, preventing execution of succeeding
* event listeners and passing down the exception the stack so that your
* business logic can stop a vetoed operation.
*
* @param aEvent The event to be passed to the event listener
* @param aObserver The event listener to which to pass the event
* @param aExecutionStrategy Can be either CONCURRENT signaling that the
* event has been fired concurrently to the event listeners, each
* event listener is invoked in its own thread or SEQUENTIAL
* signaling that the event has been fired to the event listeners in
* sequence, each event listener is invoked one after the other one
* by one in the calling thread.
*
* @return True in case succeeding event listeners are to be invoked in case
* of the SEQUENTIAL {@link ExecutionStrategy}.
*
* @throws VetoException thrown in case an operation published by the given
* event has been vetoed by an event listener. Succeeding event
* listeners are not being invoked any more in case of the
* SEQUENTIAL {@link ExecutionStrategy}.
*/
abstract protected boolean fireEvent( EV aEvent, O aObserver, ExecutionStrategy aExecutionStrategy ) throws VetoException;
/**
* This hook method allows you to handle any exceptions being thrown by an
* event listener whilst invoking a given event.
*
* @param aException The exception thrown by the given event listener.
* @param aObserver The listener which caused the exception.
* @param aEvent The event for which the exception was caused.
*/
protected void doHandleEventListenerException( Exception aException, O aObserver, EV aEvent, ExecutionStrategy aExecutionStrategy ) {
LOGGER.warn( "Caught an unexpected exception while invoking event observer instances with strategy \"" + aExecutionStrategy + "\" with message: " + ExceptionUtility.toMessage( aException ), aException );
}
// /////////////////////////////////////////////////////////////////////////
// COLLECTION:
// /////////////////////////////////////////////////////////////////////////
/**
* Determines the number of observers being registered, similar to
* {@link Container#size()}.
*
* @return The number of observers being registered.
*/
protected int size() {
return _observers.size();
}
/**
* Determines whether there are observers being registered, similar to
* {@link Container#isEmpty()}.
*
* @return True in case there are observers being registered.
*/
protected boolean isEmpty() {
return _observers.isEmpty();
}
/**
* Clears all observers from this {@link AbstractObservable}.
*/
protected void clear() {
_observers.clear();
}
// /////////////////////////////////////////////////////////////////////////
// ATTRIBUTES:
// /////////////////////////////////////////////////////////////////////////
/**
* In case of {@link ExecutionStrategy#PARALLEL} or
* {@link ExecutionStrategy#JOIN}, the threads' priority is defined by this
* attribute.
*
* @return The thread priority for the threads to be generated.
*/
public int getThreadPriority() {
return _threadPriority;
}
/**
* In case of {@link ExecutionStrategy#PARALLEL} or
* {@link ExecutionStrategy#JOIN}, the threads' priority is defined by this
* attribute.
*
* @param threadPriority The thread priority for the threads to be
* generated.
*/
public void setThreadPriority( int threadPriority ) {
_threadPriority = threadPriority;
}
@Override
public void dispose() {
ControlFlowUtility.shutdownGracefully( _executorService );
_executorService = null;
clear();
_isDisposed = true;
}
// /////////////////////////////////////////////////////////////////////////
// HELPER:
// /////////////////////////////////////////////////////////////////////////
/**
* This method distributes the events according to the distribution mode
* {@link ExecutionStrategy#SEQUENTIAL}:
*
* You determine that each event listener is signaled one after the other
* and not in parallel. In case you want to restrict thread generation, this
* sequential event distribution is the one to go for: Each event listener
* is invoked after the other one after the other.
*
* Firing events with this event execution mode causes to terminate with a
* return value of false as soon as one event listener returns false or by
* throwing a {@link VetoException} as soon as one event listener throws a
* {@link VetoException}.
*
* @param The event to be fired.
*
* @return False in case one event listener returned false, the execution of
* the succeeding event listeners is aborted.
*
* @throws VetoException in case one event listener threw a
* {@link VetoException}, the execution of the succeeding event
* listeners is aborted.
*/
private boolean fireSequentialEvent( EV aEvent ) throws VetoException {
if ( !_observers.isEmpty() ) {
for ( O eEventListener : _observers ) {
// Call the hook method for event distribution:
try {
// Call hook method for exception handling:
if ( !fireEvent( aEvent, eEventListener, ExecutionStrategy.SEQUENTIAL ) ) { return false; }
}
catch ( Exception e ) {
if ( e instanceof VetoException ) throw (VetoException) e;
if ( e instanceof VetoRuntimeException ) throw (VetoRuntimeException) e;
doHandleEventListenerException( e, eEventListener, aEvent, ExecutionStrategy.SEQUENTIAL );
}
}
}
return true;
}
/**
*
* This method distributes the events according to the distribution mode
* {@link ExecutionStrategy#PARALLEL}:
*
* You determine that each event listener is signaled in parallel. In case
* you want to prevent that one event listener can cause the succeeding
* event listeners to be delayed for signaling, then the parallel mode is
* the one to go for.
*
* Here as we do not collect any exceptions or any results from each invoked
* event listener, there are no means provided to evaluate them during or
* after event distribution.
*
* @param The event to be fired.
*/
private void fireParallelEvent( final EV aEvent ) {
if ( !_observers.isEmpty() ) {
Runnable eRunnable;
for ( final O eEventListener : _observers ) {
eRunnable = new Runnable() {
@Override
public void run() {
// Call the hook method for event distribution:
try {
// Call hook method for exception handling:
fireEvent( aEvent, eEventListener, ExecutionStrategy.PARALLEL );
}
catch ( Exception e ) {
doHandleEventListenerException( e, eEventListener, aEvent, ExecutionStrategy.PARALLEL );
}
}
};
_executorService.execute( eRunnable );
}
}
}
/**
* This method distributes the events according to the distribution mode
* {@link ExecutionStrategy#JOIN}:
*
* Similar to {@link ExecutionStrategy#PARALLEL} with the difference, that
* all threads are joined and the method terminates as soon as the latest
* thread terminates.
*
* In case any of the event listeners threw a {@link VetoException}, then
* the first {@link VetoException} is thrown after all threads terminated.
*
* In case any of the event listeners returned false, then false is returned
* after all threads terminated.
*
* @param The event to be fired.
*
* @return False in case at least one of the event listeners returned false.
*
* @throws VetoException in case at least one event listener threw a
* {@link VetoException}.
*
*/
private boolean fireJoinEvent( final EV aEvent ) throws VetoException {
boolean theResult = true;
if ( !_observers.isEmpty() ) {
List> theFutures = new ArrayList>();
Callable eCallable;
for ( final O eEventListener : _observers ) {
eCallable = new Callable() {
@Override
public Boolean call() throws Exception {
// Call the hook method for event distribution:
try {
// Call hook method for exception handling:
return fireEvent( aEvent, eEventListener, ExecutionStrategy.JOIN );
}
catch ( Exception e ) {
doHandleEventListenerException( e, eEventListener, aEvent, ExecutionStrategy.JOIN );
throw e;
}
}
};
theFutures.add( _executorService.submit( eCallable ) );
}
VetoException theVetoException = null;
for ( Future eJoinFuture : theFutures ) {
try {
if ( !eJoinFuture.get() ) {
theResult = false;
}
}
catch ( Exception e ) {
if ( e instanceof VetoException ) {
theVetoException = (VetoException) e;
}
else {
LOGGER.warn( "Caught an unexpected exception while invoking and joining event observer instances.", e );
}
}
}
if ( theVetoException != null ) { throw theVetoException; }
}
return theResult;
}
}