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