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