/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.process; import java.util.Map; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.AbstractQueuedSynchronizer; import org.geotools.util.SimpleInternationalString; import org.opengis.util.InternationalString; import org.opengis.util.ProgressListener; /** * An implementation of the Progress interface. * * @author gdavis, Jody * * * * @source $URL$ */ public class ProgressTask implements Runnable, Progress { /** Synchronization control */ private final Synchronizer synchronizer; /** * Creates a ProgressTask that will execute the * given Process when run. * * @param process the process to execute * @param input the inputs to use when executing the process * @throws NullPointerException if process is null */ public ProgressTask(Process process, Map<String,Object> input) { if (process== null) { throw new NullPointerException(); } synchronizer = new Synchronizer(process, input); } public float getProgress() { return synchronizer.getProgress(); } public boolean isCancelled() { return synchronizer.innerIsCancelled(); } public boolean isDone() { return synchronizer.innerIsDone(); } public boolean cancel(boolean mayInterruptIfRunning) { return synchronizer.innerCancel(mayInterruptIfRunning); } public Map<String,Object> get() throws InterruptedException, ExecutionException { return synchronizer.innerGet(); } public Map<String,Object> get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return synchronizer.innerGet(unit.toNanos(timeout)); } /** * This protected method is invoked when this process transitions to state * isDone (whether normally or via cancellation). The * default implementation does nothing. Subclasses may override * this method to invoke completion callbacks. You can query status inside * the implementation of this method to determine whether this task * has been canceled. */ protected void done() { } /** * Sets the result of this ProgressTask to the given value unless * this ProgressTask has already been set or has been canceled. * * @param value the value to set */ protected void set(Map<String,Object> value) { synchronizer.innerSet(value); } /** * Causes this ProgressTask to report an ExecutionException * with the given throwable as its cause, unless this ProgressTask has * already been set or has been canceled. * * @param t the cause of failure. */ protected void setException(Throwable t) { synchronizer.innerSetException(t); } /** * Sets this ProgressTask to the result of the computation unless * it has been canceled. */ public void run() { synchronizer.innerRun(); } /** * Executes the process without setting its result, and then * resets this ProgressTask to its initial state, failing to do so if the * computation encounters an exception or is canceled. This is * designed for use with processes that execute more * than once. * * @return true if successfully run and reset */ protected boolean runAndReset() { return synchronizer.innerRunAndReset(); } /** * Synchronization control for ProgressTask. * * This must be a non-static inner class in order to invoke the protected * done method. For clarity, all inner class support * methods are same as outer, prefixed with "inner". * * Uses AQS synchronizer state to represent run status */ private final class Synchronizer extends AbstractQueuedSynchronizer implements ProgressListener { private static final long serialVersionUID = 6633428077533811475L; /** State for process running */ private static final int RUNNING = 1; /** State for process completed */ private static final int COMPLETED = 2; /** State for process canceled */ private static final int CANCELED = 4; /** The process */ private final Process process; /** The process input parameters */ private Map<String,Object> input; /** The result to return from get() */ private Map<String,Object> result; /** The exception to throw from get() */ private Throwable exception; /** * The thread running process. When it is nulled after set/cancel, this * indicates that the results are now accessible. This must be * volatile to ensure visibility upon completion. */ private volatile Thread runningThread; private float percentComplete; private InternationalString processName; Synchronizer(Process process, Map<String,Object> input ) { this.process = process; this.input = input; } private boolean ranOrCancelled(int state) { return (state & (COMPLETED | CANCELED)) != 0; } /** * Implements AQS base acquire to succeed if ran or canceled */ @Override protected int tryAcquireShared(int ignore) { return innerIsDone()? 1 : -1; } /** * Implements AQS base release to always signal after setting * final done status by nulling the runningThread. */ @Override protected boolean tryReleaseShared(int ignore) { runningThread = null; return true; } boolean innerIsCancelled() { return getState() == CANCELED; } boolean innerIsDone() { return ranOrCancelled(getState()) && runningThread == null; } Map<String,Object> innerGet() throws InterruptedException, ExecutionException { acquireSharedInterruptibly(0); if (getState() == CANCELED) { throw new CancellationException(); } if (exception != null) { throw new ExecutionException(exception); } return result; } Map<String,Object> innerGet(long nanosTimeout) throws InterruptedException, ExecutionException, TimeoutException { if (!tryAcquireSharedNanos(0, nanosTimeout)) { throw new TimeoutException(); } if (getState() == CANCELED) { throw new CancellationException(); } if (exception != null) { throw new ExecutionException(exception); } return result; } void innerSet(Map<String,Object> v) { for (;;) { int s = getState(); if (ranOrCancelled(s)) { return; } if (compareAndSetState(s, COMPLETED)) { break; } } result = v; releaseShared(0); done(); } void innerSetException(Throwable t) { for (;;) { int s = getState(); if (ranOrCancelled(s)) { return; } if (compareAndSetState(s, COMPLETED)) { break; } } exception = t; result = null; releaseShared(0); done(); } boolean innerCancel(boolean mayInterruptIfRunning) { for (;;) { int s = getState(); if (ranOrCancelled(s)) { return false; } if (compareAndSetState(s, CANCELED)) { break; } } if (mayInterruptIfRunning) { Thread r = runningThread; if (r != null) { r.interrupt(); } } releaseShared(0); done(); return true; } void innerRun() { if (!compareAndSetState(0, RUNNING)) { return; } try { runningThread = Thread.currentThread(); innerSet(process.execute( input, this )); } catch(Throwable ex) { innerSetException(ex); } } boolean innerRunAndReset() { if (!compareAndSetState(0, RUNNING)) { return false; } try { runningThread = Thread.currentThread(); process.execute( input, this ); // don't set the result runningThread = null; return compareAndSetState(RUNNING, 0); } catch(Throwable ex) { innerSetException(ex); return false; } } public void complete() { // ignore } public void dispose() { // ignore } public void exceptionOccurred( Throwable t ) { innerSetException( t ); } @Deprecated public String getDescription() { return getTask().toString(); } public float getProgress() { return percentComplete; } public InternationalString getTask() { return processName; } public boolean isCanceled() { return innerIsCancelled(); } public void progress( float percent ) { this.percentComplete = percent; } public void setCanceled( boolean stop ) { innerCancel( stop ); } @Deprecated public void setDescription( String description ) { processName = new SimpleInternationalString( description ); } public void setTask( InternationalString arg0 ) { this.processName = arg0; } public void started() { // ignore } public void warningOccurred( String arg0, String arg1, String arg2 ) { // ignore } } // end Synchornizer inner class }