Revision: 216 http://skycastle.svn.sourceforge.net/skycastle/?rev=216&view=rev Author: zzorn Date: 2007-10-04 16:26:45 -0700 (Thu, 04 Oct 2007) Log Message: ----------- An initial JobQueue implementation. It keeps a worker thread, and allows enqueying jobs into it. Calculated progress for jobs based on previous execution times of that type of job, as well as by the current progress and duration. Also did some updates in the MathUtilities and ParameterChecker to support the code. Modified Paths: -------------- trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/MathUtils.java trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/ParameterChecker.java Added Paths: ----------- trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/AbstractJob.java trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/CancelReason.java trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/Job.java trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobListener.java trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobListenerAdapter.java trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobProgressListener.java trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobQueue.java trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobQueueImpl.java Modified: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/MathUtils.java =================================================================== --- trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/MathUtils.java 2007-09-30 18:25:33 UTC (rev 215) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/MathUtils.java 2007-10-04 23:26:45 UTC (rev 216) @@ -139,6 +139,30 @@ /** + * Calculates a linearily interpolated value, given a start value and position, an end value and position, + * and the position to get the value at. + * <p/> + * First calculates the relative position, then does a normal linear interpolation between the start and end value, + * using the relative position as the interpolation factor. + * + * @param position + * @param startPosition + * @param endPosition + * @param startValue + * @param endValue + */ + public static double interpolateDouble( final double position, + final double startPosition, + final double endPosition, + final double startValue, + final double endValue ) + { + final double relativePosition = ( position - startPosition ) / ( endPosition - startPosition ); + return startValue + relativePosition * ( endValue - startValue ); + } + + + /** * Calculates a linearily interpolated value, given a start position and value, an end position and value, * and the position to get the value at. * <p/> @@ -175,6 +199,42 @@ /** + * Calculates a linearily interpolated value, given a start position and value, an end position and value, + * and the position to get the value at. + * <p/> + * If the position is outside start or end position, it is treated as if it was at the start or end value respectively. + * <p/> + * First calculates the relative position, then does a normal linear interpolation between the start and end value, + * using the relative position as the interpolation factor. + * + * @param position + * @param startPosition + * @param endPosition + * @param startValue + * @param endValue + */ + public static double interpolateClampDouble( final double position, + final double startPosition, + final double endPosition, + final double startValue, + final double endValue ) + { + // Clamp + double p = position; + if ( p < startPosition ) + { + p = startPosition; + } + else if ( p > endPosition ) + { + p = endPosition; + } + + return interpolateDouble( p, startPosition, endPosition, startValue, endValue ); + } + + + /** * Calculates a smoothly (cosine) interpolated value, given a start value, an end value, * and the position to get the value at. * @@ -506,6 +566,22 @@ return rolledValue; } + /** + * @return True if (x,y) is inside the axis aligned rectangle defined by the points (x1,y1) and (x2,y2), false otherwise. + */ + public static boolean isInsideRectangle( final double x, + final double y, + final double x1, + final double y1, + final double x2, + final double y2 ) + { + return x >= x1 && + x < x2 && + y >= y1 && + y < y2; + } + //====================================================================== // Protected Methods Modified: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/ParameterChecker.java =================================================================== --- trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/ParameterChecker.java 2007-09-30 18:25:33 UTC (rev 215) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/ParameterChecker.java 2007-10-04 23:26:45 UTC (rev 216) @@ -179,6 +179,22 @@ } + public static void checkNormalNumberInRangeInclusive( final double parameter, + String parameterName, + double minimumValueInclusize, + double maximumValueInclusive ) + { + checkNormalNumber( parameter, parameterName ); + + if ( parameter < minimumValueInclusize || parameter > maximumValueInclusive ) + { + throwIllegalArgumentException( parameterName, + parameter, + "be in the range " + minimumValueInclusize + " (inclusive) to " + maximumValueInclusive + " (inclusive)" ); + } + } + + public static void checkIntegerInRange( final int parameter, String parameterName, int minimumValueInclusize, @@ -308,6 +324,20 @@ } } + public static void checkLargerThan( final double parameter, + String parameterName, + double minimumValueExclusive, + String thresholdName ) + { + if ( parameter <= minimumValueExclusive ) + { + throwIllegalArgumentException( parameterName, + parameter, + "be larger than " + thresholdName + " (" + minimumValueExclusive + ")" ); + } + } + + //====================================================================== // Private Methods Added: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/AbstractJob.java =================================================================== --- trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/AbstractJob.java (rev 0) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/AbstractJob.java 2007-10-04 23:26:45 UTC (rev 216) @@ -0,0 +1,209 @@ +package org.skycastle.util.jobqueue; + +import org.skycastle.util.MathUtils; +import org.skycastle.util.ParameterChecker; + +import java.util.HashMap; +import java.util.Map; + +/** + * {@inheritDoc} + * <p/> + * This abstract class provides common functionality for Job:s, including prediction of the progress of the job based + * on earlier jobs of the same type, and the current duration and effort done by the job. + */ +public abstract class AbstractJob + implements Job +{ + + //====================================================================== + // Private Fields + + private JobQueue myJobQueue = null; + private JobProgressListener myListener = null; + private boolean myCanceled = false; + private long myStartTime; + + //====================================================================== + // Private Constants + + private static final Map<Class<? extends Job>, Double> thePredictedTimes_s = new HashMap<Class<? extends Job>, Double>(); + private static final double PREDICTED_TIME_STABILITY_FACTOR = 0.8; + private static final double NANOSECONDS_TO_SECONDS = 0.000000001; + private static final double EFFORT_DONE_ESTIMATION_WEIGHT = 0.5; + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Job Implementation + + public final void start( final JobQueue jobQueue, final JobProgressListener listener ) throws Throwable + { + ParameterChecker.checkNotNull( jobQueue, "jobQueue" ); + myJobQueue = jobQueue; + myListener = listener; + + myStartTime = System.nanoTime(); + + // Notify listeners about the initial estimated time, based on previous tasks of this type. + updateProgress( 0, "Starting" ); + + // Run the job + try + { + doJob(); + } + catch ( Throwable throwable ) + { + // Reset parameters to avoid them leaking or such. + myJobQueue = null; + myListener = null; + + throw throwable; + } + + // Set the progress to 100%, so that any statusbar graphics and similar can be updated correctly + updateProgress( 1, "Done" ); + + // Correct our predicted time for this type of job based on how long this one took + updatePrediction( getElapsedTime_s() ); + } + + + public final void cancel() + { + myCanceled = true; + + onCancel(); + } + + //====================================================================== + // Protected Methods + + //---------------------------------------------------------------------- + // Abstract Protected Methods + + /** + * Called when this job is canceled. The implementation should clean up any reserved resources. + * <p/> + * Alternatively the job implementation can ignore this method and poll {@link #isCanceled} instead. + */ + protected abstract void onCancel(); + + /** + * Called by the {@link AbstractJob#start} method. + * Should run the job, and return when it is ready. + * + * @throws Throwable thrown if there was some exception raised during the calculation. + * In such case, the job is canceled. + */ + protected abstract void doJob() throws Throwable; + + //---------------------------------------------------------------------- + // Other Protected Methods + + /** + * Cancels this job from the job queue it belongs to. + */ + protected final void abortJob() + { + if ( myJobQueue == null ) + { + throw new IllegalStateException( "abortJob called before AbstractJob.start has been called." ); + } + + myJobQueue.cancelJob( this ); + } + + + /** + * Can be used to update the progress of the job. + * + * @param effortDone how ready the job is, the range is 0 = all work left, 0.5 = half work done, to 1 = all work done. + * @param status a user readable description of the current status of the job. + */ + protected final void updateProgress( final double effortDone, final String status ) + { + ParameterChecker.checkNormalNumberInRangeInclusive( effortDone, "effortDone", 0.0, 1.0 ); + + if ( myJobQueue == null ) + { + throw new IllegalStateException( "onProgress called before AbstractJob.start has been called." ); + } + + if ( myListener != null ) + { + final double elapsedTime_s = getElapsedTime_s(); + final double estimatedTimeByPreviousTasks_s = getEstimatedTime_s( 1 ); + final double estimatedTimeByEffortDone_s = effortDone == 0 ? estimatedTimeByPreviousTasks_s : elapsedTime_s / effortDone; + + // Average the estimation for time left that we get based on how long previous tasks have taken, + // and the estimate we get based on how long this task has taken and how complete it is. + final double estimatedTime_s = MathUtils.interpolate( EFFORT_DONE_ESTIMATION_WEIGHT, + estimatedTimeByPreviousTasks_s, + estimatedTimeByEffortDone_s ); + final double estimatedProgress = MathUtils.interpolateClampDouble( elapsedTime_s, 0, estimatedTime_s, 0, 1 ); + + myListener.onProgress( myJobQueue, + this, + status, + effortDone, + estimatedProgress, + Math.max( 0, estimatedTime_s - elapsedTime_s ) ); + } + } + + + /** + * @return true if the job was canceled, false if not. + */ + protected boolean isCanceled() + { + return myCanceled; + } + + //====================================================================== + // Private Methods + + private double getElapsedTime_s() + { + return NANOSECONDS_TO_SECONDS * ( System.nanoTime() - myStartTime ); + } + + + private void updatePrediction( final double elapsedTime_s ) + { + // Get weighted average of previous estimated duration for this type of task, + // with the observed duration of this task type + final double previousEstimatedTime_s = getEstimatedTime_s( elapsedTime_s ); + final double estimatedTime_s = MathUtils.interpolate( PREDICTED_TIME_STABILITY_FACTOR, + elapsedTime_s, + previousEstimatedTime_s ); + + // Store the new estimated duration + thePredictedTimes_s.put( getClass(), estimatedTime_s ); + } + + + private double getEstimatedTime_s( final double defaultValue ) + { + return getValue( thePredictedTimes_s, + getClass(), + defaultValue ); + } + + + private static <K, V> V getValue( final Map<K, V> map, final K key, final V defaultValue ) + { + V value = defaultValue; + + if ( map.containsKey( key ) ) + { + value = map.get( key ); + } + + return value; + } + +} Property changes on: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/AbstractJob.java ___________________________________________________________________ Name: svn:mime-type + text/plain Name: svn:keywords + Id Name: svn:eol-style + native Added: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/CancelReason.java =================================================================== --- trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/CancelReason.java (rev 0) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/CancelReason.java 2007-10-04 23:26:45 UTC (rev 216) @@ -0,0 +1,22 @@ +package org.skycastle.util.jobqueue; + +/** + * Describes the reason why a job was canceled. + */ +public enum CancelReason +{ + /** + * Client code canceled the job + */ + CANCEL_CALLED, + + /** + * Client code canceled all jobs + */ + CANCEL_ALL_CALLED, + + /** + * An exception was thrown by the job. + */ + EXCEPTION_THROWN, +} Property changes on: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/CancelReason.java ___________________________________________________________________ Name: svn:mime-type + text/plain Name: svn:keywords + Id Name: svn:eol-style + native Added: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/Job.java =================================================================== --- trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/Job.java (rev 0) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/Job.java 2007-10-04 23:26:45 UTC (rev 216) @@ -0,0 +1,30 @@ +package org.skycastle.util.jobqueue; + +/** + * Something that can be executed in a separate worker thread, and that may be canceled durin execution. + * Also allows listening to the job progress. + */ +// IDEA: Might also support splitting the job into several sub-jobs that can be executed in parallel on different cores. +public interface Job +{ + /** + * Starts a job, and returns when it is completed. + * + * @param jobQueue the jobQueue that this job was started in. + * @param listener a listener that can be notified about the progress of the job. + * + * @throws Throwable any exception thrown by the job should be caught, and the job canceled. + */ + void start( JobQueue jobQueue, JobProgressListener listener ) throws Throwable; + + /** + * Stops the job. Called by the JobQueue, should not be called by client code directly. + * <p/> + * The job can stop at its earliest convenient time. + * For example, if it has a processing loop, the loop could check a cancel flag now and again. + * <p/> + * The job can also be canceled before it has even started. + */ + void cancel(); + +} Property changes on: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/Job.java ___________________________________________________________________ Name: svn:mime-type + text/plain Name: svn:keywords + Id Name: svn:eol-style + native Added: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobListener.java =================================================================== --- trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobListener.java (rev 0) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobListener.java 2007-10-04 23:26:45 UTC (rev 216) @@ -0,0 +1,42 @@ +package org.skycastle.util.jobqueue; + +/** + * A listener that is notified about the progress, starting, and stopping of a job. + */ +public interface JobListener<J extends Job> + extends JobProgressListener<J> +{ + /** + * Called when the job was started by the {@link JobQueue}. + * + * @param jobQueue the jobQueue that is handling the job. + * @param job the job that was started. + */ + void onStarting( JobQueue<J> jobQueue, J job ); + + /** + * Called when the job was successfully finished. + * The listener can use this callback to extract result data from the job object. + * + * @param jobQueue the {@link JobQueue} the job was in. + * @param job the job that finished. + */ + void onDone( JobQueue<J> jobQueue, J job ); + + /** + * Called when a job was canceled, either by a call to cancel, or by the job itself, + * or because an exception was thrown by the start method of the job. + * + * @param jobQueue the {@link JobQueue} the job was in. + * @param job the job that was canceled. + * @param cancelReason the reason the job was canceled. + * @param explanation a user readable reason for why the job was canceled. + * @param exception an exception that caused the job to get canceled, or null if none. + */ + void onCanceled( JobQueue<J> jobQueue, + J job, + CancelReason cancelReason, + String explanation, + Throwable exception ); + +} Property changes on: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobListener.java ___________________________________________________________________ Name: svn:mime-type + text/plain Name: svn:keywords + Id Name: svn:eol-style + native Added: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobListenerAdapter.java =================================================================== --- trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobListenerAdapter.java (rev 0) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobListenerAdapter.java 2007-10-04 23:26:45 UTC (rev 216) @@ -0,0 +1,40 @@ +package org.skycastle.util.jobqueue; + +/** + * {@inheritDoc} + * <p/> + * An abstract adapter class that provides empty implementations for all methods. + */ +public abstract class JobListenerAdapter<J extends Job> + implements JobListener<J> +{ + public void onStarting( final JobQueue<J> jobQueue, final J job ) + { + + } + + public void onDone( final JobQueue<J> jobQueue, final J job ) + { + + } + + public void onCanceled( final JobQueue<J> jobQueue, + final J job, + final CancelReason cancelReason, + final String explanation, + final Throwable exception ) + { + + } + + public void onProgress( final JobQueue<J> jobQueue, + final J job, + final String status, + final float effortDone, + final float estimatedProgress, + final float estimatedTimeLeft_s ) + { + + } + +} Property changes on: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobListenerAdapter.java ___________________________________________________________________ Name: svn:mime-type + text/plain Name: svn:keywords + Id Name: svn:eol-style + native Added: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobProgressListener.java =================================================================== --- trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobProgressListener.java (rev 0) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobProgressListener.java 2007-10-04 23:26:45 UTC (rev 216) @@ -0,0 +1,28 @@ +package org.skycastle.util.jobqueue; + +/** + * A listener that is notified about the progress of a job. + */ +public interface JobProgressListener<J extends Job> +{ + /** + * Called at regular intervalls while the job is being executed, to communoicate the status and progress of the job. + * May not be called at all, depending on the job implementation. + * + * @param jobQueue the queue the job is being executed in. + * @param job the job that is being executed + * @param status a textual status describing what is currently being done for the job. + * @param effortDone how ready the job is, the range is 0 = all work left, 0.5 = half work done, to 1 = all work done. + * @param estimatedProgress how far the job is, in terms of time. 0= just started, 0.5 = half the time left, 1 = no time left. + * May be based on timings of earlier runs of the same type of job. + * @param estimatedTimeLeft_s estimated number of seconds left before this job is finished. + * May be based on timings of earlier runs of the same type of job. + */ + void onProgress( JobQueue<J> jobQueue, + J job, + String status, + double effortDone, + double estimatedProgress, + double estimatedTimeLeft_s ); + +} Property changes on: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobProgressListener.java ___________________________________________________________________ Name: svn:mime-type + text/plain Name: svn:keywords + Id Name: svn:eol-style + native Added: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobQueue.java =================================================================== --- trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobQueue.java (rev 0) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobQueue.java 2007-10-04 23:26:45 UTC (rev 216) @@ -0,0 +1,43 @@ +package org.skycastle.util.jobqueue; + +/** + * Keeps track of a sequnce of jobs, and executes them in worker threads. + * <p/> + * Notifies callers when jobs are done. Jobs can be canceled by calling the cancel method on a job, + * or all remaining jobs can be canceled at once with {@link #cancelAllJobs}. + */ +public interface JobQueue<J extends Job> +{ + /** + * @param job a {@link Job} to be executed by this job queue whenever it has time. + * Should not be null, or already enqueued. + */ + void enqueueJob( J job ); + + /** + * Cancels the specified {@link Job} and removes it from the job queue. + * + * @param job the {@link Job} to cancel. Should not be null. + */ + void cancelJob( J jobToRemove ); + + /** + * Cancels all jobs currently in this {@link JobQueue}. + */ + void cancelAllJobs(); + + + /** + * Adds the specified JobListener. + * + * @param addedJobListener should not be null or already added. + */ + void addJobListener(JobListener<J> addedJobListener ); + + /** + * Removes the specified JobListener. + * + * @param removedJobListener should not be null. + */ + void removeJobListener(JobListener<J> removedJobListener ); +} Property changes on: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobQueue.java ___________________________________________________________________ Name: svn:mime-type + text/plain Name: svn:keywords + Id Name: svn:eol-style + native Added: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobQueueImpl.java =================================================================== --- trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobQueueImpl.java (rev 0) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobQueueImpl.java 2007-10-04 23:26:45 UTC (rev 216) @@ -0,0 +1,305 @@ +package org.skycastle.util.jobqueue; + +import org.skycastle.util.ParameterChecker; + +import javax.swing.*; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * {@inheritDoc} + */ +public final class JobQueueImpl<J extends Job> + implements JobQueue<J> +{ + + //====================================================================== + // Private Fields + + /** + * The job queue. Does not contain the currently executing job. + * Should always be syncronized using itself as a lock when accessed. + */ + private final List<J> myJobs = new LinkedList<J>(); + + /** + * Listeners that are notified of job status changes and progress. Should only be called in the swing/AWT event handling thread. + */ + private final List<JobListener<J>> myListeners = new ArrayList<JobListener<J>>( 5 ); + + /** + * The currently executing job. Should be syncronized using myJobs as a lock when accessed. + */ + private J myCurrentJob = null; + + /** + * Set to the currently job that was canceled. Used to stop the currently executing job without stopping any + * future jobs if the currently executing job was already stopped. + */ + private J myCanceledJob = null; + + /** + * A job listener instance that forwards listner progress calls to the AWT event dispatching thread, + * and notifies each listener from it. + */ + private final JobListener<J> myProxyListener = new JobListener<J>() + { + + public void onStarting( final JobQueue<J> jobQueue, final J job ) + { + // Notify listeners from the event dispatching thread + SwingUtilities.invokeLater( new Runnable() + { + + public void run() + { + for ( JobListener<J> listener : myListeners ) + { + listener.onStarting( JobQueueImpl.this, job ); + } + } + + } ); + } + + + public void onDone( final JobQueue<J> jobQueue, final J job ) + { + // Notify listeners from the event dispatching thread + SwingUtilities.invokeLater( new Runnable() + { + + public void run() + { + for ( JobListener<J> listener : myListeners ) + { + listener.onDone( JobQueueImpl.this, job ); + } + } + + } ); + } + + + public void onCanceled( final JobQueue<J> jobQueue, + final J job, + final CancelReason cancelReason, + final String explanation, + final Throwable exception ) + { + // Notify listeners from the event dispatching thread + SwingUtilities.invokeLater( new Runnable() + { + + public void run() + { + for ( JobListener<J> listener : myListeners ) + { + listener.onCanceled( JobQueueImpl.this, job, cancelReason, explanation, exception ); + } + } + + } ); + } + + + public void onProgress( final JobQueue<J> jobQueue, + final J job, + final String status, + final double effortDone, + final double estimatedProgress, + final double estimatedTimeLeft_s ) + { + // Notify listeners from the event dispatching thread + SwingUtilities.invokeLater( new Runnable() + { + + public void run() + { + for ( JobListener<J> listener : myListeners ) + { + listener.onProgress( JobQueueImpl.this, job, status, effortDone, estimatedProgress, estimatedTimeLeft_s ); + } + } + + } ); + } + + }; + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Constructors + + /** + * Creates a new JobQueueImpl. Starts a worker thread that will wait for enqueued {@link Job}s. + * The worker thread is automatically terminated when the application terminates. + */ + public JobQueueImpl() + { + final JobQueue<J> queue = JobQueueImpl.this; + + // Start a thread that handles texture painting jobs + final Thread workerThread = new Thread( new Runnable() + { + + public void run() + { + while ( true ) + { + final J job = getNextJob(); + + synchronized ( myJobs ) + { + if ( job != myCanceledJob ) + { + myProxyListener.onStarting( JobQueueImpl.this, job ); + + // Run job + try + { + job.start( queue, null ); + } + catch ( Throwable throwable ) + { + myProxyListener.onCanceled( JobQueueImpl.this, + job, + CancelReason.EXCEPTION_THROWN, + throwable.getMessage(), + throwable ); + } + + myProxyListener.onDone( JobQueueImpl.this, job ); + } + + myCanceledJob = null; + } + } + } + + } ); + + // Terminate the worker thread when the application terminates. + workerThread.setDaemon( true ); + + workerThread.start(); + } + + //---------------------------------------------------------------------- + // JobQueue Implementation + + public void enqueueJob( final J job ) + { + ParameterChecker.checkNotNull( job, "job" ); + + synchronized ( myJobs ) + { + ParameterChecker.checkNotAlreadyContained( job, myJobs, "myJobs" ); + + myJobs.add( job ); + myJobs.notifyAll(); + } + } + + + public void cancelJob( final J jobToRemove ) + { + ParameterChecker.checkNotNull( jobToRemove, "jobToRemove" ); + + synchronized ( myJobs ) + { + if ( myJobs.contains( jobToRemove ) ) + { + myJobs.remove( jobToRemove ); + } + else if ( jobToRemove.equals( myCurrentJob ) ) + { + myCanceledJob = myCurrentJob; + } + + jobToRemove.cancel(); + } + } + + + public void cancelAllJobs() + { + synchronized ( myJobs ) + { + for ( J job : myJobs ) + { + job.cancel(); + } + myJobs.clear(); + + if ( myCurrentJob != null ) + { + myCanceledJob = myCurrentJob; + + myCurrentJob.cancel(); + } + } + } + + + public void addJobListener( JobListener<J> addedJobListener ) + { + ParameterChecker.checkNotNull( addedJobListener, "addedJobListener" ); + ParameterChecker.checkNotAlreadyContained( addedJobListener, myListeners, "myListeners" ); + + myListeners.add( addedJobListener ); + } + + + public void removeJobListener( JobListener<J> removedJobListener ) + { + ParameterChecker.checkNotNull( removedJobListener, "removedJobListener" ); + + myListeners.remove( removedJobListener ); + } + + //====================================================================== + // Private Methods + + /** + * @return Returns the next job. Blocks until one is available. + */ + private J getNextJob() + { + J job = null; + + while ( job == null ) + { + synchronized ( myJobs ) + { + myCurrentJob = null; + + while ( myJobs.isEmpty() ) + { + try + { + myJobs.wait(); + } + catch ( InterruptedException e ) + { + // Ignore + } + } + + if ( !myJobs.isEmpty() ) + { + job = myJobs.get( 0 ); + myJobs.remove( job ); + } + + myCurrentJob = job; + } + } + + return job; + } + +} Property changes on: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobQueueImpl.java ___________________________________________________________________ Name: svn:mime-type + text/plain Name: svn:keywords + Id Name: svn:eol-style + native This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.