AutoBuildDeactivator is capable of inhibiting follow up builds

Yet it does not lock the workspace in any way....

Change-Id: I2c88a8273faec937baae9e8df9dfc4de7fe03535
Signed-off-by: Markus Duft <markus.duft@ssi-schaefer.com>
diff --git a/org.eclipse.tea.core.ui/META-INF/MANIFEST.MF b/org.eclipse.tea.core.ui/META-INF/MANIFEST.MF
index bace8db..f8b06f1 100644
--- a/org.eclipse.tea.core.ui/META-INF/MANIFEST.MF
+++ b/org.eclipse.tea.core.ui/META-INF/MANIFEST.MF
@@ -9,7 +9,8 @@
 Export-Package: org.eclipse.tea.core.ui,
  org.eclipse.tea.core.ui.annotations,
  org.eclipse.tea.core.ui.config,
- org.eclipse.tea.core.ui.internal.menu
+ org.eclipse.tea.core.ui.internal.listeners;x-internal:=true,
+ org.eclipse.tea.core.ui.internal.menu;x-internal:=true
 Require-Bundle: org.eclipse.core.runtime,
  org.eclipse.tea.core,
  org.eclipse.osgi.services,
diff --git a/org.eclipse.tea.core.ui/src/org/eclipse/tea/core/ui/internal/listeners/AutoBuildDeactivator.java b/org.eclipse.tea.core.ui/src/org/eclipse/tea/core/ui/internal/listeners/AutoBuildDeactivator.java
index baf6369..f57e251 100644
--- a/org.eclipse.tea.core.ui/src/org/eclipse/tea/core/ui/internal/listeners/AutoBuildDeactivator.java
+++ b/org.eclipse.tea.core.ui/src/org/eclipse/tea/core/ui/internal/listeners/AutoBuildDeactivator.java
@@ -10,13 +10,30 @@
  *******************************************************************************/
 package org.eclipse.tea.core.ui.internal.listeners;
 
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import org.eclipse.core.internal.events.BuildCommand;
+import org.eclipse.core.internal.events.BuildManager;
+import org.eclipse.core.internal.events.InternalBuilder;
+import org.eclipse.core.internal.resources.Project;
+import org.eclipse.core.internal.resources.Workspace;
+import org.eclipse.core.internal.watson.ElementTree;
+import org.eclipse.core.resources.IBuildConfiguration;
+import org.eclipse.core.resources.ICommand;
+import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IWorkspace;
 import org.eclipse.core.resources.IWorkspaceDescription;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
 import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.MultiStatus;
+import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.tea.core.TaskExecutionContext;
@@ -33,21 +50,33 @@
  * task chains are running.
  */
 @Component
