Bug 578713 - NullPointerException during: "Performing API Analysis"

Avoid running analysis on *same* project in parallel (may happen if
during the analysis build will be re-triggered again due resource
changes).

The termination of the one job might cause NPE's (or other issues) in
another one, because they use (shared) data from same project builder.

Change-Id: I54e6192941164ce9351a08f9b90a11ad8ecb6bb9
Reviewed-on: https://git.eclipse.org/r/c/pde/eclipse.pde.ui/+/192370
Tested-by: PDE Bot <pde-bot@eclipse.org>
Reviewed-by: Andrey Loskutov <loskutov@gmx.de>
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/ApiAnalysisBuilder.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/ApiAnalysisBuilder.java
index b0a4b23..c877f8f 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/ApiAnalysisBuilder.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/ApiAnalysisBuilder.java
@@ -567,8 +567,9 @@
 			this.fullBuild = fullBuild;
 			this.wbaseline = wbaseline;
 			this.projects = projects;
-			// Intentionally no rule set to allow run in parallel with build locking entire workspace
-			// setRule(project);
+			// Intentionally not using project as rule to allow run in parallel with build
+			// locking entire workspace
+			setRule(new ApiAnalysisJobRule(project));
 		}
 
 		@Override
@@ -581,7 +582,7 @@
 					return Status.CANCEL_STATUS;
 				} else {
 					if (status.getCode() == IResourceStatus.RESOURCE_NOT_FOUND && project.isAccessible()) {
-						waitForLockAndReschedule(monitor, e);
+						waitForBuildAndReschedule(monitor, e);
 						return Status.OK_STATUS;
 					} else {
 						return status;
@@ -592,22 +593,19 @@
 		}
 
 		/**
-		 * In case the analysis job was interrupted by the build, let wait for the build
-		 * and start analysis again
+		 * In case the analysis job was interrupted by the resource changes, let wait
+		 * for the build and start analysis again
 		 */
-		private void waitForLockAndReschedule(IProgressMonitor monitor, CoreException e) {
+		private void waitForBuildAndReschedule(IProgressMonitor monitor, CoreException e) {
 			try {
-				Job.getJobManager().beginRule(project, monitor);
-				IStatus s = new Status(IStatus.INFO, ApiAnalysisBuilder.class,
-						"Re-scheduling API analysis for " + project.getName(), e); //$NON-NLS-1$
-				ApiPlugin.log(s);
-				schedule();
-			} catch (OperationCanceledException e1) {
+				Job.getJobManager().join(ResourcesPlugin.FAMILY_AUTO_BUILD, monitor);
+			} catch (OperationCanceledException | InterruptedException e1) {
 				// nothing to do
-			} finally {
-				// release lock, we don't want to block workspace while analysis
-				Job.getJobManager().endRule(project);
 			}
+			IStatus s = new Status(IStatus.INFO, ApiAnalysisBuilder.class,
+					"Re-scheduling API analysis for " + project.getName(), e); //$NON-NLS-1$
+			ApiPlugin.log(s);
+			schedule();
 		}
 
 		@Override
@@ -626,6 +624,30 @@
 		}
 	}
 
+	private static class ApiAnalysisJobRule implements ISchedulingRule {
+
+		private final IProject project;
+
+		public ApiAnalysisJobRule(IProject project) {
+			this.project = project;
+		}
+
+		@Override
+		public boolean contains(ISchedulingRule rule) {
+			return isConflicting(rule);
+		}
+
+		@Override
+		public boolean isConflicting(ISchedulingRule rule) {
+			if (!(rule instanceof ApiAnalysisJobRule)) {
+				return false;
+			}
+			ApiAnalysisJobRule other = (ApiAnalysisJobRule) rule;
+			return project.equals(other.project);
+		}
+
+	}
+
 	/**
 	 * Returns if the backing project should be fully built, based on the delta
 	 *