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}