001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.progress;
003
004import java.util.Arrays;
005import java.util.Iterator;
006import java.util.LinkedList;
007import java.util.Queue;
008
009/**
010 * This class contains the progress logic required to implement a {@link ProgressMonitor}.
011 */
012public abstract class AbstractProgressMonitor implements ProgressMonitor {
013
014    private static class Request {
015        private AbstractProgressMonitor originator;
016        private int childTicks;
017        private double currentValue;
018
019        private String title;
020        private String customText;
021        private String extraText;
022        private Boolean intermediate;
023
024        private boolean finishRequested;
025    }
026
027    private final CancelHandler cancelHandler;
028
029    /**
030     * Progress monitor state
031     * @since 12675 (visibility)
032     */
033    public enum State {
034        /** Initialization. Next valid states are {@link #IN_TASK} or {@link #FINISHED} */
035        INIT,
036        /** In task. Next valid states are {@link #IN_SUBTASK} or {@link #FINISHED} */
037        IN_TASK,
038        /** In subtask. Next valid states is {@link #IN_TASK} */
039        IN_SUBTASK,
040        /** Finished. Can't change state after that */
041        FINISHED
042    }
043
044    protected State state = State.INIT;
045
046    private int ticksCount;
047    private int ticks;
048    private int childTicks;
049
050    private String taskTitle;
051    private String customText;
052    private String extraText;
053    private String shownTitle;
054    private String shownCustomText;
055    private boolean intermediateTask;
056
057    private final Queue<Request> requests = new LinkedList<>();
058    private AbstractProgressMonitor currentChild;
059    private Request requestedState = new Request();
060
061    protected abstract void doBeginTask();
062
063    protected abstract void doFinishTask();
064
065    protected abstract void doSetIntermediate(boolean value);
066
067    protected abstract void doSetTitle(String title);
068
069    protected abstract void doSetCustomText(String title);
070
071    /**
072     * Create a new {@link AbstractProgressMonitor}
073     * @param cancelHandler The handler that gets notified when the process is canceled.
074     */
075    protected AbstractProgressMonitor(CancelHandler cancelHandler) {
076        this.cancelHandler = cancelHandler;
077    }
078
079    protected void checkState(State... expectedStates) {
080        for (State s:expectedStates) {
081            if (s == state)
082                return;
083        }
084        throw new ProgressException("Expected states are %s but current state is %s", Arrays.asList(expectedStates).toString(), state);
085    }
086
087    /*=======
088     * Tasks
089     =======*/
090
091    @Override
092    public void beginTask(String title) {
093        beginTask(title, DEFAULT_TICKS);
094    }
095
096    @Override
097    public synchronized void beginTask(String title, int ticks) {
098        this.taskTitle = title;
099        checkState(State.INIT);
100        state = State.IN_TASK;
101        doBeginTask();
102        setTicksCount(ticks);
103        resetState();
104    }
105
106    @Override
107    public synchronized void finishTask() {
108        if (state != State.FINISHED) {
109
110            if (state == State.IN_SUBTASK) {
111                requestedState.finishRequested = true;
112            } else {
113                checkState(State.IN_TASK);
114                state = State.FINISHED;
115                doFinishTask();
116            }
117        }
118    }
119
120    @Override
121    public synchronized void invalidate() {
122        if (state == State.INIT) {
123            state = State.FINISHED;
124            doFinishTask();
125        }
126    }
127
128    @Override
129    public synchronized void subTask(final String title) {
130        if (state == State.IN_SUBTASK) {
131            if (title != null) {
132                requestedState.title = title;
133            }
134            requestedState.intermediate = Boolean.FALSE;
135        } else {
136            checkState(State.IN_TASK);
137            if (title != null) {
138                this.taskTitle = title;
139                resetState();
140            }
141            this.intermediateTask = false;
142            doSetIntermediate(false);
143        }
144    }
145
146    @Override
147    public synchronized void indeterminateSubTask(String title) {
148        if (state == State.IN_SUBTASK) {
149            if (title != null) {
150                requestedState.title = title;
151            }
152            requestedState.intermediate = Boolean.TRUE;
153        } else {
154            checkState(State.IN_TASK);
155            if (title != null) {
156                this.taskTitle = title;
157                resetState();
158            }
159            this.intermediateTask = true;
160            doSetIntermediate(true);
161        }
162    }
163
164    @Override
165    public synchronized void setCustomText(String text) {
166        if (state == State.IN_SUBTASK) {
167            requestedState.customText = text;
168        } else {
169            this.customText = text;
170            resetState();
171        }
172    }
173
174    @Override
175    public synchronized void setExtraText(String text) {
176        if (state == State.IN_SUBTASK) {
177            requestedState.extraText = text;
178        } else {
179            this.extraText = text;
180            resetState();
181        }
182    }
183
184    /**
185     * Default implementation is empty. Override in subclasses to display the log messages.
186     */
187    @Override
188    public void appendLogMessage(String message) {
189        // do nothing
190    }
191
192    private void resetState() {
193        String newTitle;
194        if (extraText != null) {
195            newTitle = taskTitle + ' ' + extraText;
196        } else {
197            newTitle = taskTitle;
198        }
199
200        if (newTitle == null ? shownTitle != null : !newTitle.equals(shownTitle)) {
201            shownTitle = newTitle;
202            doSetTitle(shownTitle);
203        }
204
205        if (customText == null ? shownCustomText != null : !customText.equals(shownCustomText)) {
206            shownCustomText = customText;
207            doSetCustomText(shownCustomText);
208        }
209        doSetIntermediate(intermediateTask);
210    }
211
212    @Override
213    public void cancel() {
214        cancelHandler.cancel();
215    }
216
217    @Override
218    public boolean isCanceled() {
219        return cancelHandler.isCanceled();
220    }
221
222    @Override
223    public void addCancelListener(CancelListener listener) {
224        cancelHandler.addCancelListener(listener);
225    }
226
227    @Override
228    public void removeCancelListener(CancelListener listener) {
229        cancelHandler.removeCancelListener(listener);
230    }
231
232    /*=================
233     * Ticks handling
234    ==================*/
235
236    protected abstract void updateProgress(double value);
237
238    @Override
239    public synchronized void setTicks(int ticks) {
240        if (ticks >= ticksCount) {
241            ticks = ticksCount - 1;
242        }
243        this.ticks = ticks;
244        internalUpdateProgress(0);
245    }
246
247    @Override
248    public synchronized void setTicksCount(int ticks) {
249        this.ticksCount = ticks;
250        internalUpdateProgress(0);
251    }
252
253    @Override
254    public void worked(int ticks) {
255        if (ticks == ALL_TICKS) {
256            setTicks(this.ticksCount - 1);
257        } else {
258            setTicks(this.ticks + ticks);
259        }
260    }
261
262    private void internalUpdateProgress(double childProgress) {
263        if (childProgress > 1) {
264            childProgress = 1;
265        }
266        checkState(State.IN_TASK, State.IN_SUBTASK);
267        updateProgress(ticksCount == 0 ? 0 : (ticks + childProgress * childTicks) / ticksCount);
268    }
269
270    @Override
271    public synchronized int getTicks() {
272        return ticks;
273    }
274
275    @Override
276    public synchronized int getTicksCount() {
277        return ticksCount;
278    }
279
280    /*==========
281     * Subtasks
282     ==========*/
283
284    @Override
285    public synchronized ProgressMonitor createSubTaskMonitor(int ticks, boolean internal) {
286        if (ticks == ALL_TICKS) {
287            ticks = ticksCount - this.ticks;
288        }
289
290        if (state == State.IN_SUBTASK) {
291            Request request = new Request();
292            request.originator = new ChildProgress(this, cancelHandler, internal);
293            request.childTicks = ticks;
294            requests.add(request);
295            return request.originator;
296        } else {
297            checkState(State.IN_TASK);
298            state = State.IN_SUBTASK;
299            this.childTicks = ticks;
300            currentChild = new ChildProgress(this, cancelHandler, internal);
301            return currentChild;
302        }
303    }
304
305    private void applyChildRequest(Request request) {
306        if (request.customText != null) {
307            doSetCustomText(request.customText);
308        }
309
310        if (request.title != null) {
311            doSetTitle(request.title);
312        }
313
314        if (request.intermediate != null) {
315            doSetIntermediate(request.intermediate);
316        }
317
318        internalUpdateProgress(request.currentValue);
319    }
320
321    private void applyThisRequest(Request request) {
322        if (request.finishRequested) {
323            finishTask();
324        } else {
325            if (request.customText != null) {
326                this.customText = request.customText;
327            }
328
329            if (request.title != null) {
330                this.taskTitle = request.title;
331            }
332
333            if (request.intermediate != null) {
334                this.intermediateTask = request.intermediate;
335            }
336
337            if (request.extraText != null) {
338                this.extraText = request.extraText;
339            }
340
341            resetState();
342        }
343    }
344
345    protected synchronized void childFinished(AbstractProgressMonitor child) {
346        checkState(State.IN_SUBTASK);
347        if (currentChild == child) {
348            setTicks(ticks + childTicks);
349            if (requests.isEmpty()) {
350                state = State.IN_TASK;
351                applyThisRequest(requestedState);
352                requestedState = new Request();
353            } else {
354                Request newChild = requests.poll();
355                currentChild = newChild.originator;
356                childTicks = newChild.childTicks;
357                applyChildRequest(newChild);
358            }
359        } else {
360            Iterator<Request> it = requests.iterator();
361            while (it.hasNext()) {
362                if (it.next().originator == child) {
363                    it.remove();
364                    return;
365                }
366            }
367            throw new ProgressException("Subtask %s not found", child);
368        }
369    }
370
371    private Request getRequest(AbstractProgressMonitor child) {
372        for (Request request:requests) {
373            if (request.originator == child)
374                return request;
375        }
376        throw new ProgressException("Subtask %s not found", child);
377    }
378
379    protected synchronized void childSetProgress(AbstractProgressMonitor child, double value) {
380        checkState(State.IN_SUBTASK);
381        if (currentChild == child) {
382            internalUpdateProgress(value);
383        } else {
384            getRequest(child).currentValue = value;
385        }
386    }
387
388    protected synchronized void childSetTitle(AbstractProgressMonitor child, String title) {
389        checkState(State.IN_SUBTASK);
390        if (currentChild == child) {
391            doSetTitle(title);
392        } else {
393            getRequest(child).title = title;
394        }
395    }
396
397    protected synchronized void childSetCustomText(AbstractProgressMonitor child, String customText) {
398        checkState(State.IN_SUBTASK);
399        if (currentChild == child) {
400            doSetCustomText(customText);
401        } else {
402            getRequest(child).customText = customText;
403        }
404    }
405
406    protected synchronized void childSetIntermediate(AbstractProgressMonitor child, boolean value) {
407        checkState(State.IN_SUBTASK);
408        if (currentChild == child) {
409            doSetIntermediate(value);
410        } else {
411            getRequest(child).intermediate = value;
412        }
413    }
414}