/******************************************************************************* * Breakout Cave Survey Visualizer * * Copyright (C) 2014 James Edwards * * jedwards8 at fastmail dot fm * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program 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 General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *******************************************************************************/ package org.andork.swing.async; import java.util.concurrent.ExecutionException; import javax.swing.SwingUtilities; import org.andork.event.BasicPropertyChangeSupport; import org.andork.event.HierarchicalBasicPropertyChangeSupport; import org.andork.model.HasChangeSupport; import org.andork.util.ArrayUtils; import org.andork.util.Java7; public abstract class Task implements HasChangeSupport { public static enum Property { STATE, SERVICE, STATUS, INDETERMINATE, COMPLETED, TOTAL; } public static enum State { NOT_SUBMITTED, WAITING, RUNNING, CANCELING, CANCELED, FINISHED, FAILED; public boolean hasEnded() { return this == CANCELED || this == FINISHED || this == FAILED; } } private final long creationTimestamp; private final Object lock = new Object(); private State state = State.NOT_SUBMITTED; private TaskService service; private Throwable throwable; private String status; private boolean indeterminate = true; private int completed; private int total = 1000; private final BasicPropertyChangeSupport propertyChangeSupport = new BasicPropertyChangeSupport(); public Task() { creationTimestamp = System.nanoTime(); } public Task(String status) { this(); setStatus(status); } protected void afterReset() { } public final void cancel() { if (!isCancelable()) { throw new UnsupportedOperationException("task is not cancelable"); } State oldState; State newState; TaskService service; synchronized (lock) { if (state == State.CANCELED || state == State.CANCELING || state == State.FINISHED || state == State.FAILED) { return; } oldState = state; newState = state = state == State.RUNNING ? State.CANCELING : State.CANCELED; service = this.service; lock.notifyAll(); } service.cancel(this); firePropertyChange(Property.STATE, oldState, newState); } public boolean canRunInParallelWith(Task other) { return false; } @Override public HierarchicalBasicPropertyChangeSupport.External changeSupport() { return propertyChangeSupport.external(); } private void checkState(State required) { if (state != required) { throw new IllegalStateException("Operation not allowed unless state is " + required + "; state is currently " + state); } } private void checkState(State... required) { if (ArrayUtils.indexOf(required, state) < 0) { throw new IllegalStateException("Operation not allowed unless state is " + ArrayUtils.cat(required, " or ") + "; state is currently " + state); } } @Override public final boolean equals(Object o) { return super.equals(o); } protected abstract void execute() throws Exception; private final void firePropertyChange(final Property property, final Object oldValue, final Object newValue) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { propertyChangeSupport.firePropertyChange(Task.this, property, oldValue, newValue); } }); } public int getCompleted() { return completed; } public long getCreationTimestamp() { return creationTimestamp; } public final State getState() { synchronized (lock) { return state; } } public String getStatus() { return status; } public Throwable getThrowable() { synchronized (lock) { return throwable; } } public int getTotal() { return total; } public final boolean hasEnded() { return getState().hasEnded(); } @Override public final int hashCode() { return super.hashCode(); } public boolean isCancelable() { return false; } public final boolean isCanceled() { return getState() == State.CANCELED; } public final boolean isCanceling() { return getState() == State.CANCELING; } public boolean isIndeterminate() { return indeterminate; } public final void reset() { State oldState; synchronized (lock) { checkState(State.CANCELED, State.FINISHED, State.FAILED); oldState = state; state = State.NOT_SUBMITTED; service = null; lock.notifyAll(); } firePropertyChange(Property.STATE, oldState, State.NOT_SUBMITTED); afterReset(); } public final void run() { try { setRunning(); execute(); setCanceledOrFinished(); } catch (Throwable e) { synchronized (lock) { if (state == State.CANCELING) { setCanceledOrFinished(); } else { setFailed(e); } } } } private void setCanceledOrFinished() { State oldState; State newState; synchronized (lock) { oldState = state; switch (state) { case CANCELING: state = State.CANCELED; break; case RUNNING: state = State.FINISHED; break; default: throw new IllegalStateException("Operation not allowed unless state == CANCELED or FINISHED"); } newState = state; lock.notifyAll(); } firePropertyChange(Property.STATE, oldState, newState); } public void setCompleted(int completed) { if (completed < 0) { throw new IllegalArgumentException("completed must be >= 0"); } Integer oldValue = null; synchronized (lock) { if (this.completed != completed) { oldValue = this.completed; this.completed = completed; } } if (oldValue != null) { firePropertyChange(Property.COMPLETED, oldValue, completed); } } private void setFailed(Throwable t) { State oldState; synchronized (lock) { oldState = state; state = State.FAILED; throwable = t; lock.notifyAll(); } t.printStackTrace(); firePropertyChange(Property.STATE, oldState, State.FAILED); } public void setIndeterminate(boolean indefinite) { Boolean oldValue = null; synchronized (lock) { if (indeterminate != indefinite) { oldValue = indeterminate; indeterminate = indefinite; } } if (oldValue != null) { firePropertyChange(Property.INDETERMINATE, oldValue, indefinite); } } private void setRunning() { synchronized (lock) { checkState(State.WAITING); state = State.RUNNING; lock.notifyAll(); } firePropertyChange(Property.STATE, State.WAITING, State.RUNNING); } public final void setService(TaskService service) { if (service == null) { throw new IllegalArgumentException("service must be non-null"); } synchronized (lock) { if (this.service != null) { throw new IllegalStateException("Task is still registered with a service"); } checkState(State.NOT_SUBMITTED); state = State.WAITING; this.service = service; lock.notifyAll(); } firePropertyChange(Property.STATE, State.NOT_SUBMITTED, State.WAITING); } public void setStatus(String status) { String oldValue = null; synchronized (lock) { if (!Java7.Objects.equals(this.status, status)) { oldValue = this.status; this.status = status; } } if (oldValue != null) { firePropertyChange(Property.STATUS, oldValue, status); } } public void setTotal(int total) { if (total < 0) { throw new IllegalArgumentException("total must be >= 0"); } Integer oldValue = null; synchronized (lock) { if (this.total != total) { oldValue = this.total; this.total = total; } } if (oldValue != null) { firePropertyChange(Property.TOTAL, oldValue, total); } } public final void waitUntilHasEnded() throws InterruptedException { synchronized (lock) { while (!hasEnded()) { lock.wait(); } } } public final void waitUntilHasFinished() throws InterruptedException, ExecutionException { waitUntilHasEnded(); if (throwable != null) { throw new ExecutionException(throwable); } } }