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}