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