Bug 573595 - AutoBuildJob following a search and replace might run as
system job

Don't schedule "real" autobuild job if autobuild is off. Instead,
schedule a "dummy" system job that only sends PRE_BUILD/POST_BUILD
events to follow the contract defined by IResourceChangeEvent.

With that, there is no need to set the "system" state for the real
autobuild job to hide it.

This prevents all kinds of build job state inconsistencies shown in the
Progress view - if the autobuild is disabled, "real" autobuild job is
simply not running (only system job that sends events), if autobuild is
enabled, "real" autobuild job is always shown in the Progress view.

Change-Id: If0a37d9d1a0fd7fe0ff54a416e087edb9755d13c
Reviewed-on: https://git.eclipse.org/r/c/platform/eclipse.platform.resources/+/181719
Tested-by: Platform Bot <platform-bot@eclipse.org>
Reviewed-by: Simeon Andreev <simeon.danailov.andreev@gmail.com>
Reviewed-by: Andrey Loskutov <loskutov@gmx.de>
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/AutoBuildJob.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/AutoBuildJob.java
index 04177f2..55452e4 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/AutoBuildJob.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/events/AutoBuildJob.java
@@ -44,6 +44,7 @@
 	private Preferences preferences = ResourcesPlugin.getPlugin().getPluginPreferences();
 	private final Bundle systemBundle = Platform.getBundle("org.eclipse.osgi"); //$NON-NLS-1$
 	private Workspace workspace;
+	private final Job noBuildJob;
 
 	AutoBuildJob(Workspace workspace) {
 		super(Messages.events_building_0);
@@ -52,6 +53,7 @@
 		isAutoBuilding = workspace.isAutoBuilding();
 		this.workspace = workspace;
 		this.preferences.addPropertyChangeListener(this);
+		noBuildJob = new AutoBuildOffJob();
 	}
 
 	/**
@@ -98,12 +100,16 @@
 				wakeUp(delay);
 				break;
 			case NONE :
-				try {
-					setSystem(!isAutoBuilding);
-				} catch (IllegalStateException e) {
-					//ignore - the job has been scheduled since we last checked its state
+				if (isAutoBuilding) {
+					schedule(delay);
+				} else {
+					// The code below is required to maintain the ancient contract
+					// in IResourceChangeEvent, stating that even if autobuild is
+					// switched off, we still send PRE_BUILD/POST_BUILD events
+					if (noBuildJob.getState() != Job.RUNNING) {
+						noBuildJob.schedule(delay);
+					}
 				}
-				schedule(delay);
 				break;
 		}
 	}
@@ -223,7 +229,8 @@
 		boolean wasAutoBuilding = isAutoBuilding;
 		isAutoBuilding = preferences.getBoolean(ResourcesPlugin.PREF_AUTO_BUILDING);
 		//force a build if autobuild has been turned on
-		if (!forceBuild && !wasAutoBuilding && isAutoBuilding) {
+		if (!wasAutoBuilding && isAutoBuilding) {
+			noBuildJob.cancel();
 			forceBuild = true;
 			build(false);
 		}
@@ -282,4 +289,45 @@
 			forceBuild = avoidBuild = buildNeeded = false;
 		}
 	}
+
+	/**
+	 * The class is required to maintain the ancient contract in
+	 * IResourceChangeEvent, stating that even if autobuild is switched off, we
+	 * still should send PRE_BUILD/POST_BUILD events. This job only send events, and
+	 * never triggers a build.
+	 */
+	private final class AutoBuildOffJob extends Job {
+
+		private AutoBuildOffJob() {
+			super("Sending build events with disabled autobuild"); //$NON-NLS-1$
+			setRule(workspace.getRoot());
+			setSystem(true);
+		}
+
+		@Override
+		public boolean belongsTo(Object family) {
+			return family == ResourcesPlugin.FAMILY_AUTO_BUILD;
+		}
+
+		@Override
+		protected IStatus run(IProgressMonitor monitor) {
+			final ISchedulingRule rule = workspace.getRuleFactory().buildRule();
+			try {
+				workspace.prepareOperation(rule, monitor);
+				workspace.beginOperation(true);
+				final int trigger = IncrementalProjectBuilder.AUTO_BUILD;
+				workspace.broadcastBuildEvent(workspace, IResourceChangeEvent.PRE_BUILD, trigger);
+				workspace.broadcastBuildEvent(workspace, IResourceChangeEvent.POST_BUILD, trigger);
+			} catch (CoreException e) {
+				return e.getStatus();
+			} finally {
+				try {
+					workspace.endOperation(rule, false);
+				} catch (CoreException e) {
+					return e.getStatus();
+				}
+			}
+			return Status.OK_STATUS;
+		}
+	}
 }
diff --git a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/IResourceChangeListenerTest.java b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/IResourceChangeListenerTest.java
index 0f406fb..a1e576c 100644
--- a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/IResourceChangeListenerTest.java
+++ b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/IResourceChangeListenerTest.java
@@ -514,6 +514,37 @@
 		}
 	}
 
+	/**
+	 * Checks that even with autobuild disabled,
+	 * {@code IResourceChangeEvent.PRE_BUILD} and
+	 * {@code IResourceChangeEvent.POST_BUILD} are fired.
+	 */
+	public void testTouchFileWithAutobuildOff() throws Exception {
+		SimpleListener preBuild = new SimpleListener();
+		SimpleListener postBuild = new SimpleListener();
+		final IWorkspace workspace = getWorkspace();
+		try {
+			setAutoBuilding(false);
+			workspace.addResourceChangeListener(preBuild, IResourceChangeEvent.PRE_BUILD);
+			workspace.addResourceChangeListener(postBuild, IResourceChangeEvent.POST_BUILD);
+
+			file1.touch(getMonitor());
+
+			// wait for autobuild so POST_BUILD will fire
+			waitForBuild();
+
+			int trigger = IncrementalProjectBuilder.AUTO_BUILD;
+			assertEquals("Should see PRE_BUILD event", trigger, preBuild.trigger);
+			assertEquals("Should see POST_BUILD event", trigger, postBuild.trigger);
+			assertEquals("Should see workspace root on PRE_BUILD event", workspace, preBuild.source);
+			assertEquals("Should see workspace root on POST_BUILD event", workspace, postBuild.source);
+		} finally {
+			setAutoBuilding(true);
+			workspace.removeResourceChangeListener(preBuild);
+			workspace.removeResourceChangeListener(postBuild);
+		}
+	}
+
 	public void testChangeFileToFolder() {
 		try {
 			/* change file1 into a folder */