1 /******************************************************************************* 2 * Copyright (c) 2003, 2020 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 * Brock Janiczak <brockj@tpg.com.au> - Fix for Bug 123169 [Progress] NPE from JobInfo 14 * Martin W. Kirst <martin.kirst@s1998.tu-chemnitz.de> - jUnit test for Bug 361121 [Progress] DetailedProgressViewer's comparator violates its general contract 15 *******************************************************************************/ 16 package org.eclipse.e4.ui.progress.internal; 17 18 import java.util.Queue; 19 import java.util.concurrent.ConcurrentLinkedQueue; 20 21 import org.eclipse.core.runtime.IProgressMonitor; 22 import org.eclipse.core.runtime.IStatus; 23 import org.eclipse.core.runtime.jobs.Job; 24 import org.eclipse.jface.resource.JFaceResources; 25 import org.eclipse.osgi.util.NLS; 26 import org.eclipse.swt.graphics.Image; 27 28 /** 29 * JobInfo is the class that keeps track of the tree structure for objects that 30 * display job status in a tree. 31 */ 32 public class JobInfo extends JobTreeElement { 33 private IStatus blockedStatus; 34 35 private volatile boolean canceled; 36 private final Queue<JobTreeElement> children = new ConcurrentLinkedQueue<>(); 37 38 private final Job job; 39 40 private GroupInfo parent; 41 42 private TaskInfo taskInfo; 43 44 private ProgressManager progressManager; 45 46 private FinishedJobs finishedJobs; 47 48 // Default to no progress 49 private int ticks = -1; 50 51 /** 52 * Creates a top level JobInfo. 53 * 54 * @param enclosingJob the job to represent by this info 55 */ JobInfo(Job enclosingJob, ProgressManager progressManager, FinishedJobs finishedJobs)56 protected JobInfo(Job enclosingJob, ProgressManager progressManager, 57 FinishedJobs finishedJobs) { 58 this.job = enclosingJob; 59 this.progressManager = progressManager; 60 this.finishedJobs = finishedJobs; 61 } 62 63 /** 64 * Adds the subtask to the receiver. 65 * 66 * @param subTaskName name for the sub task 67 */ addSubTask(String subTaskName)68 void addSubTask(String subTaskName) { 69 children.add(new SubTaskInfo(this, subTaskName)); 70 } 71 72 /** 73 * Adds the amount of work to the job info. 74 * 75 * @param workIncrement 76 */ addWork(double workIncrement)77 void addWork(double workIncrement) { 78 if (taskInfo == null) { 79 return; 80 } 81 if (parent == null || ticks < 1) { 82 taskInfo.addWork(workIncrement); 83 } else { 84 taskInfo.addWork(workIncrement, parent, ticks); 85 } 86 } 87 88 /** 89 * Begins the task called taskName with the supplied work. 90 * 91 * @param taskName 92 * @param work 93 */ beginTask(String taskName, int work)94 void beginTask(String taskName, int work) { 95 taskInfo = new TaskInfo(this, taskName, work); 96 } 97 98 @Override cancel()99 public void cancel() { 100 this.canceled = true; 101 this.job.cancel(); 102 // Call the refresh so that this is updated immediately 103 if (progressManager != null) { 104 progressManager.refreshJobInfo(this); 105 } 106 } 107 108 /** 109 * Clears the collection of subtasks an the task info. 110 */ clearChildren()111 void clearChildren() { 112 children.clear(); 113 } 114 clearTaskInfo()115 void clearTaskInfo() { 116 if (finishedJobs != null) { 117 finishedJobs.remove(taskInfo); 118 } 119 taskInfo = null; 120 } 121 122 /** 123 * Compares the job of the receiver to another job. 124 * 125 * @param jobInfo The info we are comparing to 126 * @return Returns a negative integer, zero, or a positive integer as this 127 * object is less than, equal to, or greater than the specified object. 128 */ compareJobs(JobInfo jobInfo)129 private int compareJobs(JobInfo jobInfo) { 130 Job job2 = jobInfo.getJob(); 131 132 // User jobs have top priority 133 if (job.isUser()) { 134 if (!job2.isUser()) { 135 return -1; 136 } 137 } else { 138 if (job2.isUser()) { 139 return 1; 140 } 141 } 142 143 // Show the blocked ones last. 144 if (isBlocked()) { 145 if (!jobInfo.isBlocked()) { 146 return 1; 147 } 148 } else { 149 if (jobInfo.isBlocked()) { 150 return -1; 151 } 152 } 153 154 int thisPriority = job.getPriority(); 155 int otherPriority = job2.getPriority(); 156 // If equal priority, order by names 157 if (thisPriority == otherPriority) { 158 return job.getName().compareTo(job2.getName()); 159 } 160 161 // order by priority (lower value is higher priority) 162 if (thisPriority < otherPriority) { 163 return -1; 164 } 165 return 1; 166 } 167 168 @Override compareTo(Object other)169 public int compareTo(Object other) { 170 if (!(other instanceof JobInfo)) { 171 return super.compareTo(other); 172 } 173 JobInfo element = (JobInfo) other; 174 175 boolean thisCanceled = isCanceled(); 176 boolean anotherCanceled = element.isCanceled(); 177 if (thisCanceled && !anotherCanceled) { 178 // If the receiver is cancelled then it is lowest priority 179 return 1; 180 } else if (!thisCanceled && anotherCanceled) { 181 return -1; 182 } 183 184 int thisState = getJob().getState(); 185 int anotherState = element.getJob().getState(); 186 187 // if equal job state, compare other job attributes 188 if (thisState == anotherState) { 189 return compareJobs(element); 190 } 191 192 // ordering by job states, Job.RUNNING should be ordered first 193 return Integer.compare(anotherState, thisState); 194 } 195 196 /** 197 * Dispose of the receiver. 198 */ dispose()199 void dispose() { 200 if (parent != null) { 201 parent.removeJobInfo(this); 202 } 203 } 204 205 /** 206 * Return the blocked status or <code>null</code> if there isn't one. 207 * 208 * @return the blockedStatus. 209 */ getBlockedStatus()210 public IStatus getBlockedStatus() { 211 return blockedStatus; 212 } 213 214 @Override getChildren()215 Object[] getChildren() { 216 return children.toArray(); 217 } 218 219 @Override getCondensedDisplayString()220 String getCondensedDisplayString() { 221 TaskInfo info = getTaskInfo(); 222 if (info != null) { 223 return info.getDisplayStringWithoutTask(true); 224 } 225 return getJob().getName(); 226 } 227 228 @Override getDisplayImage()229 public Image getDisplayImage() { 230 int done = getPercentDone(); 231 if (done > 0) { 232 return super.getDisplayImage(); 233 } 234 if (isBlocked()) { 235 return JFaceResources.getImage(ProgressManager.BLOCKED_JOB_KEY); 236 } 237 int state = getJob().getState(); 238 if (state == Job.SLEEPING) { 239 return JFaceResources.getImage(ProgressManager.SLEEPING_JOB_KEY); 240 } 241 if (state == Job.WAITING) { 242 return JFaceResources.getImage(ProgressManager.WAITING_JOB_KEY); 243 } 244 // By default return the first progress image. 245 return super.getDisplayImage(); 246 247 } 248 249 @Override getDisplayString()250 String getDisplayString() { 251 return getDisplayString(true); 252 } 253 254 @Override getDisplayString(boolean showProgress)255 String getDisplayString(boolean showProgress) { 256 String name = getDisplayStringWithStatus(showProgress); 257 if (job.isSystem()) { 258 return NLS.bind(ProgressMessages.JobInfo_System, (new Object[] { name })); 259 } 260 return name; 261 } 262 263 /** 264 * Returns the display string based on the current status and the name of the 265 * job. 266 * 267 * @param showProgress a boolean to indicate if we should show progress or not. 268 * 269 * @return String 270 */ getDisplayStringWithStatus(boolean showProgress)271 private String getDisplayStringWithStatus(boolean showProgress) { 272 if (isCanceled()) { 273 return NLS.bind(ProgressMessages.JobInfo_Cancelled, (new Object[] { getJob().getName() })); 274 } 275 IStatus blockedStatusLocal = getBlockedStatus(); 276 if (blockedStatusLocal != null) { 277 return NLS.bind(ProgressMessages.JobInfo_Blocked, 278 (new Object[] { getJob().getName(), blockedStatusLocal.getMessage() })); 279 } 280 if (getJob().getState() == Job.RUNNING) { 281 TaskInfo info = getTaskInfo(); 282 if (info == null) { 283 return getJob().getName(); 284 } 285 return info.getDisplayString(showProgress); 286 } 287 if (getJob().getState() == Job.SLEEPING) { 288 return NLS.bind(ProgressMessages.JobInfo_Sleeping, (new Object[] { getJob().getName() })); 289 } 290 291 return NLS.bind(ProgressMessages.JobInfo_Waiting, (new Object[] { getJob().getName() })); 292 } 293 294 /** 295 * Returns the GroupInfo for the receiver if it' is active. 296 * 297 * @return GroupInfo or <code>null</code>. 298 */ getGroupInfo()299 GroupInfo getGroupInfo() { 300 if (parent != null) { 301 return parent; 302 } 303 return null; 304 } 305 306 /** 307 * Returns the job that the receiver is collecting data on. 308 * 309 * @return Job 310 */ getJob()311 public Job getJob() { 312 return job; 313 } 314 315 @Override getParent()316 public GroupInfo getParent() { 317 return parent; 318 } 319 320 /** 321 * Returns the amount of progress we have had as a percentage. If there is no 322 * progress or it is indeterminate return IProgressMonitor.UNKNOWN. 323 * 324 * @return int 325 */ getPercentDone()326 int getPercentDone() { 327 TaskInfo info = getTaskInfo(); 328 if (info != null) { 329 if (info.totalWork == IProgressMonitor.UNKNOWN) { 330 return IProgressMonitor.UNKNOWN; 331 } 332 if (info.totalWork == 0) { 333 return 0; 334 } 335 return (int) info.preWork * 100 / info.totalWork; 336 } 337 return IProgressMonitor.UNKNOWN; 338 } 339 340 /** 341 * @return the taskInfo. 342 */ getTaskInfo()343 TaskInfo getTaskInfo() { 344 return taskInfo; 345 } 346 347 @Override hasChildren()348 boolean hasChildren() { 349 return !children.isEmpty(); 350 } 351 352 /** 353 * Returns whether or not there is a task. 354 * 355 * @return boolean 356 */ hasTaskInfo()357 boolean hasTaskInfo() { 358 return taskInfo != null; 359 } 360 361 @Override isActive()362 boolean isActive() { 363 return getJob().getState() != Job.NONE; 364 } 365 366 /** 367 * Returns whether or not the receiver is blocked. 368 * 369 * @return boolean <code>true</code> if this is a currently blocked job. 370 */ isBlocked()371 public boolean isBlocked() { 372 return getBlockedStatus() != null; 373 } 374 375 /** 376 * Returns whether or not the job was cancelled in the UI. 377 * 378 * @return boolean 379 */ isCanceled()380 public boolean isCanceled() { 381 return canceled; 382 } 383 384 @Override isCancellable()385 public boolean isCancellable() { 386 return super.isCancellable(); 387 } 388 389 @Override isJobInfo()390 boolean isJobInfo() { 391 return true; 392 } 393 394 /** 395 * Sets the description of the blocking status. 396 * 397 * @param blockedStatus The IStatus that describes the blockage or 398 * <code>null</code> 399 */ setBlockedStatus(IStatus blockedStatus)400 public void setBlockedStatus(IStatus blockedStatus) { 401 this.blockedStatus = blockedStatus; 402 } 403 404 /** 405 * Sets the GroupInfo to be the group. 406 * 407 * @param group 408 */ setGroupInfo(GroupInfo group)409 void setGroupInfo(GroupInfo group) { 410 parent = group; 411 } 412 413 /** 414 * Sets the name of the taskInfo. 415 * 416 * @param name 417 */ setTaskName(String name)418 void setTaskName(String name) { 419 taskInfo.setTaskName(name); 420 } 421 422 /** 423 * Sets the number of ticks this job represents. Default is indeterminate (-1). 424 * 425 * @param ticks The ticks to set. 426 */ setTicks(int ticks)427 public void setTicks(int ticks) { 428 this.ticks = ticks; 429 } 430 } 431