+@SuppressWarnings("restriction")
 public class AutoBuildDeactivator implements TaskingLifeCycleListener {
 
 	private boolean autoBuildOriginalState = false;
+	private int unprotectedDepth;
 	private static AtomicInteger nestCount = new AtomicInteger(0);
+	private static final Map<IProject, ElementTree> suppressedProjects = new HashMap<>();
+
+	public Workspace getWorkspace() {
+		return ((Workspace) ResourcesPlugin.getWorkspace());
+	}
 
 	@BeginTaskChain
-	public synchronized void begin(TaskingLog log) {
+	public synchronized void begin(TaskingLog log) throws CoreException {
 		if (nestCount.getAndIncrement() == 0) {
 			log.debug("Disabling automatic build...");
 			autoBuildOriginalState = setAutoBuild(log, false);
+
+			getWorkspace().prepareOperation(null, null);
+			getWorkspace().beginOperation(true);
+			unprotectedDepth = getWorkspace().getWorkManager().beginUnprotected();
 		}
 	}
 
 	@FinishTaskChain
-	public synchronized void finish(TaskExecutionContext context, TaskingLog log, MultiStatus status) {
+	public synchronized void finish(TaskExecutionContext context, TaskingLog log, MultiStatus status)
+			throws CoreException {
 		if (nestCount.decrementAndGet() != 0) {
 			return;
 		}
@@ -68,20 +97,141 @@
 		} else {
 			setAutoBuild(log, autoBuildOriginalState);
 		}
+
+		getWorkspace().getWorkManager().endUnprotected(unprotectedDepth);
+		getWorkspace().endOperation(null, false, null);
+	}
+
+	/**
+	 * Allows to suppress a build of the specified projects. This has two
+	 * effects. After finishing the currently running task chain, auto build
+	 * will be enabled and forced by Eclipse - at this point, auto build is
+	 * immediately cancelled and the force flag is reset. After that, the "last
+	 * built" state of each of the given projects is updated with the current
+	 * state.
+	 *
+	 * @param project
+	 *            the project to assume to be cleanly built.
+	 */
+	public static void avoidBuild(IProject project) {
+		synchronized (suppressedProjects) {
+			ElementTree currentTree = null;
+			try {
+				IBuildConfiguration bc = project.getActiveBuildConfig();
+				int highestStamp = 0;
+				for (ICommand c : ((Project) project).internalGetDescription().getBuildSpec(false)) {
+					IncrementalProjectBuilder builder = ((BuildCommand) c).getBuilder(bc);
+
+					Method getTree = InternalBuilder.class.getDeclaredMethod("getLastBuiltTree");
+					getTree.setAccessible(true);
+					ElementTree t = (ElementTree) getTree.invoke(builder);
+					getTree.setAccessible(false);
+
+					Field stampField = ElementTree.class.getDeclaredField("treeStamp");
+					stampField.setAccessible(true);
+					int stamp = (int) stampField.get(t);
+					stampField.setAccessible(false);
+
+					if (stamp > highestStamp) {
+						highestStamp = stamp;
+						currentTree = t;
+					}
+				}
+			} catch (Exception e) {
+				e.printStackTrace();
+				// oups;
+			}
+
+			if (currentTree != null) {
+				suppressedProjects.put(project, currentTree);
+			} else {
+				System.err.println("no tree for " + project);
+			}
+		}
 	}
 
 	private boolean setAutoBuild(TaskingLog log, boolean autoBuild) {
 		boolean originalState = false;
-		try {
-			final IWorkspace workspace = ResourcesPlugin.getWorkspace();
-			final IWorkspaceDescription workspaceDesc = workspace.getDescription();
-			originalState = workspaceDesc.isAutoBuilding();
-			workspaceDesc.setAutoBuilding(autoBuild);
-			workspace.setDescription(workspaceDesc);
-		} catch (Exception e) {
-			log.error("Failed to restore AutoBuild, target=" + autoBuild, e);
+		synchronized (suppressedProjects) {
+			try {
+				final IWorkspace workspace = ResourcesPlugin.getWorkspace();
+				final IWorkspaceDescription workspaceDesc = workspace.getDescription();
+				originalState = workspaceDesc.isAutoBuilding();
+				workspaceDesc.setAutoBuilding(autoBuild);
+				workspace.setDescription(workspaceDesc);
+
+				// we have 100ms worst case to react (scheduling delay of auto
+				// build).
+				if (!suppressedProjects.isEmpty()) {
+					suppressBuild(suppressedProjects);
+				}
+			} catch (Exception e) {
+				log.error("Failed to restore AutoBuild, target=" + autoBuild, e);
+			} finally {
+				suppressedProjects.clear();
+			}
 		}
 		return originalState;
 	}
 
+	/**
+	 * Immediately cancels auto build, and suppresses any forced build and all
+	 * deltas for the given projects.
+	 *
+	 * @param suppressedProjects
+	 */
+	private static void suppressBuild(Map<IProject, ElementTree> suppressedProjects) {
+		// This is a little hacky but avoids that the autobuild will immediately
+		// re-build what we built just now.
+		try {
+			BuildManager mgr = ((Workspace) ResourcesPlugin.getWorkspace()).getBuildManager();
+
+			// get the job
+			Field jobField = mgr.getClass().getDeclaredField("autoBuildJob");
+			jobField.setAccessible(true);
+			Object o = jobField.get(mgr);
+			jobField.setAccessible(false);
+
+			// cancel the job
+			((Job) o).cancel();
+
+			// reset force flag
+			Field force = o.getClass().getDeclaredField("forceBuild");
+			force.setAccessible(true);
+			force.set(o, false);
+			force.setAccessible(false);
+
+			// now we need re-set the current delta trees on all projects
+			if (!suppressedProjects.isEmpty()) {
+				for (Entry<IProject, ElementTree> entry : suppressedProjects.entrySet()) {
+					IProject project = entry.getKey();
+					IBuildConfiguration bc = project.getActiveBuildConfig();
+					ElementTree elementTree = suppressedProjects.get(project);
+					if (elementTree != null) {
+						ICommand[] commands;
+						// yay - now make sure the tree of all builders is the
+						// current tree of the project
+						if (project.isAccessible()) {
+							commands = ((Project) project).internalGetDescription().getBuildSpec(false);
+						} else {
+							continue;
+						}
+
+						for (ICommand c : commands) {
+							IncrementalProjectBuilder b = ((BuildCommand) c).getBuilder(bc);
+							Method setTree = InternalBuilder.class.getDeclaredMethod("setLastBuiltTree",
+									ElementTree.class);
+							setTree.setAccessible(true);
+							setTree.invoke(b, elementTree);
+							setTree.setAccessible(false);
+						}
+					}
+				}
+			}
+		} catch (Exception e) {
+			System.err.println("cannot avoid autobuild");
+			e.printStackTrace();
+		}
+	}
+
 }