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