Bug 472066 - Fix deadlocks in AnnotationProcessorLoaderFactory.

- Synchronize on a private final variable rather than 'this' to make
  it impossible for external objects to deadlock this object by
  preventing access to internals.

- Remove all synchronized instance methods and replace them with
  the smallest-possible synchronized blocks that protect the integrity
  of the cache.

Change-Id: I8862a113de5580b5c86f16917eb67dbbd2905e4d
Signed-off-by: Stefan Xenos <sxenos@gmail.com>
diff --git a/org.eclipse.jdt.apt.core/src/org/eclipse/jdt/apt/core/internal/AnnotationProcessorFactoryLoader.java b/org.eclipse.jdt.apt.core/src/org/eclipse/jdt/apt/core/internal/AnnotationProcessorFactoryLoader.java
index 010fdd2..4e9d75a 100644
--- a/org.eclipse.jdt.apt.core/src/org/eclipse/jdt/apt/core/internal/AnnotationProcessorFactoryLoader.java
+++ b/org.eclipse.jdt.apt.core/src/org/eclipse/jdt/apt/core/internal/AnnotationProcessorFactoryLoader.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2007 BEA Systems, Inc.
+ * Copyright (c) 2005, 2015 BEA Systems, Inc.
  * 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:
  *    wharley@bea.com - initial API and implementation
+ *    sxenos@gmail.com - Bug 472066 - Deadlock in AnnotationProcessorFactoryLoader
  *******************************************************************************/
 
 package org.eclipse.jdt.apt.core.internal;
@@ -26,8 +27,8 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.Map.Entry;
+import java.util.Set;
 
 import org.eclipse.core.resources.IMarker;
 import org.eclipse.core.resources.IProject;
@@ -42,10 +43,10 @@
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.jdt.apt.core.internal.util.FactoryContainer;
-import org.eclipse.jdt.apt.core.internal.util.FactoryPath;
-import org.eclipse.jdt.apt.core.internal.util.FactoryPathUtil;
 import org.eclipse.jdt.apt.core.internal.util.FactoryContainer.FactoryType;
+import org.eclipse.jdt.apt.core.internal.util.FactoryPath;
 import org.eclipse.jdt.apt.core.internal.util.FactoryPath.Attributes;
+import org.eclipse.jdt.apt.core.internal.util.FactoryPathUtil;
 import org.eclipse.jdt.core.IJavaProject;
 import org.eclipse.jdt.core.JavaCore;
 
@@ -123,11 +124,15 @@
 	
 	private static final String JAR_EXTENSION = "jar"; //$NON-NLS-1$
 	
+	private static final Object cacheMutex = new Object();
+	
 	// Caches the factory classes associated with each project.
 	// See class comments for lifecycle of items in this cache.
+	// Guarded by cacheMutex
 	private final Map<IJavaProject, Map<AnnotationProcessorFactory, FactoryPath.Attributes>> _project2Java5Factories = 
 		new HashMap<IJavaProject, Map<AnnotationProcessorFactory, FactoryPath.Attributes>>();
 	
+	// Guarded by cacheMutex
 	private final Map<IJavaProject, Map<IServiceFactory, FactoryPath.Attributes>> _project2Java6Factories =
 		new HashMap<IJavaProject, Map<IServiceFactory, FactoryPath.Attributes>>();
     
@@ -135,15 +140,18 @@
 	// are not reloaded on every batch build, unlike batch processors 
 	// which are.
 	// See class comments for lifecycle of items in this cache.
+	// Guarded by cacheMutex
 	private final Map<IJavaProject, ClassLoader> _iterativeLoaders = 
 		new HashMap<IJavaProject, ClassLoader>();
 	
+	// Guarded by cacheMutex
 	private final Map<IJavaProject,ClassLoader> _batchLoaders = 
 		new HashMap<IJavaProject,ClassLoader>();
 	
 	// Caches information about which resources affect which projects'
 	// factory paths.
 	// See class comments for lifecycle of items in this cache.
+	// Guarded by cacheMutex	
 	private final Map<String, Set<IJavaProject>> _container2Project =
 		new HashMap<String, Set<IJavaProject>>();
 	
