Revision: 219 http://skycastle.svn.sourceforge.net/skycastle/?rev=219&view=rev Author: zzorn Date: 2007-10-05 12:35:12 -0700 (Fri, 05 Oct 2007) Log Message: ----------- Added test to the jobqueue, and fixed some issues that were revealed by the tests. Modified Paths: -------------- trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/MathUtils.java 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/JobListenerAdapter.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 Added Paths: ----------- trunk/skycastle/modules/utils/src/test/java/org/skycastle/util/jobqueue/ trunk/skycastle/modules/utils/src/test/java/org/skycastle/util/jobqueue/TestJobQueue.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-10-04 23:28:15 UTC (rev 218) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/MathUtils.java 2007-10-05 19:35:12 UTC (rev 219) @@ -64,7 +64,25 @@ return value; } + /** + * @param value + * + * @return the input value, clamped to the 0..1 range. + */ + public static double clampToZeroToOne( double value ) + { + if ( value < 0 ) + { + value = 0; + } + if ( value > 1 ) + { + value = 1; + } + return value; + } + /** * Clamps the given value to the -1..1 range. */ Modified: 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 2007-10-04 23:28:15 UTC (rev 218) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/AbstractJob.java 2007-10-05 19:35:12 UTC (rev 219) @@ -19,9 +19,12 @@ //====================================================================== // Private Fields + private final Object myCancelLock = new Object(); + private JobQueue myJobQueue = null; private JobProgressListener myListener = null; private boolean myCanceled = false; + private boolean myDone = false; private long myStartTime; //====================================================================== @@ -40,10 +43,28 @@ public final void start( final JobQueue jobQueue, final JobProgressListener listener ) throws Throwable { + if ( myDone ) + { + throw new IllegalArgumentException( "The job has already been done once, " + + "it is not possible to run the same job twice." ); + } + ParameterChecker.checkNotNull( jobQueue, "jobQueue" ); myJobQueue = jobQueue; myListener = listener; + // Check if the job was canceled before it was started. + // If so, notify the job queue, and return from this method. + synchronized ( myCancelLock ) + { + if ( myCanceled ) + { + myJobQueue.cancelJob( this ); + + return; + } + } + myStartTime = System.nanoTime(); // Notify listeners about the initial estimated time, based on previous tasks of this type. @@ -68,16 +89,45 @@ // Correct our predicted time for this type of job based on how long this one took updatePrediction( getElapsedTime_s() ); + + myDone = true; + } public final void cancel() { - myCanceled = true; + // Cancel can be called both from the event handling thread, as well as from the job itself, + // so syncronize the implementation so that a job doesn't get canceled twice. + synchronized ( myCancelLock ) + { + if ( !myCanceled ) + { + myCanceled = true; - onCancel(); + if ( myJobQueue != null ) + { + myJobQueue.cancelJob( this ); + } + + onCancel(); + } + } } + //---------------------------------------------------------------------- + // Other Public Methods + + /** + * @return true if the job has completed and the result is ready, + * but the progress listeners have not necessarily yet been called. + * False if the job is not started, or is still ongoing. + */ + public final boolean isDone() + { + return myDone; + } + //====================================================================== // Protected Methods @@ -104,20 +154,6 @@ // 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. @@ -148,8 +184,8 @@ myListener.onProgress( myJobQueue, this, status, - effortDone, - estimatedProgress, + MathUtils.clampToZeroToOne( effortDone ), + MathUtils.clampToZeroToOne( estimatedProgress ), Math.max( 0, estimatedTime_s - elapsedTime_s ) ); } } @@ -158,11 +194,20 @@ /** * @return true if the job was canceled, false if not. */ - protected boolean isCanceled() + protected final boolean isCanceled() { return myCanceled; } + + /** + * @return true if the job was not canceled, false if it was canceled. + */ + protected final boolean isRunning() + { + return !myCanceled; + } + //====================================================================== // Private Methods Modified: 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 2007-10-04 23:28:15 UTC (rev 218) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/CancelReason.java 2007-10-05 19:35:12 UTC (rev 219) @@ -6,16 +6,11 @@ public enum CancelReason { /** - * Client code canceled the job + * Client code or the job itself canceled the job */ CANCEL_CALLED, /** - * Client code canceled all jobs - */ - CANCEL_ALL_CALLED, - - /** * An exception was thrown by the job. */ EXCEPTION_THROWN, Modified: 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 2007-10-04 23:28:15 UTC (rev 218) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/Job.java 2007-10-05 19:35:12 UTC (rev 219) @@ -18,12 +18,15 @@ void start( JobQueue jobQueue, JobProgressListener listener ) throws Throwable; /** - * Stops the job. Called by the JobQueue, should not be called by client code directly. + * Stops the job and removes it from the jobQueue. * <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. + * <p/> + * Implementations should make sure to call {@link JobQueue#cancelJob} to make sure the job is removed from the + * job queue also (or they can extend AbstractJob that takes care of it). */ void cancel(); Modified: 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 2007-10-04 23:28:15 UTC (rev 218) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobListenerAdapter.java 2007-10-05 19:35:12 UTC (rev 219) @@ -30,9 +30,9 @@ public void onProgress( final JobQueue<J> jobQueue, final J job, final String status, - final float effortDone, - final float estimatedProgress, - final float estimatedTimeLeft_s ) + final double effortDone, + final double estimatedProgress, + final double estimatedTimeLeft_s ) { } Modified: 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 2007-10-04 23:28:15 UTC (rev 218) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobQueue.java 2007-10-05 19:35:12 UTC (rev 219) @@ -17,27 +17,28 @@ /** * Cancels the specified {@link Job} and removes it from the job queue. * - * @param job the {@link Job} to cancel. Should not be null. + * @param jobToRemove the {@link Job} to cancel. Should not be null. + * + * @return true if the job was found and canceled, false if no such job was found. */ - void cancelJob( J jobToRemove ); + boolean 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 ); + void addJobListener( JobListener<J> addedJobListener ); /** * Removes the specified JobListener. * * @param removedJobListener should not be null. */ - void removeJobListener(JobListener<J> removedJobListener ); + void removeJobListener( JobListener<J> removedJobListener ); } Modified: 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 2007-10-04 23:28:15 UTC (rev 218) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/jobqueue/JobQueueImpl.java 2007-10-05 19:35:12 UTC (rev 219) @@ -29,17 +29,6 @@ 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. */ @@ -128,6 +117,17 @@ }; + /** + * 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; + //====================================================================== // Public Methods @@ -161,7 +161,7 @@ // Run job try { - job.start( queue, null ); + job.start( queue, myProxyListener ); } catch ( Throwable throwable ) { @@ -205,7 +205,7 @@ } - public void cancelJob( final J jobToRemove ) + public boolean cancelJob( final J jobToRemove ) { ParameterChecker.checkNotNull( jobToRemove, "jobToRemove" ); @@ -214,14 +214,22 @@ if ( myJobs.contains( jobToRemove ) ) { myJobs.remove( jobToRemove ); + cancelJobAndNotifyListner( jobToRemove ); } else if ( jobToRemove.equals( myCurrentJob ) ) { myCanceledJob = myCurrentJob; + cancelJobAndNotifyListner( myCurrentJob ); } + else + { + return false; + } jobToRemove.cancel(); } + + return true; } @@ -231,15 +239,14 @@ { for ( J job : myJobs ) { - job.cancel(); + cancelJobAndNotifyListner( job ); } myJobs.clear(); if ( myCurrentJob != null ) { myCanceledJob = myCurrentJob; - - myCurrentJob.cancel(); + cancelJobAndNotifyListner( myCurrentJob ); } } } @@ -264,6 +271,17 @@ //====================================================================== // Private Methods + private void cancelJobAndNotifyListner( final J canceledJob ) + { + canceledJob.cancel(); + myProxyListener.onCanceled( this, + canceledJob, + CancelReason.CANCEL_CALLED, + "Job canceled", + null ); + } + + /** * @return Returns the next job. Blocks until one is available. */ Added: trunk/skycastle/modules/utils/src/test/java/org/skycastle/util/jobqueue/TestJobQueue.java =================================================================== --- trunk/skycastle/modules/utils/src/test/java/org/skycastle/util/jobqueue/TestJobQueue.java (rev 0) +++ trunk/skycastle/modules/utils/src/test/java/org/skycastle/util/jobqueue/TestJobQueue.java 2007-10-05 19:35:12 UTC (rev 219) @@ -0,0 +1,324 @@ +package org.skycastle.util.jobqueue; + +import junit.framework.TestCase; + +/** + * {@inheritDoc} + */ +public class TestJobQueue + extends TestCase +{ + + //====================================================================== + // Private Fields + + private JobQueue<CalculationJob> myJobQueue; + private CalculationJob myJob; + private TestJobQueue.MyJobListener myJobListener; + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Test Methods + + public void testCancel() throws Exception + { + myJob.cancel(); + + myJobQueue.enqueueJob( myJob ); + + Thread.sleep( 500 ); + + assertTrue( "onCancel should have been called", myJobListener.isCancelCalled() ); + } + + + public void testCancelSomeJobs() throws Exception + { + final CalculationJob job1 = new CalculationJob( "job 1" ); + final CalculationJob job2 = new CalculationJob( "job 2" ); + final CalculationJob job3 = new CalculationJob( "job 3" ); + final CalculationJob job4 = new CalculationJob( "job 4" ); + + assertTrue( "Job 1 should not be done", !job1.isDone() ); + assertTrue( "Job 2 should not be done", !job2.isDone() ); + assertTrue( "Job 3 should not be done", !job3.isDone() ); + assertTrue( "Job 4 should not be done", !job4.isDone() ); + + + myJobQueue.enqueueJob( job1 ); + myJobQueue.enqueueJob( job2 ); + myJobQueue.enqueueJob( job3 ); + myJobQueue.enqueueJob( job4 ); + + final boolean foundAndCanceled = myJobQueue.cancelJob( job2 ); + assertTrue( "Job 2 should be found and canceled", foundAndCanceled ); + + job3.cancel(); + + Thread.sleep( 2000 ); + + assertTrue( "onDone should have been called", myJobListener.isDoneCalled() ); + assertTrue( "onStarting should have been called", myJobListener.isStartingCalled() ); + assertTrue( "onProgress should have been called", myJobListener.isProgressCaled() ); + assertTrue( "onCancel should have been called", myJobListener.isCancelCalled() ); + +/* Some of the jobs can get done before they are canceled - this is quite machine dependent, so commented out these tests for now. + assertTrue( "Job 1 should be done", job1.isDone() ); + assertTrue( "Job 2 should not be done", !job2.isDone() ); + assertTrue( "Job 3 should not be done", !job3.isDone() ); + assertTrue( "Job 4 should be done", job4.isDone() ); +*/ + + assertTrue( "Job 1 should not be canceled", !job1.isCanceled() ); + assertTrue( "Job 2 should be canceled", job2.isCanceled() ); + assertTrue( "Job 3 should be canceled", job3.isCanceled() ); + assertTrue( "Job 4 should not be canceled", !job4.isCanceled() ); + } + + + public void testJobQueue() throws Exception + { + myJobQueue.enqueueJob( myJob ); + + Thread.sleep( 500 ); + + assertTrue( "onDone should have been called", myJobListener.isDoneCalled() ); + assertTrue( "onStarting should have been called", myJobListener.isStartingCalled() ); + assertTrue( "onProgress should have been called", myJobListener.isProgressCaled() ); + assertFalse( "onCancel should not have been called", myJobListener.isCancelCalled() ); + assertEquals( "Result should be correct", 101.0, myJobListener.getResult(), 0.001 ); + assertEquals( "Result should be correct", 101.0, myJob.getResult(), 0.001 ); + } + + + public void testSeveralJobs() throws Exception + { + final CalculationJob job1 = new CalculationJob( "job 1" ); + final CalculationJob job2 = new CalculationJob( "job 2" ); + final CalculationJob job3 = new CalculationJob( "job 3" ); + final CalculationJob job4 = new CalculationJob( "job 4" ); + + assertTrue( "Job 1 should not be done", !job1.isDone() ); + assertTrue( "Job 2 should not be done", !job2.isDone() ); + assertTrue( "Job 3 should not be done", !job3.isDone() ); + assertTrue( "Job 4 should not be done", !job4.isDone() ); + + myJobQueue.enqueueJob( job1 ); + myJobQueue.enqueueJob( job2 ); + myJobQueue.enqueueJob( job3 ); + myJobQueue.enqueueJob( job4 ); + + Thread.sleep( 2000 ); + + assertTrue( "onDone should have been called", myJobListener.isDoneCalled() ); + assertTrue( "onStarting should have been called", myJobListener.isStartingCalled() ); + assertTrue( "onProgress should have been called", myJobListener.isProgressCaled() ); + assertFalse( "onCancel should not have been called", myJobListener.isCancelCalled() ); + assertEquals( "Result should be correct", 101.0, myJobListener.getResult(), 0.001 ); + + assertTrue( "Job 1 should be done", job1.isDone() ); + assertTrue( "Job 2 should be done", job2.isDone() ); + assertTrue( "Job 3 should be done", job3.isDone() ); + assertTrue( "Job 4 should be done", job4.isDone() ); + } + + //====================================================================== + // Protected Methods + + protected void setUp() throws Exception + { + myJobQueue = new JobQueueImpl<CalculationJob>(); + + myJobListener = new MyJobListener(); + myJobQueue.addJobListener( myJobListener ); + + myJob = new CalculationJob( "calculation job" ); + } + + //====================================================================== + // Inner Classes + + private final class CalculationJob + extends AbstractJob + { + + //====================================================================== + // Private Fields + + private final String myName; + + private double myResult; + + //====================================================================== + // Private Constants + + private static final int LOOP_COUNT = 100; + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Other Public Methods + + public double getResult() + { + return myResult; + } + + //---------------------------------------------------------------------- + // Caononical Methods + + public String toString() + { + return myName; + } + + //====================================================================== + // Protected Methods + + protected void doJob() throws Throwable + { + double omm = 1; + for ( int i = 0; i < LOOP_COUNT && isRunning(); i++ ) + { + final double oldOmm = omm; + omm += omm * omm; + omm /= oldOmm; + + if ( i % 20 == 0 ) + { + updateProgress( ( 1.0 * i ) / ( 1.0 * LOOP_COUNT ), "Calculating Omm" ); + } + + Thread.sleep( 3 ); + } + + + myResult = omm; + } + + + protected void onCancel() + { + // Nothing to do + } + + //====================================================================== + // Private Methods + + private CalculationJob( final String name ) + { + myName = name; + } + + } + + private final class MyJobListener + extends JobListenerAdapter<CalculationJob> + { + + //====================================================================== + // Private Fields + + private double myResult; + private boolean myStartingCalled; + private boolean myDoneCalled; + private boolean myProgressCaled; + private boolean myCancelCalled; + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // JobListener Implementation + + public void onStarting( final JobQueue<CalculationJob> jobQueue, final CalculationJob job ) + { + myStartingCalled = true; + } + + + public void onDone( final JobQueue<CalculationJob> jobQueue, final CalculationJob job ) + { + myDoneCalled = true; + myResult = job.getResult(); + } + + + public void onCanceled( final JobQueue<CalculationJob> jobQueue, + final CalculationJob job, + final CancelReason cancelReason, + final String explanation, + final Throwable exception ) + { + myCancelCalled = true; + } + + //---------------------------------------------------------------------- + // JobProgressListener Implementation + + public void onProgress( final JobQueue<CalculationJob> jobQueue, + final CalculationJob job, + final String status, + final double effortDone, + final double estimatedProgress, + final double estimatedTimeLeft_s ) + { + myProgressCaled = true; + } + + //---------------------------------------------------------------------- + // Other Public Methods + + public final void reset() + { + myResult = 1; + myStartingCalled = false; + myDoneCalled = false; + myProgressCaled = false; + myCancelCalled = false; + } + + + public double getResult() + { + return myResult; + } + + + public boolean isStartingCalled() + { + return myStartingCalled; + } + + + public boolean isDoneCalled() + { + return myDoneCalled; + } + + + public boolean isProgressCaled() + { + return myProgressCaled; + } + + + public boolean isCancelCalled() + { + return myCancelCalled; + } + + //====================================================================== + // Private Methods + + private MyJobListener() + { + reset(); + } + + } + +} Property changes on: trunk/skycastle/modules/utils/src/test/java/org/skycastle/util/jobqueue/TestJobQueue.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.