// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.progress; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; import java.util.Queue; public abstract class AbstractProgressMonitor implements ProgressMonitor { private static class Request { AbstractProgressMonitor originator; int childTicks; double currentValue; String title; String customText; String extraText; Boolean intermediate; boolean finishRequested; } private final CancelHandler cancelHandler; protected enum State {INIT, IN_TASK, IN_SUBTASK, FINISHED} protected State state = State.INIT; int ticksCount; int ticks; private int childTicks; private String taskTitle; private String customText; private String extraText; private String shownTitle; private String shownCustomText; private boolean intermediateTask; private Queue requests = new LinkedList(); private AbstractProgressMonitor currentChild; private Request requestedState = new Request(); protected abstract void doBeginTask(); protected abstract void doFinishTask(); protected abstract void doSetIntermediate(boolean value); protected abstract void doSetTitle(String title); protected abstract void doSetCustomText(String title); protected AbstractProgressMonitor(CancelHandler cancelHandler) { this.cancelHandler = cancelHandler; } protected void checkState(State... expectedStates) { for (State s:expectedStates) { if (s == state) return; } throw new ProgressException("Expected states are %s but current state is %s", Arrays.asList(expectedStates).toString(), state); } /*======= * Tasks =======*/ public void beginTask(String title) { beginTask(title, DEFAULT_TICKS); } public synchronized void beginTask(final String title, int ticks) { this.taskTitle = title; checkState(State.INIT); state = State.IN_TASK; doBeginTask(); setTicksCount(ticks); resetState(); } public synchronized void finishTask() { if (state != State.FINISHED) { if (state == State.IN_SUBTASK) { requestedState.finishRequested = true; } else { checkState(State.IN_TASK); state = State.FINISHED; doFinishTask(); } } } public synchronized void invalidate() { if (state == State.INIT) { state = State.FINISHED; doFinishTask(); } } public synchronized void subTask(final String title) { if (state == State.IN_SUBTASK) { if (title != null) { requestedState.title = title; } requestedState.intermediate = false; } else { checkState(State.IN_TASK); if (title != null) { this.taskTitle = title; resetState(); } this.intermediateTask = false; doSetIntermediate(false); } } public synchronized void indeterminateSubTask(String title) { if (state == State.IN_SUBTASK) { if (title != null) { requestedState.title = title; } requestedState.intermediate = true; } else { checkState(State.IN_TASK); if (title != null) { this.taskTitle = title; resetState(); } this.intermediateTask = true; doSetIntermediate(true); } } public synchronized void setCustomText(String text) { if (state == State.IN_SUBTASK) { requestedState.customText = text; } else { this.customText = text; resetState(); } } public synchronized void setExtraText(String text) { if (state == State.IN_SUBTASK) { requestedState.extraText = text; } else { this.extraText = text; resetState(); } } /** * Default implementation is empty. Override in subclasses to display the log messages. */ public void appendLogMessage(String message) { // do nothing } private void resetState() { String newTitle; if (extraText != null) { newTitle = taskTitle + " " + extraText; } else { newTitle = taskTitle; } if (newTitle == null?shownTitle != null:!newTitle.equals(shownTitle)) { shownTitle = newTitle; doSetTitle(shownTitle); } if (customText == null?shownCustomText != null:!customText.equals(shownCustomText)) { shownCustomText = customText; doSetCustomText(shownCustomText); } doSetIntermediate(intermediateTask); } public void cancel() { cancelHandler.cancel(); } public boolean isCancelled() { return cancelHandler.isCanceled(); } public void addCancelListener(CancelListener listener) { cancelHandler.addCancelListener(listener); } public void removeCancelListener(CancelListener listener) { cancelHandler.removeCancelListener(listener); } /*================= * Ticks handling ==================*/ abstract void updateProgress(double value); public synchronized void setTicks(int ticks) { if (ticks >= ticksCount) { ticks = ticksCount - 1; } this.ticks = ticks; internalUpdateProgress(0); } public synchronized void setTicksCount(int ticks) { this.ticksCount = ticks; internalUpdateProgress(0); } public void worked(int ticks) { if (ticks == ALL_TICKS) { setTicks(this.ticksCount - 1); } else { setTicks(this.ticks + ticks); } } private void internalUpdateProgress(double childProgress) { if (childProgress > 1) { childProgress = 1; } checkState(State.IN_TASK, State.IN_SUBTASK); updateProgress(ticksCount == 0?0:(ticks + childProgress * childTicks) / ticksCount); } public synchronized int getTicks() { return ticks; } public synchronized int getTicksCount() { return ticksCount; } /*========== * Subtasks ==========*/ public synchronized ProgressMonitor createSubTaskMonitor(int ticks, boolean internal) { if (ticks == ALL_TICKS) { ticks = ticksCount - this.ticks; } if (state == State.IN_SUBTASK) { Request request = new Request(); request.originator = new ChildProgress(this, cancelHandler, internal); request.childTicks = ticks; requests.add(request); return request.originator; } else { checkState(State.IN_TASK); state = State.IN_SUBTASK; this.childTicks = ticks; currentChild = new ChildProgress(this, cancelHandler, internal); return currentChild; } } private void applyChildRequest(Request request) { if (request.customText != null) { doSetCustomText(request.customText); } if (request.title != null) { doSetTitle(request.title); } if (request.intermediate != null) { doSetIntermediate(request.intermediate); } internalUpdateProgress(request.currentValue); } private void applyThisRequest(Request request) { if (request.finishRequested) { finishTask(); } else { if (request.customText != null) { this.customText = request.customText; } if (request.title != null) { this.taskTitle = request.title; } if (request.intermediate != null) { this.intermediateTask = request.intermediate; } if (request.extraText != null) { this.extraText = request.extraText; } resetState(); } } protected synchronized void childFinished(AbstractProgressMonitor child) { checkState(State.IN_SUBTASK); if (currentChild == child) { setTicks(ticks + childTicks); if (requests.isEmpty()) { state = State.IN_TASK; applyThisRequest(requestedState); requestedState = new Request(); } else { Request newChild = requests.poll(); currentChild = newChild.originator; childTicks = newChild.childTicks; applyChildRequest(newChild); } } else { Iterator it = requests.iterator(); while (it.hasNext()) { if (it.next().originator == child) { it.remove(); return; } } throw new ProgressException("Subtask %s not found", child); } } private Request getRequest(AbstractProgressMonitor child) { for (Request request:requests) { if (request.originator == child) return request; } throw new ProgressException("Subtask %s not found", child); } protected synchronized void childSetProgress(AbstractProgressMonitor child, double value) { checkState(State.IN_SUBTASK); if (currentChild == child) { internalUpdateProgress(value); } else { getRequest(child).currentValue = value; } } protected synchronized void childSetTitle(AbstractProgressMonitor child, String title) { checkState(State.IN_SUBTASK); if (currentChild == child) { doSetTitle(title); } else { getRequest(child).title = title; } } protected synchronized void childSetCustomText(AbstractProgressMonitor child, String customText) { checkState(State.IN_SUBTASK); if (currentChild == child) { doSetCustomText(customText); } else { getRequest(child).customText = customText; } } protected synchronized void childSetIntermediate(AbstractProgressMonitor child, boolean value) { checkState(State.IN_SUBTASK); if (currentChild == child) { doSetIntermediate(value); } else { getRequest(child).intermediate = value; } } }