@@ -154,44 +162,42 @@
 	 */
 	private class ResourceListener implements IResourceChangeListener {
 
+		@Override
 		public void resourceChanged(IResourceChangeEvent event) {
 			Map<IJavaProject, LoadFailureHandler> failureHandlers = new HashMap<IJavaProject, LoadFailureHandler>();
-			synchronized (AnnotationProcessorFactoryLoader.this) {
-				switch (event.getType()) {
-				
-				// Project deletion
-				case (IResourceChangeEvent.PRE_DELETE) :
-					IResource project = event.getResource();
-					if (project != null && project instanceof IProject) {
-						IJavaProject jproj = JavaCore.create((IProject)project);
-						if (jproj != null) {
-							uncacheProject(jproj);
-						}
+			switch (event.getType()) {
+			
+			// Project deletion
+			case (IResourceChangeEvent.PRE_DELETE) :
+				IResource project = event.getResource();
+				if (project != null && project instanceof IProject) {
+					IJavaProject jproj = JavaCore.create((IProject)project);
+					if (jproj != null) {
+						uncacheProject(jproj);
 					}
-					break;
-					
-				// Changes to jar files or .factorypath files
-				case (IResourceChangeEvent.PRE_BUILD) :
-					IResourceDelta rootDelta = event.getDelta();
-					FactoryPathDeltaVisitor visitor = new FactoryPathDeltaVisitor();
-					try {
-						rootDelta.accept(visitor);
-					} catch (CoreException e) {
-						AptPlugin.log(e, "Unable to determine whether resource change affects annotation processor factory path"); //$NON-NLS-1$
-					}
-					Set<IJavaProject> affected = visitor.getAffectedProjects();
-					if (affected != null) {
-						processChanges(affected, failureHandlers);
-					}
-					break;
-	
 				}
+				break;
+				
+			// Changes to jar files or .factorypath files
+			case (IResourceChangeEvent.PRE_BUILD) :
+				IResourceDelta rootDelta = event.getDelta();
+				FactoryPathDeltaVisitor visitor = new FactoryPathDeltaVisitor();
+				try {
+					rootDelta.accept(visitor);
+				} catch (CoreException e) {
+					AptPlugin.log(e, "Unable to determine whether resource change affects annotation processor factory path"); //$NON-NLS-1$
+				}
+				Set<IJavaProject> affected = visitor.getAffectedProjects();
+				if (affected != null) {
+					processChanges(affected, failureHandlers);
+				}
+				break;
+
 			}
 			for (LoadFailureHandler handler : failureHandlers.values()) {
 				handler.reportFailureMarkers();
 			}
 		}
-		
 	}
 	
 	/**
@@ -225,6 +231,7 @@
 		/**
 		 * @return true to visit children
 		 */
+		@Override
 		public boolean visit(IResourceDelta delta) {
 			switch (delta.getKind()) {
 			default:
@@ -256,13 +263,15 @@
 				if (JAR_EXTENSION.equals(ext)) {
 					IPath absolutePath = res.getLocation();
 					if (absolutePath == null) {
-						// Jar file within a deleted project.  In this case getLocation() 
-						// returns null, so we can't get a canonical path.  Bounce every
-						// factory path that contains anything resembling this jar.
-						for (Entry<String, Set<IJavaProject>> entry : _container2Project.entrySet()) {
-							IPath jarPath = new Path(entry.getKey());
-							if (relativePath.lastSegment().equals(jarPath.lastSegment())) {
-								addAffected(entry.getValue());
+						synchronized (cacheMutex) {
+							// Jar file within a deleted project.  In this case getLocation() 
+							// returns null, so we can't get a canonical path.  Bounce every
+							// factory path that contains anything resembling this jar.
+							for (Entry<String, Set<IJavaProject>> entry : _container2Project.entrySet()) {
+								IPath jarPath = new Path(entry.getKey());
+								if (relativePath.lastSegment().equals(jarPath.lastSegment())) {
+									addAffected(entry.getValue());
+								}
 							}
 						}
 					}
@@ -270,9 +279,12 @@
 						// Lookup key is the canonical path of the resource
 						String key = null;
 						key = absolutePath.toFile().getCanonicalPath();
-						Set<IJavaProject> projects = _container2Project.get(key);
-						if (projects != null) {
-							addAffected(projects);
+						
+						synchronized (cacheMutex) {
+							Set<IJavaProject> projects = _container2Project.get(key);
+							if (projects != null) {
+								addAffected(projects);
+							}
 						}
 					}
 				}
@@ -308,21 +320,25 @@
      * changes to project-specific factory paths, are picked up through the
      * ResourceChangedListener mechanism instead.
      */
-    public synchronized void resetAll() {
+    public void resetAll() {
     	removeAptBuildProblemMarkers( null );
-    	_project2Java5Factories.clear();
-    	_project2Java6Factories.clear();
-    	// Need to close the iterative classloaders
-    	for (ClassLoader cl : _iterativeLoaders.values()) {
-    		tryToCloseClassLoader(cl);
-    	}
-    	_iterativeLoaders.clear();
-    	_container2Project.clear();
+    	Set<ClassLoader> toClose = new HashSet<ClassLoader>();
     	
-    	for (ClassLoader cl : _batchLoaders.values()) {
-    		tryToCloseClassLoader(cl);
+    	synchronized (cacheMutex) {
+    		toClose.addAll(_iterativeLoaders.values());
+    		toClose.addAll(_batchLoaders.values());
+    		
+        	_project2Java5Factories.clear();
+        	_project2Java6Factories.clear();
+        	_iterativeLoaders.clear();
+        	_container2Project.clear();
+        	_batchLoaders.clear();
     	}
-    	_batchLoaders.clear();
+    	    	
+    	// Need to close the iterative and batch classloaders
+    	for (ClassLoader cl : toClose) {
+    		tryToCloseClassLoader(cl);
+    	}    	
     	
     	// Validate all projects
 		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
@@ -335,14 +351,19 @@
      * Called when doing a clean build -- resets
      * the classloaders for the batch processors
      */
-    public synchronized void resetBatchProcessors(IJavaProject javaProj) {
+    public void resetBatchProcessors(IJavaProject javaProj) {
     	Iterable<Attributes> attrs = null;
-    	Map<AnnotationProcessorFactory, Attributes> factories = _project2Java5Factories.get(javaProj);
+    	
+    	Map<AnnotationProcessorFactory, Attributes> factories;
+		Map<IServiceFactory, Attributes> java6factories;
+		synchronized (cacheMutex) {
+			factories = _project2Java5Factories.get(javaProj);
+			java6factories = _project2Java6Factories.get(javaProj);
+    	}
     	if (factories != null) {
     		attrs = factories.values();
     	}
     	else {
-    		Map<IServiceFactory, Attributes> java6factories = _project2Java6Factories.get(javaProj);
     		if (java6factories != null) {
     			attrs = java6factories.values();
     		}
@@ -357,13 +378,17 @@
     			batchProcsFound = true;
     			break;
     		}
-    	}
-    	if (batchProcsFound) {
-    		_project2Java5Factories.remove(javaProj);
-    		_project2Java6Factories.remove(javaProj);
-    	}
+		}
 
-    	ClassLoader c = _batchLoaders.remove(javaProj);
+		ClassLoader c;
+		synchronized (cacheMutex) {
+			if (batchProcsFound) {
+				_project2Java5Factories.remove(javaProj);
+				_project2Java6Factories.remove(javaProj);
+			}
+
+			c = _batchLoaders.remove(javaProj);
+    	}
     	tryToCloseClassLoader(c);
     }
     
@@ -380,20 +405,29 @@
     	// We can't create problem markers inside synchronization -- see https://bugs.eclipse.org/bugs/show_bug.cgi?id=184923
     	LoadFailureHandler failureHandler = new LoadFailureHandler(jproj);
     	
-    	synchronized (this) {
-	    	Map<AnnotationProcessorFactory, FactoryPath.Attributes> factories = _project2Java5Factories.get(jproj);
-	    	if( factories != null )
-	    		return Collections.unmodifiableMap(factories);
-	    	
+    	Map<AnnotationProcessorFactory, FactoryPath.Attributes> factories;
+    	synchronized (cacheMutex) {
+	    	factories = _project2Java5Factories.get(jproj);
+    	}
+    	
+    	if( factories == null ) {    	
 	    	// Load the project
 			FactoryPath fp = FactoryPathUtil.getFactoryPath(jproj);
 			Map<FactoryContainer, FactoryPath.Attributes> containers = fp.getEnabledContainers();
 			loadFactories(containers, jproj, failureHandler);
+	    	
+	    	failureHandler.reportFailureMarkers();
+	    	
+	    	synchronized (cacheMutex) {
+		    	factories = _project2Java5Factories.get(jproj);
+	    	}
     	}
     	
-    	failureHandler.reportFailureMarkers();
-		return Collections.unmodifiableMap(_project2Java5Factories.get(jproj));
-    	
+    	if (factories != null) {
+    		return Collections.unmodifiableMap(factories);
+    	} else {
+    		return Collections.emptyMap();
+    	}
     }
     
     /**
@@ -404,34 +438,40 @@
      * will not be null.
      */
     public Map<IServiceFactory, FactoryPath.Attributes> 
-    	getJava6FactoriesAndAttributesForProject(IJavaProject jproj){
+    	getJava6FactoriesAndAttributesForProject(IJavaProject jproj) {
     	
     	// We can't create problem markers inside synchronization -- see https://bugs.eclipse.org/bugs/show_bug.cgi?id=184923
     	LoadFailureHandler failureHandler = new LoadFailureHandler(jproj);
     	
-    	synchronized (this) {
+    	Map<IServiceFactory, FactoryPath.Attributes> factories;
+    	synchronized (cacheMutex) {
+	    	 factories = _project2Java6Factories.get(jproj);
+    	}
     	
-	    	Map<IServiceFactory, FactoryPath.Attributes> factories = _project2Java6Factories.get(jproj);
-	    	if( factories != null )
-	    		return Collections.unmodifiableMap(factories);
-	    	
+	    if (factories == null) {	
 	    	// Load the project
 			FactoryPath fp = FactoryPathUtil.getFactoryPath(jproj);
 			Map<FactoryContainer, FactoryPath.Attributes> containers = fp.getEnabledContainers();
 			loadFactories(containers, jproj, failureHandler);
+			
+	    	synchronized (cacheMutex) {
+		    	 factories = _project2Java6Factories.get(jproj);
+	    	}
     	}
     	
     	failureHandler.reportFailureMarkers();
-		return Collections.unmodifiableMap(_project2Java6Factories.get(jproj));
-    	
+    	if (factories != null) {
+    		return Collections.unmodifiableMap(factories);
+    	} else {
+    		return Collections.emptyMap();
+    	}
     }
     
-/**
+    /**
      * Convenience method: get the key set of the map returned by
      * @see #getJava5FactoriesAndAttributesForProject(IJavaProject) as a List.
      */
-    public synchronized List<AnnotationProcessorFactory> getJava5FactoriesForProject( IJavaProject jproj ) {
-    	
+    public List<AnnotationProcessorFactory> getJava5FactoriesForProject( IJavaProject jproj ) {
     	Map<AnnotationProcessorFactory, FactoryPath.Attributes> factoriesAndAttrs = 
     		getJava5FactoriesAndAttributesForProject(jproj);
     	final List<AnnotationProcessorFactory> factories = 
@@ -446,12 +486,14 @@
      * @param jproj must not be null
      */
 	private void addToResourcesMap(String key, IJavaProject jproj) {
-		Set<IJavaProject> s = _container2Project.get(key);
-		if (s == null) {
-			s = new HashSet<IJavaProject>();
-			_container2Project.put(key, s);
+		synchronized (cacheMutex) {
+			Set<IJavaProject> s = _container2Project.get(key);
+			if (s == null) {
+				s = new HashSet<IJavaProject>();
+				_container2Project.put(key, s);
+			}
+			s.add(jproj);
 		}
-		s.add(jproj);
 	}
 
 	/**
@@ -503,14 +545,16 @@
 		}
 		
 		// Need to use the cached classloader if we have one
-		ClassLoader iterativeClassLoader = _iterativeLoaders.get(project);
-		if (iterativeClassLoader == null) {
-			iterativeClassLoader = _createIterativeClassLoader(containers);
-			_iterativeLoaders.put(project, iterativeClassLoader);
+		ClassLoader iterativeClassLoader;
+		synchronized (cacheMutex) {
+			iterativeClassLoader = _iterativeLoaders.get(project);
+			if (iterativeClassLoader == null) {
+				iterativeClassLoader = _createIterativeClassLoader(containers);
+				_iterativeLoaders.put(project, iterativeClassLoader);
+			}
 		}
 		
-		_createBatchClassLoader(containers, project);
-		ClassLoader batchClassLoader = _batchLoaders.get(project);
+		ClassLoader batchClassLoader = _createBatchClassLoader(containers, project);
 		
 		for ( Map.Entry<FactoryContainer, FactoryPath.Attributes> entry : containers.entrySet() )
 		{
@@ -544,8 +588,11 @@
 				AptPlugin.log(ioe, Messages.AnnotationProcessorFactoryLoader_ioError + ioe.getLocalizedMessage());
 			}
 		}
-		_project2Java5Factories.put(project, java5Factories);
-		_project2Java6Factories.put(project, java6Factories);
+		
+		synchronized (cacheMutex) {
+			_project2Java5Factories.put(project, java5Factories);
+			_project2Java6Factories.put(project, java6Factories);
+		}
 	}
 
 	private List<AnnotationProcessorFactory> loadJava5FactoryClasses( 
@@ -643,12 +690,17 @@
 	 * @param jproj
 	 */
     private void uncacheProject(IJavaProject jproj) {
-		_project2Java5Factories.remove(jproj);
-		_project2Java6Factories.remove(jproj);
-		ClassLoader c = _iterativeLoaders.remove(jproj);
+    	ClassLoader c;
+    	ClassLoader cl;
+    	
+    	synchronized (cacheMutex) {
+			_project2Java5Factories.remove(jproj);
+			_project2Java6Factories.remove(jproj);
+			c = _iterativeLoaders.remove(jproj);
+			cl = _batchLoaders.remove(jproj);
+    	}
+
 		tryToCloseClassLoader(c);
-		
-		ClassLoader cl = _batchLoaders.remove(jproj);
 		tryToCloseClassLoader(cl);
 		
 		removeProjectFromResourceMap(jproj);
@@ -661,7 +713,11 @@
 	 */
 	private void removeAptBuildProblemMarkers( IJavaProject jproj ) {
 		// note that _project2Java6Factories.keySet() should be same as that for Java5.
-		Set<IJavaProject> jprojects = (jproj == null) ? _project2Java5Factories.keySet() : Collections.singleton(jproj);
+		Set<IJavaProject> jprojects;
+		synchronized (cacheMutex) {
+			jprojects = (jproj == null) ? new HashSet<IJavaProject>(_project2Java5Factories.keySet())
+					: Collections.singleton(jproj);
+		}
 		try {
 			for (IJavaProject jp : jprojects) {
 				if (jp.exists()) {
@@ -685,14 +741,16 @@
 	 * factory path.
 	 */
 	private void removeProjectFromResourceMap(IJavaProject jproj) {
-		Iterator<Entry<String, Set<IJavaProject>>> i = _container2Project.entrySet().iterator();
-		while (i.hasNext()) {
-			Entry<String, Set<IJavaProject>> e = i.next();
-			Set<IJavaProject> s = e.getValue();
-			s.remove(jproj);
-			// Remove any resulting orphaned resources.
-			if (s.isEmpty()) {
-				i.remove();
+		synchronized (cacheMutex) {
+			Iterator<Entry<String, Set<IJavaProject>>> i = _container2Project.entrySet().iterator();
+			while (i.hasNext()) {
+				Entry<String, Set<IJavaProject>> e = i.next();
+				Set<IJavaProject> s = e.getValue();
+				s.remove(jproj);
+				// Remove any resulting orphaned resources.
+				if (s.isEmpty()) {
+					i.remove();
+				}
 			}
 		}
 	}
@@ -742,7 +800,7 @@
 	/**
 	 * @param containers an ordered map.
 	 */
-	private ClassLoader _createIterativeClassLoader( Map<FactoryContainer, FactoryPath.Attributes> containers )
+	private static ClassLoader _createIterativeClassLoader( Map<FactoryContainer, FactoryPath.Attributes> containers )
 	{
 		ArrayList<File> fileList = new ArrayList<File>( containers.size() );
 		for (Map.Entry<FactoryContainer, FactoryPath.Attributes> entry : containers.entrySet()) {
@@ -764,10 +822,11 @@
 		return cl;
 	}
 	
-	private void _createBatchClassLoader(Map<FactoryContainer, FactoryPath.Attributes> containers,
-			IJavaProject p) 
-	{
-		
+	/**
+	 * Returns the batch class loader or null if none
+	 */
+	private ClassLoader _createBatchClassLoader(Map<FactoryContainer, FactoryPath.Attributes> containers,
+			IJavaProject p) {		
 		ArrayList<File> fileList = new ArrayList<File>( containers.size() );
 		for (Map.Entry<FactoryContainer, FactoryPath.Attributes> entry : containers.entrySet()) {
 			FactoryPath.Attributes attr = entry.getValue();
@@ -781,15 +840,20 @@
 			}
 		}
 		
+		ClassLoader result = null;
 		// Try to use the iterative CL as parent, so we can resolve classes within it
-		ClassLoader parentCL = _iterativeLoaders.get(p);
-		if (parentCL == null) {
-			parentCL = AnnotationProcessorFactoryLoader.class.getClassLoader();
+		synchronized (cacheMutex) {
+			ClassLoader parentCL = _iterativeLoaders.get(p);
+			if (parentCL == null) {
+				parentCL = AnnotationProcessorFactoryLoader.class.getClassLoader();
+			}
+			
+			if ( fileList.size() > 0 ) {
+				result = createClassLoader( fileList, parentCL);
+				_batchLoaders.put(p, result);
+			}
 		}
-		
-		if ( fileList.size() > 0 ) {
-			_batchLoaders.put(p,createClassLoader( fileList, parentCL));
-		}
+		return result;
 	}
 	
 	private static ClassLoader createClassLoader(List<File> files, ClassLoader parentCL) {