Bug 457504 - Publish a job group's final status to IJobChangeListeners

Publish a job group's final status by extending the IJobChangeEvent
with a new getJobGroupResult() method, and when the last job in a
job group has executed, have the JobManager update the IJobChangeEvent
with the job group's final status.

Change-Id: I641f2670bab15d0a9af67e928e7b78a57718878f
Signed-off-by: Terry Parker <tparker@google.com>
diff --git a/bundles/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobChangeEvent.java b/bundles/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobChangeEvent.java
index 9108bfa..4b19dd0 100644
--- a/bundles/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobChangeEvent.java
+++ b/bundles/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobChangeEvent.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2003, 2012 IBM Corporation and others.
+ * Copyright (c) 2003, 2015 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -7,6 +7,7 @@
  * 
  * Contributors:
  *     IBM - Initial API and implementation
+ *     Terry Parker - Bug 457504, Publish a job group's final status to IJobChangeListeners
  *******************************************************************************/
 package org.eclipse.core.internal.jobs;
 
@@ -25,6 +26,12 @@
 	 */
 	IStatus result = null;
 	/**
+	 * The result returned by the job's job group, if this event signals
+	 * completion of the last job in a group, or <code>null</code> if not
+	 * applicable.
+	 */
+	IStatus jobGroupResult = null;
+	/**
 	 * The amount of time to wait after scheduling the job before it should be run,
 	 * or <code>-1</code> if not applicable for this type of event.
 	 */
@@ -57,4 +64,12 @@
 	public IStatus getResult() {
 		return result;
 	}
+
+	/* (non-Javadoc)
+	 * Method declared on IJobChangeEvent
+	 */
+	@Override
+	public IStatus getJobGroupResult() {
+		return jobGroupResult;
+	}
 }
diff --git a/bundles/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobManager.java b/bundles/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobManager.java
index dd1ef88..85a9722 100644
--- a/bundles/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobManager.java
+++ b/bundles/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobManager.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2003, 2014 IBM Corporation and others.
+ * Copyright (c) 2003, 2015 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -13,6 +13,7 @@
  *     Oracle Corporation - Fix for bug 316839
  *     Thirumala Reddy Mutchukota - Bug 432049, JobGroup API and implementation
  *     Jan Koehnlein - Fix for bug 60964 (454698)
+ *     Terry Parker - Bug 457504, Publish a job group's final status to IJobChangeListeners
  *******************************************************************************/
 package org.eclipse.core.internal.jobs;
 
@@ -105,6 +106,12 @@
 
 	final ImplicitJobs implicitJobs = new ImplicitJobs(this);
 
+	/**
+	 * Listeners for the job lifecycle. It is important that the
+	 * JobManager#JobGroupUpdater is the first one that is dispatched to, since
+	 * it updates the JobChangeEvent#jobGroupStatus field, which other listeners
+	 * may use.
+	 */
 	private final JobListeners jobListeners = new JobListeners();
 
 	/**
@@ -1852,9 +1859,15 @@
 						isJobGroupCompleted = true;
 					}
 				}
-				// Log the group result when the job group is completed.
-				if (isJobGroupCompleted && jobGroupResult.matches(IStatus.ERROR | IStatus.WARNING))
-					RuntimeLog.log(jobGroupResult);
+
+				// If the job group is completing, add the job group's status to the event
+				// and log errors and warnings.
+				if (isJobGroupCompleted) {
+					((JobChangeEvent) event).jobGroupResult = jobGroupResult;
+					if (jobGroupResult.matches(IStatus.ERROR | IStatus.WARNING))
+						RuntimeLog.log(jobGroupResult);
+				}
+
 				return;
 			}
 
diff --git a/bundles/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeEvent.java b/bundles/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeEvent.java
index 8fa4943..b843251 100644
--- a/bundles/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeEvent.java
+++ b/bundles/org.eclipse.core.jobs/src/org/eclipse/core/runtime/jobs/IJobChangeEvent.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2003, 2012 IBM Corporation and others.
+ * Copyright (c) 2003, 2015 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -7,6 +7,7 @@
  *
  * Contributors:
  *     IBM - Initial API and implementation
+ *     Terry Parker - Bug 457504, Publish a job group's final status to IJobChangeListeners
  *******************************************************************************/
 package org.eclipse.core.runtime.jobs;
 
@@ -44,4 +45,14 @@
 	 * @return the status for this event
 	 */
 	public IStatus getResult();
+
+	/**
+	 * The result returned by the job's job group, if this event signals
+	 * completion of the last job in a group, or <code>null</code> if not
+	 * applicable.  This value is only applicable for the <code>done</code> event.
+	 *
+	 * @return the job group status for this event, or <code>null</code>
+	 * @since 3.7
+	 */
+	public IStatus getJobGroupResult();
 }
diff --git a/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/jobs/JobGroupTest.java b/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/jobs/JobGroupTest.java
index 8198604..909ac04 100644
--- a/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/jobs/JobGroupTest.java
+++ b/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/jobs/JobGroupTest.java
@@ -1286,6 +1286,49 @@
 		waitForCompletion(jobGroup);
 	}
 
+	/**
+	 * Tests that the JobManager publishes a final job group status to IJobChangeListeners.
+	 */
+	public void testJobManagerPublishesJobGroupResults() {
+		final int NUM_GROUP_JOBS = 3;
+		final String GROUP_NAME = "TestJobGroup";
+		final JobGroup jobGroup = new JobGroup(GROUP_NAME, 1, NUM_GROUP_JOBS);
+
+		// Record job completion events for all jobs in this job group.
+		final List<IJobChangeEvent> events = new ArrayList<IJobChangeEvent>(NUM_GROUP_JOBS);
+		IJobChangeListener listener = new JobChangeAdapter() {
+			@Override
+			public void done(IJobChangeEvent event) {
+				if (event.getJob().getJobGroup() == jobGroup) {
+					events.add(event);
+				}
+			}
+		};
+		manager.addJobChangeListener(listener);
+
+		// Execute all jobs in the job group and validate that the last job includes the job group result.
+		try {
+			for (int i = 0; i < NUM_GROUP_JOBS; i++) {
+				TestJob testJob = new TestJob("GroupJob", 10, 1);
+				testJob.setJobGroup(jobGroup);
+				testJob.schedule();
+			}
+			waitForCompletion(jobGroup);
+
+			assertEquals("Should have seen as many job completion events as the count of jobs in the job group.", NUM_GROUP_JOBS, events.size());
+			for (int i = 0; i < NUM_GROUP_JOBS; i++) {
+				IJobChangeEvent event = events.get(i);
+				assertNotNull("All job completion events should have a job status.", event.getResult());
+				if (i < NUM_GROUP_JOBS - 1)
+					assertNull("Only the last job competion event shoud have a job group status.", event.getJobGroupResult());
+				else
+					assertNotNull("The last job competion event shoud have a job group status.", event.getJobGroupResult());
+			}
+		} finally {
+			manager.removeJobChangeListener(listener);
+		}
+	}
+
 	private void assertState(String msg, Job job, int expectedState) {
 		int actualState = job.getState();
 		if (actualState != expectedState)