/*
* class: SequentialRecordLoopImpl
*
* Version $Id: SequentialRecordLoopImpl.java 8584 2006-08-10 23:06:37Z duns $
*
* Date: February 10 2003
*
* (c) 2003 LBNL
*/
package org.freehep.record.loop;
import org.freehep.record.loop.event.ConfigurationEvent;
import org.freehep.record.loop.event.LoopEvent;
import org.freehep.record.loop.event.LoopProgressEvent;
import org.freehep.record.loop.event.RecordAdapter;
import org.freehep.record.loop.event.RecordEvent;
import org.freehep.record.loop.event.RecordListener;
import org.freehep.record.loop.event.RecordLoopListener;
import org.freehep.record.loop.event.RecordSuppliedEvent;
import org.freehep.record.source.EndOfSourceException;
import org.freehep.record.source.NoSuchRecordException;
import org.freehep.record.source.SequentialRecordSource;
import java.io.IOException;
import java.util.TooManyListenersException;
import java.util.Vector;
/**
This class is the default implementation of the
<code>SequentialRecordLoop</code> interface.
This class is minimally thread safe i.e. only the "setInterruptResquested"
and "isInterruptRequested" methods are considered thread safe. The rest
of the implementation is designed to run in a single thread. When its
RecordListener responses to any RecordEvents, a return from that
method signals that the listener has completed all responsibilities
related to that message.
*
* @version $Id: SequentialRecordLoopImpl.java 8584 2006-08-10 23:06:37Z duns $
* @author patton
*/
public class SequentialRecordLoopImpl
implements SequentialRecordLoop
{
// public static final member data
// protected static final member data
// static final member data
// private static final member data
/** Listener to use if no other has been added. */
private static final RecordListener NULL_LISTENER =
new RecordAdapter();
/** The maximum value of the progress fraction. */
private static final double MAX_PROGRESS = (double) 1.0;
/** The value to set to ignore the progress interval. */
private static final long IGNORE_PROGRESS = (long) 1;
/** The default progress interval, number of millisseconds, if no
* guideline have been set. */
private static final long DEFAULT_MILLIS_INTERVAL = (long) 2000;
// private static member data
// private instance member data
/** The SequentialRecordSource in use with this object. */
private SequentialRecordSource source;
/** The RecordListener currently listening to this object. */
private RecordListener listener = NULL_LISTENER;
/** The list of RecordLoopListeners listeninf to this object. */
private Vector loopListeners = new Vector();
/** The total number of records supplied. */
private long totalSupplied;
/** The total number countable of records supplied. */
private long totalCountableSupplied;
/** The number of countable countable records to supply. */
private long target;
/** The number of records supplied by the current or last loop. */
private long supplied;
/** The number of countable records supplied by the current or last
* loop. */
private long countableSupplied;
/** Standard RecordEvent for this object to send out. */
private final RecordEvent recordEvent;
/** Standard LoopEvent for this object to send out. */
private final LoopEvent loopEvent;
/** True if the loop should be interrupted. */
private boolean interruptRequested;
/** True if interruptRequested was true at last attempt to
* getCurrentRecord. */
private boolean stoppedDueToInterrupt;
/** True if the RecordListener should be configured rather than
* reconfigured. */
private boolean newListener;
/** True if the current configuration object has been set since the last
* loop. */
private boolean newConfiguration;
/** The current configureation object. */
private Object configuration;
/** True if this object's source is been exhausted. */
private boolean sourceExhausted;
/** True if this object's source can not read the expected record. */
private boolean noRecord;
/** The current record being supplied by this object. */
private Object currentRecord;
/** True is the current record is a countable record. */
private boolean countableRecord;
/** The the number of records between progress reports. */
private long recordInterval;
/** The value of totalSupplied at the last progress report. */
private long lastProgressSupplied;
/** The the number of milliseconds between progress reports. */
private long millisInterval;
/** The time of the last progress report. */
private long lastProgressTime;
// constructors
/**
* Create an instance of this class.
*/
public SequentialRecordLoopImpl()
{
this(null);
}
/**
* Create an instance of this class with the specified
* SequentialRecordSource.
*
* @param supplier the SequentialRecordSource to use with this object.
* @see #setRecordSource
*/
public SequentialRecordLoopImpl(SequentialRecordSource supplier)
{
this.source = supplier;
recordEvent = new RecordEvent(this);
loopEvent = new LoopEvent(this);
}
// instance member function (alphabetic)
public void addRecordListener(RecordListener listener)
throws TooManyListenersException
{
// Test whether this object already has a listener
if (NULL_LISTENER != this.listener) {
throw new TooManyListenersException("An RecordListener is" +
" already registers with this SequentialRecordLoop");
}
// If no listener is specified set listener to be NULL_LISTENER
if (null == listener) {
listener = NULL_LISTENER;
// Otherwise set listener to that specified
} else {
this.listener = listener;
}
// Flag listener to be configured
newListener = true;
}
public void addRecordLoopListener(RecordLoopListener listener)
{
// If loopListener should not be added then don't
if ((null == listener) || (loopListeners.contains(listener))) {
return;
}
// Add loopListener to list
loopListeners.add(listener);
}
/**
* This method signals the listener that new loop is about to begin.
*
* @param target the number of records that should have been supplied.
* @throws IllegalStateException if this is called when this object has no
* SequentialRecordSource.
* @see #setRecordSource
*/
void beginLoop(long target)
throws IllegalStateException
{
// Throw exception if not source is specified
if (null == source) {
throw new IllegalStateException("No SequentialRecordSource is set");
}
// Set up intial variable for this loop
this.target = target;
countableSupplied = 0;
sourceExhausted = false;
noRecord = false;
stoppedDueToInterrupt = false;
// Tell the loopListeners that a loop is about to begin.
final int finished = loopListeners.size();
for (int loopListener = 0;
finished != loopListener;
loopListener++) {
((RecordLoopListener)
loopListeners.get(loopListener)).loopBeginning(loopEvent);
}
// Tranistion this objects listener into its configured state.
if (newListener) {
listener.configure(new ConfigurationEvent(this,
configuration));
newConfiguration = false;
newListener = false;
} else {
if (newConfiguration) {
listener.reconfigure(new ConfigurationEvent(this,
configuration));
newConfiguration = false;
} else {
listener.resume(recordEvent);
}
}
// Tell the loopListeners none of the loop has yet been done.
fireProgress(0.0);
}
public void dispose()
{
removeRecordListener(listener);
}
public void doNotCount(Object record)
{
// If the specified record in not the current record do nothing
if (currentRecord != record) {
return;
}
countableRecord = false;
}
/**
* This method signals the listener that the current loop has ended.
*
* @return the number of countable records that have been supplied.
* @throws LoopInterruptedException if the loop is interrupted.
* @throws LoopSourceExhaustedException if number is non-negative and the
* source runs out of records.
* @throws NoLoopRecordException if the record to supply could not be
* found.
*/
long endLoop()
throws LoopException
{
// Tranistion this objects listener into its suspended state.
listener.suspend(recordEvent);
// Tell the looplisteners the final progress through the loop
fireProgress(getProgress());
// Tell the loopListeners that a loop has ended
final int finished = loopListeners.size();
for (int loopListener = 0;
finished != loopListener;
loopListener++) {
((RecordLoopListener)
loopListeners.get(loopListener)).loopEnded(loopEvent);
}
// Throw exception if an interrupt existed before the loop completed
if (stoppedDueToInterrupt || isInterruptRequested()) {
setInterruptRequested(false);
throw new LoopInterruptedException(
getClass().getName() +
" was interrupted after " +
countableSupplied +
" records supplied.",
supplied,
countableSupplied);
}
// Throw exception if the end of the source was detected and a limited
// number of records was requested
if (sourceExhausted && (!(0 > target))) {
throw new LoopSourceExhaustedException(
"Source was exhausted after " +
countableSupplied +
" records supplied.",
supplied,
countableSupplied);
}
// Throw exception if no record could be read by the source
if (noRecord) {
throw new NoLoopRecordException(
"Filed to find a reocrd after " +
countableSupplied +
" records supplied.",
supplied,
countableSupplied);
}
return countableSupplied;
}
/**
* Tells any loopListeners the progress of the loop.
*/
private void fireProgress(double fraction)
{
lastProgressSupplied = totalSupplied;
lastProgressTime = System.currentTimeMillis();
LoopProgressEvent progressEvent = new LoopProgressEvent(this,
fraction);
final int finished = loopListeners.size();
for (int loopListener = 0;
finished != loopListener;
loopListener++) {
((RecordLoopListener)
loopListeners.get(loopListener)).progress(progressEvent);
}
}
/**
* This attempts to get a new record object from the source. Before
* invoking the getCurrentRecord method, this routine checks to see if an
* interrupt has been requested, and if so set the internal flag to
* indicate this is the case.
*
* @return the record object supplied or null if no object is supplied.
*/
Object getNextRecord()
throws IOException
{
Object result = null;
if (isInterruptRequested()) {
stoppedDueToInterrupt = true;
} else {
source.next();
try {
result = source.getCurrentRecord();
if (null == result) {
throw new NullPointerException("'null' returned by" +
" SequentialRecordSource getCurrentRecord()" +
" method");
}
} catch (EndOfSourceException e) {
sourceExhausted = true;
result = null;
} catch (NoSuchRecordException e) {
noRecord = true;
result = null;
}
}
return result;
}
/**
* Returns the fraction of the loop which has been completed.
*
* @return the fraction of the loop which has been completed.
*/
private double getProgress()
{
// If no records should be supplied the progress in 100%
if (0 == target) {
return (double) 1.0;
}
// If limited loop requested the report progress
if (0 < target) {
return countableSupplied / ((double) target);
}
// If unlimited loop and no countable records have been supplied,
// progress is 0%
if (0 == countableSupplied) {
return 0.0;
}
// If unlimited loop try to guess progress if possible
long estimatedSize = source.getEstimatedSize();
// If no guess is possible simply report 50%
if (0 > estimatedSize) {
return 0.5;
}
double progress = supplied / ((double) estimatedSize);
if (MAX_PROGRESS < progress) {
progress = MAX_PROGRESS;
}
return progress;
}
public RecordListener getRecordListener()
{
return listener;
}
public SequentialRecordSource getRecordSource()
{
return source;
}
public long getTotalSupplied()
{
return totalSupplied;
}
public long getTotalCountableSupplied()
{
return totalCountableSupplied;
}
/**
* Returns true if there may be more records available and the number
* supplied has not reached the number requested.
*
* @return true if more records are needed to complete the loop.
*/
boolean hasMoreRecords()
{
return ((!stoppedDueToInterrupt) &&
(target != countableSupplied) &&
(!sourceExhausted));
}
public synchronized boolean isInterruptRequested()
{
return interruptRequested;
}
public long loop(final long number)
throws LoopException, IOException
{
beginLoop(number);
while (hasMoreRecords()) {
supplyRecord(getNextRecord());
}
return endLoop();
}
public void removeRecordListener(RecordListener listener)
{
// If specified listener is this object's then remove it
if (this.listener == listener) {
if (!newListener) {
listener.finish(recordEvent);
}
this.listener = NULL_LISTENER;
}
}
public void removeRecordLoopListener(RecordLoopListener listener)
{
// If loopListener is not is list then do nothing
if ((null == listener) || (!loopListeners.contains(listener))) {
return;
}
// Remove loopListener from list
loopListeners.remove(listener);
}
public void reset()
throws IOException
{
// Reset to source by reqinding it
source.rewind();
// If the listener has had a record supplied to it then reset it by
// sending a finish event.
if (!newListener) {
listener.finish(recordEvent);
newListener = true;
}
// Tell the loopListeners that a loop has been reset.
final int finished = loopListeners.size();
for (int loopListener = 0;
finished != loopListener;
loopListener++) {
((RecordLoopListener)
loopListeners.get(loopListener)).loopReset(loopEvent);
}
}
public void setConfiguration(Object configuration)
{
this.configuration = configuration;
newConfiguration = true;
}
public synchronized void setInterruptRequested(boolean interruptRequested)
{
this.interruptRequested = interruptRequested;
}
public void setProgessByRecords(long records)
{
this.recordInterval = records;
}
public void setProgessByTime(long milliseconds)
{
this.millisInterval = milliseconds;
}
public void setRecordSource(SequentialRecordSource supplier)
{
this.source = supplier;
}
/**
* Supplies the specified record object, if it is not null, to the
* listener of this object.
*
* @param record the record object to be supplied.
*/
void supplyRecord(Object record)
{
// If no record to supply, do nothing
if (null == record) {
return;
}
// Set the record to be supplied as countable
currentRecord = record;
countableRecord = true;
// Supply the record to the listener
listener.recordSupplied(new RecordSuppliedEvent(this,
record));
currentRecord = null;
totalSupplied++;
// If supplied record is countable then count it
if (countableRecord) {
totalCountableSupplied++;
supplied++;
countableSupplied++;
}
// Tell the source that the loop has finished with the record.
source.releaseRecord(record);
// If there are loopListeners see if a progress report is needed.
if (0 != loopListeners.size()) {
// Check to see if the record progress update has expired.
long recordsRemaining;
if (0 < recordInterval) {
recordsRemaining =
recordInterval + lastProgressSupplied - totalSupplied;
} else {
recordsRemaining = IGNORE_PROGRESS;
}
// Check to see if the time progress update has expired.
long millisRemaining;
if (0 < millisInterval) {
millisRemaining =
millisInterval + lastProgressTime -
System.currentTimeMillis();
} else {
millisRemaining = IGNORE_PROGRESS;
}
// If either interval has expired or no interval has be specified
// and the default interval has expired, issue progress report
if ((!(0 < recordsRemaining)) || (!(0 < millisRemaining)) ||
((!(0 < recordInterval)) && (!(0 < millisInterval)) &&
(0 > DEFAULT_MILLIS_INTERVAL -
System.currentTimeMillis()))) {
fireProgress(getProgress());
}
}
}
// static member functions (alphabetic)
// Description of this object.
// public String toString() {}
// public static void main(String args[]) {}
}