All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.refcodes.observer.impls.AbstractObservable Maven / Gradle / Ivy

Go to download

Artifact for event handling purposes according to the observer (observable) pattern.

There is a newer version: 1.1.1
Show newest version
// /////////////////////////////////////////////////////////////////////////////
// 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; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy