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