Bug 578243 - Deadlock between ApiBaseline.resolvePackage() and
BundleComponent.getHost()

Removed unneeded synchronization in ApiBaseline / Component and related
classes.

Change-Id: Id119d3d00c6faa5c3e08dd82f58adc6deb573b06
Signed-off-by: Andrey Loskutov <loskutov@gmx.de>
Reviewed-on: https://git.eclipse.org/r/c/pde/eclipse.pde.ui/+/189712
Tested-by: PDE Bot <pde-bot@eclipse.org>
diff --git a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/search/tests/TestRequestor.java b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/search/tests/TestRequestor.java
index c915e68..4491c61 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/search/tests/TestRequestor.java
+++ b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/search/tests/TestRequestor.java
@@ -133,7 +133,7 @@
 				}
 			}
 			catch(Exception e) {
-				this.test.reportFailure(e.getMessage());
+				throw new IllegalStateException(e);
 			}
 		}
 		return this.scope;
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ApiBaselineManager.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ApiBaselineManager.java
index 3d537a6..cb6d374 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ApiBaselineManager.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ApiBaselineManager.java
@@ -24,13 +24,16 @@
 import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -110,9 +113,9 @@
 	/**
 	 * Cache of baseline names to the location with their infos in it
 	 */
-	private HashMap<String, String> handlecache = null;
+	private volatile Map<String, String> handlecache;
 
-	private HashSet<String> hasinfos = null;
+	private volatile Set<String> hasinfos;
 
 	/**
 	 * The current default {@link IApiBaseline}
@@ -147,6 +150,7 @@
 			ApiPlugin.getDefault().addSaveParticipant(this);
 			savelocation = ApiPlugin.getDefault().getStateLocation().append(".api_profiles").addTrailingSeparator(); //$NON-NLS-1$
 		}
+		hasinfos = Collections.emptySet();
 	}
 
 	/**
@@ -224,7 +228,7 @@
 	 */
 	public void loadBaselineInfos(IApiBaseline baseline) throws CoreException {
 		initializeStateCache();
-		if (hasinfos.contains(baseline.getName())) {
+		if (isBaselineLoaded(baseline)) {
 			return;
 		}
 		String filename = handlecache.get(baseline.getName());
@@ -241,6 +245,10 @@
 		}
 	}
 
+	public boolean isBaselineLoaded(IApiBaseline baseline) {
+		return hasinfos.contains(baseline.getName());
+	}
+
 	/**
 	 * Initializes the baseline cache lazily. Only performs work if the current
 	 * cache has not been created yet
@@ -252,7 +260,7 @@
 		long time = System.currentTimeMillis();
 		if (baselinecache == null) {
 			handlecache = new HashMap<>(8);
-			hasinfos = new HashSet<>(8);
+			hasinfos = ConcurrentHashMap.newKeySet(8);
 			baselinecache = new LinkedHashMap<>(8);
 			if (!ApiPlugin.isRunningInFramework()) {
 				return;
@@ -302,14 +310,14 @@
 		} else {
 			node.remove(DEFAULT_BASELINE);
 		}
-		if (baselinecache != null && hasinfos != null) {
+		if (baselinecache != null && !hasinfos.isEmpty()) {
 			File dir = new File(savelocation.toOSString());
 			Files.createDirectories(dir.toPath());
 			IApiBaseline baseline = null;
 			for (Entry<String, IApiBaseline> entry : baselinecache.entrySet()) {
 				String id = entry.getKey();
 				baseline = entry.getValue();
-				if (!hasinfos.contains(baseline.getName())) {
+				if (!isBaselineLoaded(baseline)) {
 					continue;
 				}
 				File file = savelocation.append(id + BASELINE_FILE_EXTENSION).toFile();
@@ -557,7 +565,7 @@
 			if (handlecache != null) {
 				handlecache.clear();
 			}
-			if (hasinfos != null) {
+			if (!hasinfos.isEmpty()) {
 				hasinfos.clear();
 			}
 			StubApiComponent.disposeAllCaches();
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/AbstractApiTypeContainer.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/AbstractApiTypeContainer.java
index 30f4931..9f00247 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/AbstractApiTypeContainer.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/AbstractApiTypeContainer.java
@@ -16,7 +16,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.List;
 
 import org.eclipse.core.runtime.CoreException;
@@ -39,7 +38,7 @@
 	/**
 	 * Collection of {@link IApiTypeContainer}s
 	 */
-	private List<IApiTypeContainer> fApiTypeContainers = null;
+	private volatile List<IApiTypeContainer> fApiTypeContainers;
 
 	/**
 	 * Constructor
@@ -62,27 +61,34 @@
 	}
 
 	@Override
-	public synchronized void close() throws CoreException {
+	public void close() throws CoreException {
 		if (fApiTypeContainers == null) {
 			return;
 		}
-		// clean component cache elements
-		ApiModelCache.getCache().removeElementInfo(this);
-
 		MultiStatus multi = null;
 		IStatus single = null;
-		IApiTypeContainer[] containers = getApiTypeContainers();
-		for (IApiTypeContainer container : containers) {
-			try {
-				container.close();
-			} catch (CoreException e) {
-				if (single == null) {
-					single = e.getStatus();
-				} else {
-					if (multi == null) {
-						multi = new MultiStatus(ApiPlugin.PLUGIN_ID, single.getCode(), single.getMessage(), single.getException());
+		synchronized (this) {
+			if (fApiTypeContainers == null) {
+				return;
+			}
+
+			// clean component cache elements
+			ApiModelCache.getCache().removeElementInfo(this);
+
+			IApiTypeContainer[] containers = getApiTypeContainers();
+			for (IApiTypeContainer container : containers) {
+				try {
+					container.close();
+				} catch (CoreException e) {
+					if (single == null) {
+						single = e.getStatus();
+					} else {
+						if (multi == null) {
+							multi = new MultiStatus(ApiPlugin.PLUGIN_ID, single.getCode(), single.getMessage(),
+									single.getException());
+						}
+						multi.add(e.getStatus());
 					}
-					multi.add(e.getStatus());
 				}
 			}
 		}
@@ -171,11 +177,17 @@
 	 *
 	 * @return the {@link IApiTypeContainer}s
 	 */
-	protected synchronized IApiTypeContainer[] getApiTypeContainers() throws CoreException {
-		if (fApiTypeContainers == null) {
-			fApiTypeContainers = createApiTypeContainers();
+	protected IApiTypeContainer[] getApiTypeContainers() throws CoreException {
+		List<IApiTypeContainer> typeContainers = fApiTypeContainers;
+		if (typeContainers == null) {
+			synchronized (this) {
+				if (typeContainers == null) {
+					typeContainers = createApiTypeContainers();
+					fApiTypeContainers = typeContainers;
+				}
+			}
 		}
-		return fApiTypeContainers.toArray(new IApiTypeContainer[fApiTypeContainers.size()]);
+		return typeContainers.toArray(new IApiTypeContainer[typeContainers.size()]);
 	}
 
 	/**
@@ -185,16 +197,11 @@
 	 * @param id the given id
 	 * @return the {@link IApiTypeContainer}s
 	 */
-	protected synchronized IApiTypeContainer[] getApiTypeContainers(String id) throws CoreException {
-		if (fApiTypeContainers == null) {
-			fApiTypeContainers = createApiTypeContainers();
-		}
+	protected IApiTypeContainer[] getApiTypeContainers(String id) throws CoreException {
+		IApiTypeContainer[] typeContainers = getApiTypeContainers();
 		List<IApiTypeContainer> containers = new ArrayList<>();
-		String origin = null;
-		IApiTypeContainer container = null;
-		for (Iterator<IApiTypeContainer> iterator = fApiTypeContainers.iterator(); iterator.hasNext();) {
-			container = iterator.next();
-			origin = ((IApiComponent) container.getAncestor(IApiElement.COMPONENT)).getSymbolicName();
+		for (IApiTypeContainer container : typeContainers) {
+			String origin = ((IApiComponent) container.getAncestor(IApiElement.COMPONENT)).getSymbolicName();
 			if (origin != null && origin.equals(id)) {
 				containers.add(container);
 			}
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/ApiBaseline.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/ApiBaseline.java
index a160194..fd9dfc3 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/ApiBaseline.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/ApiBaseline.java
@@ -35,6 +35,7 @@
 import java.util.Properties;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.runtime.CoreException;
@@ -88,7 +89,7 @@
 	/**
 	 * OSGi bundle state
 	 */
-	private State fState;
+	private volatile State fState;
 
 	/**
 	 * Execution environment identifier
@@ -127,30 +128,33 @@
 	 * <p>
 	 * Map of <code>PackageName -> Map(componentName -> IApiComponent[])</code>
 	 * </p>
-	 * For each package the cache contains a map of API components that provide
-	 * that package, by source component name (including the <code>null</code>
-	 * component name).
+	 * For each package the cache contains a map of API components that provide that
+	 * package, by source component name (including the <code>null</code> component
+	 * name). This map can be updated on the fly on changes in the workspave.
 	 */
-	private HashMap<String, HashMap<IApiComponent, IApiComponent[]>> fComponentsProvidingPackageCache = null;
+	private final Map<String, Map<IApiComponent, IApiComponent[]>> fComponentsProvidingPackageCache;
 
 	/**
 	 * Maps component id's to components.
 	 * <p>
 	 * Map of <code>componentId -> {@link IApiComponent}</code>
 	 * </p>
+	 * This map is not supposed to be modified except on creation / disposal.
 	 */
 	private volatile Map<String, IApiComponent> fComponentsById;
 	/**
 	 * Maps component id's to all components sorted from higher to lower version.
+	 * This map is not supposed to be modified except on creation / disposal.
 	 */
-	private HashMap<String, Set<IApiComponent>> fAllComponentsById = null;
+	private volatile Map<String, Set<IApiComponent>> fAllComponentsById;
 	/**
 	 * Maps project name's to components.
 	 * <p>
 	 * Map of <code>project name -> {@link IApiComponent}</code>
 	 * </p>
+	 * This map is not supposed to be modified except on creation / disposal.
 	 */
-	private HashMap<String, IApiComponent> fComponentsByProjectNames = null;
+	private volatile Map<String, IApiComponent> fComponentsByProjectNames;
 	/**
 	 * Cache of system package names
 	 */
@@ -160,8 +164,9 @@
 	 * The VM install this baseline is bound to for system libraries or
 	 * <code>null</code>. Only used in the IDE when OSGi is running.
 	 */
-	private IVMInstall fVMBinding = null;
+	private IVMInstall fVMBinding;
 
+	private volatile boolean disposed;
 
 	/**
 	 * Constructs a new API baseline with the given name.
@@ -170,6 +175,7 @@
 	 */
 	public ApiBaseline(String name) {
 		super(null, IApiElement.BASELINE, name);
+		fComponentsProvidingPackageCache = new ConcurrentHashMap<>(8);
 		fAutoResolve = true;
 		fEEStatus = Status.error(CoreMessages.ApiBaseline_0);
 	}
@@ -361,13 +367,10 @@
 
 
 	/**
-	 * Clears the package -> components cache and sets it to <code>null</code>
+	 * Clears the package -> components cache
 	 */
-	private synchronized void clearComponentsCache() {
-		if (fComponentsProvidingPackageCache != null) {
-			fComponentsProvidingPackageCache.clear();
-			fComponentsProvidingPackageCache = null;
-		}
+	private void clearComponentsCache() {
+		fComponentsProvidingPackageCache.clear();
 	}
 
 	/**
@@ -545,30 +548,26 @@
 	@Override
 	public IApiComponent[] getApiComponents() {
 		loadBaselineInfos();
-		if (fComponentsById == null) {
+		Map<String, IApiComponent> componentsById = fComponentsById;
+		if (disposed || componentsById == null) {
 			return EMPTY_COMPONENTS;
 		}
-		Collection<IApiComponent> values = fComponentsById.values();
+		Collection<IApiComponent> values = componentsById.values();
 		return values.toArray(new IApiComponent[values.size()]);
 	}
 
 	@Override
-	public synchronized IApiComponent[] resolvePackage(IApiComponent sourceComponent, String packageName) throws CoreException {
-		HashMap<IApiComponent, IApiComponent[]> componentsForPackage = null;
-		if (fComponentsProvidingPackageCache != null) {
-			componentsForPackage = fComponentsProvidingPackageCache.get(packageName);
-		} else {
-			fComponentsProvidingPackageCache = new HashMap<>(8);
+	public IApiComponent[] resolvePackage(IApiComponent sourceComponent, String packageName) throws CoreException {
+		if (disposed) {
+			IStatus error = Status.error("Trying to use disposed baseline " + getName()); //$NON-NLS-1$
+			throw new CoreException(error);
 		}
+		Map<IApiComponent, IApiComponent[]> componentsForPackage = fComponentsProvidingPackageCache
+				.computeIfAbsent(packageName, x -> new ConcurrentHashMap<>(8));
 		IApiComponent[] cachedComponents = null;
-		if (componentsForPackage != null) {
-			cachedComponents = componentsForPackage.get(sourceComponent);
-			if (cachedComponents != null && cachedComponents.length > 0) {
-				return cachedComponents;
-			}
-		} else {
-			componentsForPackage = new HashMap<>(8);
-			fComponentsProvidingPackageCache.put(packageName, componentsForPackage);
+		cachedComponents = componentsForPackage.get(sourceComponent);
+		if (cachedComponents != null && cachedComponents.length > 0) {
+			return cachedComponents;
 		}
 
 		// check resolvePackage0 before the system packages to avoid wrong
@@ -613,7 +612,8 @@
 	 * @param componentsList
 	 * @throws CoreException
 	 */
-	private void resolvePackage0(IApiComponent component, String packageName, List<IApiComponent> componentsList) throws CoreException {
+	private void resolvePackage0(IApiComponent component, String packageName, List<IApiComponent> componentsList)
+			throws CoreException {
 		if (component instanceof BundleComponent) {
 			BundleDescription bundle = ((BundleComponent) component).getBundleDescription();
 			if (bundle != null) {
@@ -704,8 +704,13 @@
 	 * @noreference This method is not intended to be referenced by clients.
 	 */
 	public State getState() {
+		if (disposed) {
+			return fState;
+		}
 		if (fState == null) {
-			fState = StateObjectFactory.defaultFactory.createState(true);
+			synchronized (this) {
+				fState = StateObjectFactory.defaultFactory.createState(true);
+			}
 		}
 		return fState;
 	}
@@ -713,22 +718,25 @@
 	@Override
 	public IApiComponent getApiComponent(String id) {
 		loadBaselineInfos();
-		if (fComponentsById == null) {
+		Map<String, IApiComponent> componentsById = fComponentsById;
+		if (disposed || componentsById == null) {
 			return null;
 		}
-		return fComponentsById.get(id);
+		return componentsById.get(id);
 	}
 
 	@Override
 	public Set<IApiComponent> getAllApiComponents(String id) {
 		loadBaselineInfos();
-		if (fAllComponentsById == null) {
+		Map<String, Set<IApiComponent>> componentsById = fAllComponentsById;
+		if (disposed || componentsById == null) {
 			return Collections.emptySet();
 		}
-		if (fAllComponentsById.get(id) == null) {
+		Set<IApiComponent> set = componentsById.get(id);
+		if (set == null) {
 			return Collections.emptySet();
 		}
-		return fAllComponentsById.get(id);
+		return set;
 	}
 
 	@Override
@@ -740,14 +748,20 @@
 	 * Loads the information from the *.profile file the first time the baseline
 	 * is accessed
 	 */
-	private synchronized void loadBaselineInfos() {
-		if (fComponentsById != null) {
+	private void loadBaselineInfos() {
+		if (disposed) {
 			return;
 		}
-		try {
-			ApiBaselineManager.getManager().loadBaselineInfos(this);
-		} catch (CoreException ce) {
-			ApiPlugin.log(ce);
+		ApiBaselineManager manager = ApiBaselineManager.getManager();
+		if (fComponentsById != null && manager.isBaselineLoaded(this)) {
+			return;
+		}
+		synchronized (this) {
+			try {
+				manager.loadBaselineInfos(this);
+			} catch (CoreException ce) {
+				ApiPlugin.log(ce);
+			}
 		}
 	}
 
@@ -810,6 +824,9 @@
 	 * performs the actual dispose of mappings and cached elements
 	 */
 	protected void doDispose() {
+		if (disposed) {
+			return;
+		}
 		if (ApiPlugin.isRunningInFramework()) {
 			JavaRuntime.removeVMInstallChangedListener(this);
 		}
@@ -840,6 +857,7 @@
 			}
 			fSystemLibraryComponentList = new ArrayList<>();
 		}
+		disposed = true;
 	}
 
 	/**
@@ -912,10 +930,8 @@
 	 * @nooverride This method is not intended to be re-implemented or extended
 	 *             by clients.
 	 */
-	public synchronized void clearPackage(String packageName) {
-		if (fComponentsProvidingPackageCache != null) {
-			fComponentsProvidingPackageCache.remove(packageName);
-		}
+	public void clearPackage(String packageName) {
+		fComponentsProvidingPackageCache.remove(packageName);
 	}
 
 	@Override
@@ -996,9 +1012,10 @@
 	@Override
 	public IApiComponent getApiComponent(IProject project) {
 		loadBaselineInfos();
-		if (fComponentsByProjectNames == null) {
+		Map<String, IApiComponent> componentsByProjectNames = fComponentsByProjectNames;
+		if (disposed || componentsByProjectNames == null) {
 			return null;
 		}
-		return fComponentsByProjectNames.get(project.getName());
+		return componentsByProjectNames.get(project.getName());
 	}
 }
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/BundleComponent.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/BundleComponent.java
index 74511b8..6fa57ba 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/BundleComponent.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/BundleComponent.java
@@ -99,7 +99,7 @@
 	/**
 	 * Dictionary parsed from MANIFEST.MF
 	 */
-	private Map<String, String> fManifest;
+	private volatile Map<String, String> fManifest;
 
 	/**
 	 * Manifest headers that are maintained after {@link BundleDescription}
@@ -114,7 +114,7 @@
 	/**
 	 * Whether there is an underlying .api_description file
 	 */
-	private boolean fHasApiDescription = false;
+	private volatile boolean fHasApiDescription;
 
 	/**
 	 * Root location of component in the file system
@@ -124,33 +124,35 @@
 	/**
 	 * Underlying bundle description (OSGi model of a bundle)
 	 */
-	private BundleDescription fBundleDescription;
+	private volatile BundleDescription fBundleDescription;
 
 	/**
 	 * Symbolic name of this bundle
 	 */
-	private String fSymbolicName = null;
+	private String fSymbolicName;
 
 	/**
 	 * Bundle version
 	 */
-	private Version fVersion = null;
+	private volatile Version fVersion;
 
 	/**
 	 * Cached value for the lowest EEs
 	 */
-	private String[] lowestEEs;
+	private volatile String[] lowestEEs;
 
 	/**
 	 * Flag to know if this component is a binary bundle in the workspace i.e.
 	 * an imported binary bundle
 	 */
-	private boolean fWorkspaceBinary = false;
+	private boolean fWorkspaceBinary;
 
 	/**
 	 * The id of this component
 	 */
-	private long fBundleId = 0L;
+	private long fBundleId;
+
+	private volatile boolean disposed;
 
 	/**
 	 * Constructs a new API component from the specified location in the file
@@ -178,12 +180,16 @@
 
 	@Override
 	public void dispose() {
+		if (isDisposed()) {
+			return;
+		}
 		try {
 			super.dispose();
 		} finally {
 			synchronized (this) {
 				fManifest = null;
 				fBundleDescription = null;
+				disposed = true;
 			}
 		}
 	}
@@ -195,28 +201,39 @@
 	 * @return manifest dictionary or <code>null</code>
 	 * @exception CoreException if something goes terribly wrong
 	 */
-	protected synchronized Map<String, String> getManifest() throws CoreException {
-		if (fManifest == null) {
-			File bundleLocation = new File(fLocation);
-			try {
-				fManifest = ManifestUtils.loadManifest(bundleLocation);
-			} catch (CoreException e) {
-				if (e.getStatus().getCode() == ManifestUtils.STATUS_CODE_NOT_A_BUNDLE_MANIFEST) {
-					// If we load a component with a manifest file that isn't a
-					// bundle, ignore it
-					return null;
-				} else {
-					throw e;
-				}
+	protected Map<String, String> getManifest() throws CoreException {
+		if (fManifest != null) {
+			return fManifest;
+		}
+		Map<String, String> manifest = loadManifest(new File(fLocation), isWorkspaceBinary());
+		synchronized (this) {
+			if (fManifest == null) {
+				fManifest = manifest;
 			}
-			if (isWorkspaceBinary()) {
+			return fManifest;
+		}
+	}
+
+	private static Map<String, String> loadManifest(File bundleLocation, boolean isWorkspaceBinary)
+			throws CoreException {
+		try {
+			Map<String, String> manifest = ManifestUtils.loadManifest(bundleLocation);
+			if (isWorkspaceBinary) {
 				// must account for bundles in development mode - look for class
 				// files in output
 				// folders rather than jars
-				TargetWeaver.weaveManifest(fManifest, bundleLocation);
+				TargetWeaver.weaveManifest(manifest, bundleLocation);
+			}
+			return manifest;
+		} catch (CoreException e) {
+			if (e.getStatus().getCode() == ManifestUtils.STATUS_CODE_NOT_A_BUNDLE_MANIFEST) {
+				// If we load a component with a manifest file that isn't a
+				// bundle, ignore it
+				return null;
+			} else {
+				throw e;
 			}
 		}
-		return fManifest;
 	}
 
 	/**
@@ -266,29 +283,32 @@
 	 *
 	 * @throws CoreException on failure
 	 */
-	protected synchronized void init() {
-		if (fBundleDescription != null) {
+	protected void init() {
+		if (isDisposed() || fBundleDescription != null) {
 			return;
 		}
-		try {
-			Map<String, String> manifest = getManifest();
-			if (manifest == null) {
-				ApiPlugin.log(Status.error("Unable to find a manifest for the component from: " + fLocation, //$NON-NLS-1$
-				null));
-				return;
+		synchronized (this) {
+			try {
+				Map<String, String> manifest = getManifest();
+				if (manifest == null) {
+					ApiPlugin.log(Status.error("Unable to find a manifest for the component from: " + fLocation, //$NON-NLS-1$
+							null));
+					return;
+				}
+				BundleDescription bundleDescription = getBundleDescription(manifest, fLocation, fBundleId);
+				fSymbolicName = bundleDescription.getSymbolicName();
+				fVersion = bundleDescription.getVersion();
+				setName(manifest.get(Constants.BUNDLE_NAME));
+				fBundleDescription = bundleDescription;
+			} catch (BundleException e) {
+				ApiPlugin.log(Status.error("Unable to create API component from specified location: " + fLocation, //$NON-NLS-1$
+						e));
+			} catch (CoreException ce) {
+				ApiPlugin.log(ce);
 			}
-			fBundleDescription = getBundleDescription(manifest, fLocation, fBundleId);
-			fSymbolicName = fBundleDescription.getSymbolicName();
-			fVersion = fBundleDescription.getVersion();
-			setName(manifest.get(Constants.BUNDLE_NAME));
-		} catch (BundleException e) {
-			ApiPlugin.log(Status.error("Unable to create API component from specified location: " + fLocation, //$NON-NLS-1$
-			e));
-		} catch (CoreException ce) {
-			ApiPlugin.log(ce);
+			// compact manifest after initialization - only keep used headers
+			doManifestCompaction();
 		}
-		// compact manifest after initialization - only keep used headers
-		doManifestCompaction();
 	}
 
 	/**
@@ -342,7 +362,7 @@
 	 * @return the bundle for the given manifest, <code>null</code> otherwise
 	 * @throws BundleException
 	 */
-	protected BundleDescription lookupBundle(State state, Map<String, String> manifest) throws BundleException {
+	protected static BundleDescription lookupBundle(State state, Map<String, String> manifest) throws BundleException {
 		Version version = null;
 		try {
 			// just in case the version is not a number
@@ -542,7 +562,7 @@
 	 * @see org.eclipse.pde.api.tools.internal.AbstractApiTypeContainer#createApiTypeContainers()
 	 */
 	@Override
-	protected synchronized List<IApiTypeContainer> createApiTypeContainers() throws CoreException {
+	protected List<IApiTypeContainer> createApiTypeContainers() throws CoreException {
 		List<IApiTypeContainer> containers = new ArrayList<>(5);
 		List<IApiComponent> all = new ArrayList<>();
 		// build the classpath from bundle and all fragments
@@ -629,7 +649,7 @@
 	 * @return classpath entries as bundle relative paths
 	 * @throws BundleException
 	 */
-	protected String[] getClasspathEntries(Map<String, String> manifest) throws BundleException {
+	protected static String[] getClasspathEntries(Map<String, String> manifest) throws BundleException {
 		ManifestElement[] classpath = ManifestElement.parseHeader(Constants.BUNDLE_CLASSPATH, manifest.get(Constants.BUNDLE_CLASSPATH));
 		String elements[] = null;
 		if (classpath == null) {
@@ -734,7 +754,7 @@
 	 *             fails to write the file(s)
 	 * @throws IOException, CoreException
 	 */
-	void extractDirectory(ZipFile zip, String pathprefix, File parent) throws IOException, CoreException {
+	static void extractDirectory(ZipFile zip, String pathprefix, File parent) throws IOException, CoreException {
 		Enumeration<? extends ZipEntry> entries = zip.entries();
 		String prefix = (pathprefix == null ? Util.EMPTY_STRING : pathprefix);
 		ZipEntry entry = null;
@@ -768,7 +788,7 @@
 	 *         otherwise
 	 * @throws IOException
 	 */
-	File extractEntry(ZipFile zip, ZipEntry entry, File parent) throws IOException {
+	static File extractEntry(ZipFile zip, ZipEntry entry, File parent) throws IOException {
 		InputStream inputStream = null;
 		File file;
 		FileOutputStream outputStream = null;
@@ -806,7 +826,7 @@
 		return file;
 	}
 
-	public void closingZipFileAndStream(InputStream stream, ZipFile jarFile) {
+	public static void closingZipFileAndStream(InputStream stream, ZipFile jarFile) {
 		try {
 			if (stream != null) {
 				stream.close();
@@ -831,7 +851,7 @@
 	 * @param bundleLocation the root location of the bundle
 	 * @return the file contents or <code>null</code> if not present
 	 */
-	protected String readFileContents(String xmlFileName, File bundleLocation) {
+	protected static String readFileContents(String xmlFileName, File bundleLocation) {
 		ZipFile jarFile = null;
 		InputStream stream = null;
 		try {
@@ -868,7 +888,7 @@
 	 * @return API description XML as a string or <code>null</code> if none
 	 * @throws IOException if unable to parse
 	 */
-	protected String loadApiDescription(File bundleLocation) throws IOException {
+	protected static String loadApiDescription(File bundleLocation) throws IOException {
 		ZipFile jarFile = null;
 		InputStream stream = null;
 		String contents = null;
@@ -908,7 +928,7 @@
 	 * @return URL to the file
 	 * @throws MalformedURLException
 	 */
-	protected URL getFileInBundle(File bundleLocation, String filePath) throws MalformedURLException {
+	protected static URL getFileInBundle(File bundleLocation, String filePath) throws MalformedURLException {
 		String extension = new Path(bundleLocation.getName()).getFileExtension();
 		StringBuilder urlSt = new StringBuilder();
 		if (extension != null && extension.equals("jar") && bundleLocation.isFile()) { //$NON-NLS-1$
@@ -926,11 +946,8 @@
 	}
 
 	@Override
-	public synchronized String[] getExecutionEnvironments() throws CoreException {
-		if (fBundleDescription == null) {
-			baselineDisposed(getBaseline());
-		}
-		return fBundleDescription.getExecutionEnvironments();
+	public String[] getExecutionEnvironments() throws CoreException {
+		return getBundleDescription().getExecutionEnvironments();
 	}
 
 	@Override
@@ -940,11 +957,8 @@
 	}
 
 	@Override
-	public synchronized IRequiredComponentDescription[] getRequiredComponents() throws CoreException {
-		if (fBundleDescription == null) {
-			baselineDisposed(getBaseline());
-		}
-		BundleSpecification[] requiredBundles = fBundleDescription.getRequiredBundles();
+	public IRequiredComponentDescription[] getRequiredComponents() throws CoreException {
+		BundleSpecification[] requiredBundles = getBundleDescription().getRequiredBundles();
 		IRequiredComponentDescription[] req = new IRequiredComponentDescription[requiredBundles.length];
 		for (int i = 0; i < requiredBundles.length; i++) {
 			BundleSpecification bundle = requiredBundles[i];
@@ -954,7 +968,7 @@
 	}
 
 	@Override
-	public synchronized String getVersion() {
+	public String getVersion() {
 		init();
 		// remove the qualifier
 		StringBuilder buffer = new StringBuilder();
@@ -971,14 +985,16 @@
 	/**
 	 * Returns this component's bundle description.
 	 *
-	 * @return bundle description
+	 * @return bundle description, never null
+	 * @throws CoreException if this component or the baseline is already disposed
 	 */
-	public synchronized BundleDescription getBundleDescription() throws CoreException {
+	public BundleDescription getBundleDescription() throws CoreException {
 		init();
-		if (fBundleDescription == null) {
+		BundleDescription description = fBundleDescription;
+		if (isDisposed() || description == null) {
 			baselineDisposed(getBaseline());
 		}
-		return fBundleDescription;
+		return description;
 	}
 
 	@Override
@@ -995,16 +1011,20 @@
 				buffer.append("[dev bundle: ").append(fWorkspaceBinary).append("]"); //$NON-NLS-1$ //$NON-NLS-2$
 				return buffer.toString();
 			} catch (CoreException ce) {
-				// ignore
+				return super.toString();
 			}
 		} else {
 			StringBuilder buffer = new StringBuilder();
-			buffer.append("Un-initialized Bundle Component"); //$NON-NLS-1$
+			if (isDisposed()) {
+				buffer.append("Disposed "); //$NON-NLS-1$
+			} else {
+				buffer.append("Un-initialized "); //$NON-NLS-1$
+			}
+			buffer.append("Bundle Component"); //$NON-NLS-1$
 			buffer.append("[location: ").append(fLocation).append("]"); //$NON-NLS-1$ //$NON-NLS-2$
 			buffer.append("[dev bundle: ").append(fWorkspaceBinary).append("]"); //$NON-NLS-1$ //$NON-NLS-2$
 			return buffer.toString();
 		}
-		return super.toString();
 	}
 
 	@Override
@@ -1018,11 +1038,15 @@
 	}
 
 	@Override
-	public synchronized boolean isSourceComponent() throws CoreException {
+	public boolean isSourceComponent() throws CoreException {
 		Map<String, String> manifest = getManifest();
 		if (manifest == null) {
 			baselineDisposed(getBaseline());
 		}
+		return isSourceComponent(manifest, new File(getLocation()));
+	}
+
+	private static boolean isSourceComponent(Map<String, String> manifest, File location) {
 		ManifestElement[] sourceBundle = null;
 		try {
 			sourceBundle = ManifestElement.parseHeader(IApiCoreConstants.ECLIPSE_SOURCE_BUNDLE, manifest.get(IApiCoreConstants.ECLIPSE_SOURCE_BUNDLE));
@@ -1034,7 +1058,7 @@
 			return true;
 		}
 		// check for the old format
-		String pluginXMLContents = readFileContents(IApiCoreConstants.PLUGIN_XML_NAME, new File(getLocation()));
+		String pluginXMLContents = readFileContents(IApiCoreConstants.PLUGIN_XML_NAME, location);
 		if (pluginXMLContents != null) {
 			if (containsSourceExtensionPoint(pluginXMLContents)) {
 				return true;
@@ -1042,7 +1066,7 @@
 		}
 		// check if it contains a fragment.xml with the appropriate extension
 		// point
-		pluginXMLContents = readFileContents(IApiCoreConstants.FRAGMENT_XML_NAME, new File(getLocation()));
+		pluginXMLContents = readFileContents(IApiCoreConstants.FRAGMENT_XML_NAME, location);
 		if (pluginXMLContents != null) {
 			if (containsSourceExtensionPoint(pluginXMLContents)) {
 				return true;
@@ -1058,7 +1082,7 @@
 	 * @param pluginXMLContents the given file contents
 	 * @return true if it contains a source extension point, false otherwise
 	 */
-	private boolean containsSourceExtensionPoint(String pluginXMLContents) {
+	private static boolean containsSourceExtensionPoint(String pluginXMLContents) {
 		SAXParserFactory factory = null;
 		try {
 			factory = SAXParserFactory.newInstance();
@@ -1089,21 +1113,13 @@
 	}
 
 	@Override
-	public synchronized boolean isFragment() throws CoreException {
-		init();
-		if (fBundleDescription == null) {
-			baselineDisposed(getBaseline());
-		}
-		return fBundleDescription.getHost() != null;
+	public boolean isFragment() throws CoreException {
+		return getBundleDescription().getHost() != null;
 	}
 
 	@Override
-	public synchronized IApiComponent getHost() throws CoreException {
-		init();
-		if (fBundleDescription == null) {
-			baselineDisposed(getBaseline());
-		}
-		HostSpecification host = fBundleDescription.getHost();
+	public IApiComponent getHost() throws CoreException {
+		HostSpecification host = getBundleDescription().getHost();
 		if (host != null) {
 			return getBaseline().getApiComponent(host.getName());
 		}
@@ -1111,7 +1127,7 @@
 	}
 
 	@Override
-	public synchronized boolean hasFragments() throws CoreException {
+	public boolean hasFragments() throws CoreException {
 		return getBundleDescription().getFragments().length != 0;
 	}
 
@@ -1141,8 +1157,17 @@
 		if (lowestEEs != null) {
 			return lowestEEs;
 		}
-		String[] temp = null;
 		String[] executionEnvironments = getExecutionEnvironments();
+		String[] ees = computeLowestEEs(executionEnvironments);
+		synchronized (this) {
+			lowestEEs = ees;
+			return lowestEEs;
+		}
+	}
+
+	private static String[] computeLowestEEs(String[] executionEnvironments) {
+		String[] temp = null;
+
 		int length = executionEnvironments.length;
 		switch (length) {
 			case 0:
@@ -1224,19 +1249,15 @@
 					}
 				}
 		}
-		lowestEEs = temp;
 		return temp;
 	}
 
 	@Override
-	public synchronized ResolverError[] getErrors() throws CoreException {
-		init();
+	public ResolverError[] getErrors() throws CoreException {
 		ApiBaseline baseline = (ApiBaseline) getBaseline();
-		if (fBundleDescription == null) {
-			baselineDisposed(baseline);
-		}
 		if (baseline != null) {
-			ResolverError[] resolverErrors = baseline.getState().getResolverErrors(fBundleDescription);
+			BundleDescription bundleDescription = getBundleDescription();
+			ResolverError[] resolverErrors = baseline.getState().getResolverErrors(bundleDescription);
 			if (resolverErrors.length == 0) {
 				return null;
 			}
@@ -1250,6 +1271,11 @@
 	 * @throws CoreException with the baseline disposed information
 	 */
 	protected void baselineDisposed(IApiBaseline baseline) throws CoreException {
-		throw new CoreException(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, ApiPlugin.REPORT_BASELINE_IS_DISPOSED, NLS.bind(Messages.BundleApiComponent_baseline_disposed, baseline.getName()), null));
+		throw new CoreException(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, ApiPlugin.REPORT_BASELINE_IS_DISPOSED,
+				NLS.bind(Messages.BundleApiComponent_baseline_disposed, getName(), baseline.getName()), null));
+	}
+
+	protected boolean isDisposed() {
+		return disposed;
 	}
 }
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/Component.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/Component.java
index a66bbab..a9ad50c 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/Component.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/Component.java
@@ -37,17 +37,17 @@
 	/**
 	 * API description
 	 */
-	private IApiDescription fApiDescription = null;
+	private volatile IApiDescription fApiDescription;
 
 	/**
 	 * API Filter store
 	 */
-	private IApiFilterStore fFilterStore = null;
+	private volatile IApiFilterStore fFilterStore;
 
 	/**
 	 * References in API use scan reports
 	 */
-	private IReferenceCollection fReferences;
+	private volatile IReferenceCollection fReferences;
 
 	/**
 	 * Constructs an API component in the given {@link IApiBaseline}.
@@ -95,8 +95,11 @@
 	}
 
 	@Override
-	public synchronized IApiDescription getApiDescription() throws CoreException {
-		if (fApiDescription == null) {
+	public IApiDescription getApiDescription() throws CoreException {
+		if (fApiDescription != null) {
+			return fApiDescription;
+		}
+		synchronized (this) {
 			fApiDescription = createApiDescription();
 		}
 		return fApiDescription;
@@ -107,7 +110,7 @@
 	 *
 	 * @return whether this component has created an API description
 	 */
-	protected synchronized boolean isApiDescriptionInitialized() {
+	protected boolean isApiDescriptionInitialized() {
 		return fApiDescription != null;
 	}
 
@@ -116,17 +119,17 @@
 	 *
 	 * @return true if a store has been created, false other wise
 	 */
-	protected synchronized boolean hasApiFilterStore() {
+	protected boolean hasApiFilterStore() {
 		return fFilterStore != null;
 	}
 
 	@Override
-	public synchronized IApiTypeContainer[] getApiTypeContainers() throws CoreException {
+	public IApiTypeContainer[] getApiTypeContainers() throws CoreException {
 		return super.getApiTypeContainers();
 	}
 
 	@Override
-	public synchronized IApiTypeContainer[] getApiTypeContainers(String id) throws CoreException {
+	public IApiTypeContainer[] getApiTypeContainers(String id) throws CoreException {
 		if (this.hasFragments()) {
 			return super.getApiTypeContainers(id);
 		} else {
@@ -144,7 +147,11 @@
 	@Override
 	public IApiFilterStore getFilterStore() throws CoreException {
 		if (fFilterStore == null) {
-			fFilterStore = createApiFilterStore();
+			synchronized (this) {
+				if (fFilterStore == null) {
+					fFilterStore = createApiFilterStore();
+				}
+			}
 		}
 		return fFilterStore;
 	}
@@ -170,7 +177,11 @@
 	@Override
 	public IReferenceCollection getExternalDependencies() {
 		if (fReferences == null) {
-			fReferences = new UseScanReferences();
+			synchronized (this) {
+				if (fReferences == null) {
+					fReferences = new UseScanReferences();
+				}
+			}
 		}
 		return fReferences;
 	}
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/CompositeApiTypeContainer.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/CompositeApiTypeContainer.java
index cdcbfd7..5596f19 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/CompositeApiTypeContainer.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/CompositeApiTypeContainer.java
@@ -45,6 +45,12 @@
 	}
 
 	@Override
+	public void close() throws CoreException {
+		fContainers.clear();
+		super.close();
+	}
+
+	@Override
 	public String toString() {
 		StringBuilder buff = new StringBuilder();
 		buff.append("Composite Class File Container:\n"); //$NON-NLS-1$
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/Messages.properties b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/Messages.properties
index 0364a20..2d54b7d 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/Messages.properties
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/Messages.properties
@@ -16,7 +16,7 @@
 ApiType_2=Unsupported operation - API component required for resolution
 ApiType_3=Unable to resolve member type {0} for {1}
 ApiScope_0=Unable to visit this element type: {0}
-BundleApiComponent_baseline_disposed=Baseline ''{0}'' is disposed
+BundleApiComponent_baseline_disposed=Component ''{0}'' in the baseline ''{1}'' is disposed
 BundleComponent_failed_to_lookup_fragment=Failed to look up resolved fragment: {0}
 configuring_baseline=Configuring baseline
 resolving_target_definition=resolving target definition...
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/ProjectComponent.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/ProjectComponent.java
index 7239c25..7fe2a2c 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/ProjectComponent.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/ProjectComponent.java
@@ -15,9 +15,9 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.eclipse.core.resources.IContainer;
 import org.eclipse.core.resources.IProject;
@@ -39,7 +39,6 @@
 import org.eclipse.pde.api.tools.internal.provisional.IApiDescription;
 import org.eclipse.pde.api.tools.internal.provisional.IApiFilterStore;
 import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline;
-import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent;
 import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeContainer;
 import org.eclipse.pde.api.tools.internal.util.Util;
 import org.eclipse.pde.core.build.IBuild;
@@ -77,22 +76,22 @@
 	/**
 	 * Associated Java project
 	 */
-	private IJavaProject fProject = null;
+	private IJavaProject fProject;
 
 	/**
 	 * Associated IPluginModelBase object
 	 */
-	private IPluginModelBase fModel = null;
+	private IPluginModelBase fModel;
 
 	/**
 	 * A cache of bundle class path entries to class file containers.
 	 */
-	private Map<String, IApiTypeContainer> fPathToOutputContainers = null;
+	private volatile Map<String, IApiTypeContainer> fPathToOutputContainers;
 
 	/**
 	 * A cache of output location paths to corresponding class file containers.
 	 */
-	private Map<IPath, IApiTypeContainer> fOutputLocationToContainer = null;
+	private volatile Map<IPath, IApiTypeContainer> fOutputLocationToContainer;
 
 	/**
 	 * Constructs an API component for the given Java project in the specified
@@ -160,6 +159,9 @@
 
 	@Override
 	public void dispose() {
+		if (isDisposed()) {
+			return;
+		}
 		try {
 			if (hasApiFilterStore()) {
 				getFilterStore().dispose();
@@ -204,114 +206,131 @@
 	}
 
 	@Override
-	protected synchronized List<IApiTypeContainer> createApiTypeContainers() throws CoreException {
+	protected List<IApiTypeContainer> createApiTypeContainers() throws CoreException {
+		if (isDisposed()) {
+			baselineDisposed(getBaseline());
+		}
 		// first populate build.properties cache so we can create class file
 		// containers
 		// from bundle classpath entries
-		fPathToOutputContainers = new HashMap<>(4);
-		fOutputLocationToContainer = new HashMap<>(4);
+		fPathToOutputContainers = new ConcurrentHashMap<>(4);
+		fOutputLocationToContainer = new ConcurrentHashMap<>(4);
 		if (fProject.exists() && fProject.getProject().isOpen()) {
 			IPluginModelBase model = PluginRegistry.findModel(fProject.getProject());
 			if (model != null) {
-				IBuildModel buildModel = PluginRegistry.createBuildModel(model);
-				if (buildModel != null) {
-					IBuild build = buildModel.getBuild();
-					IBuildEntry entry = build.getEntry(ENTRY_CUSTOM);
-					if (entry != null) {
-						String[] tokens = entry.getTokens();
-						if (tokens.length == 1 && tokens[0].equals("true")) { //$NON-NLS-1$
-							// hack : add the current output location for each
-							// classpath entries
-							IClasspathEntry[] classpathEntries = fProject.getRawClasspath();
-							List<IApiTypeContainer> containers = new ArrayList<>();
-							for (IClasspathEntry classpathEntrie : classpathEntries) {
-								IClasspathEntry classpathEntry = classpathEntrie;
-								switch (classpathEntry.getEntryKind()) {
-									case IClasspathEntry.CPE_SOURCE:
-										String containerPath = classpathEntry.getPath().removeFirstSegments(1).toString();
-										IApiTypeContainer container = getApiTypeContainer(containerPath, this);
-										if (container != null && !containers.contains(container)) {
-											containers.add(container);
-										}
-										break;
-									case IClasspathEntry.CPE_VARIABLE:
-										classpathEntry = JavaCore.getResolvedClasspathEntry(classpathEntry);
-										//$FALL-THROUGH$
-									case IClasspathEntry.CPE_LIBRARY:
-										IPath path = classpathEntry.getPath();
-										if (Util.isArchive(path.lastSegment())) {
-											IResource resource = ResourcesPlugin.getWorkspace().getRoot().findMember(path);
-											if (resource != null) {
-												// jar inside the workspace
-												containers.add(new ArchiveApiTypeContainer(this, resource.getLocation().toOSString()));
-											} else {
-												// external jar
-												containers.add(new ArchiveApiTypeContainer(this, path.toOSString()));
-											}
-										}
-										break;
-									default:
-										break;
-								}
-							}
-							if (!containers.isEmpty()) {
-								IApiTypeContainer cfc = null;
-								if (containers.size() == 1) {
-									cfc = containers.get(0);
-								} else {
-									cfc = new CompositeApiTypeContainer(this, containers);
-								}
-								fPathToOutputContainers.put(".", cfc); //$NON-NLS-1$
-							}
-						}
-					} else {
-						IBuildEntry[] entries = build.getBuildEntries();
-						int length = entries.length;
-						for (int i = 0; i < length; i++) {
-							IBuildEntry buildEntry = entries[i];
-							String name = buildEntry.getName();
-							if (name.startsWith(IBuildEntry.JAR_PREFIX)) {
-								retrieveContainers(name, IBuildEntry.JAR_PREFIX, buildEntry);
-							} else if (name.startsWith(EXTRA_PREFIX)) {
-								retrieveContainers(name, EXTRA_PREFIX, buildEntry);
-							}
-						}
-					}
-				}
+				createContainersFromProjectModel(model, this, fPathToOutputContainers, fOutputLocationToContainer);
 			}
 			return super.createApiTypeContainers();
 		}
 		return Collections.emptyList();
 	}
 
-	private void retrieveContainers(String name, String prefix, IBuildEntry buildEntry) throws CoreException {
+	private static void createContainersFromProjectModel(IPluginModelBase model, ProjectComponent project,
+			Map<String, IApiTypeContainer> pathToOutputContainers,
+			Map<IPath, IApiTypeContainer> outputLocationToContainer) throws CoreException {
+		IBuildModel buildModel = PluginRegistry.createBuildModel(model);
+		if (buildModel == null) {
+			return;
+		}
+		IBuild build = buildModel.getBuild();
+		IBuildEntry entry = build.getEntry(ENTRY_CUSTOM);
+		if (entry != null) {
+			String[] tokens = entry.getTokens();
+			if (tokens.length == 1 && tokens[0].equals("true")) { //$NON-NLS-1$
+				// hack : add the current output location for each
+				// classpath entries
+				IClasspathEntry[] classpathEntries = project.fProject.getRawClasspath();
+				List<IApiTypeContainer> containers = new ArrayList<>();
+				for (IClasspathEntry classpathEntrie : classpathEntries) {
+					IClasspathEntry classpathEntry = classpathEntrie;
+					switch (classpathEntry.getEntryKind())
+						{
+						case IClasspathEntry.CPE_SOURCE:
+							String containerPath = classpathEntry.getPath().removeFirstSegments(1).toString();
+							IApiTypeContainer container = getApiTypeContainer(containerPath, project,
+									outputLocationToContainer);
+							if (container != null && !containers.contains(container)) {
+								containers.add(container);
+						}
+							break;
+						case IClasspathEntry.CPE_VARIABLE:
+							classpathEntry = JavaCore.getResolvedClasspathEntry(classpathEntry);
+							//$FALL-THROUGH$
+						case IClasspathEntry.CPE_LIBRARY:
+							IPath path = classpathEntry.getPath();
+							if (Util.isArchive(path.lastSegment())) {
+								IResource resource = ResourcesPlugin.getWorkspace().getRoot().findMember(path);
+								if (resource != null) {
+									// jar inside the workspace
+									containers.add(
+											new ArchiveApiTypeContainer(project, resource.getLocation().toOSString()));
+								} else {
+									// external jar
+									containers.add(new ArchiveApiTypeContainer(project, path.toOSString()));
+								}
+						}
+							break;
+						default:
+							break;
+					}
+				}
+				if (!containers.isEmpty()) {
+					IApiTypeContainer cfc = null;
+					if (containers.size() == 1) {
+						cfc = containers.get(0);
+					} else {
+						cfc = new CompositeApiTypeContainer(project, containers);
+					}
+					pathToOutputContainers.put(".", cfc); //$NON-NLS-1$
+				}
+			}
+		} else {
+			IBuildEntry[] entries = build.getBuildEntries();
+			int length = entries.length;
+			for (int i = 0; i < length; i++) {
+				IBuildEntry buildEntry = entries[i];
+				String name = buildEntry.getName();
+				if (name.startsWith(IBuildEntry.JAR_PREFIX)) {
+					retrieveContainers(name, IBuildEntry.JAR_PREFIX, buildEntry, project, pathToOutputContainers,
+							outputLocationToContainer);
+				} else if (name.startsWith(EXTRA_PREFIX)) {
+					retrieveContainers(name, EXTRA_PREFIX, buildEntry, project, pathToOutputContainers,
+							outputLocationToContainer);
+				}
+			}
+		}
+	}
+
+	private static void retrieveContainers(String name, String prefix, IBuildEntry buildEntry, ProjectComponent project,
+			Map<String, IApiTypeContainer> pathToOutputContainers,
+			Map<IPath, IApiTypeContainer> outputLocationToContainer) throws CoreException {
 		String jar = name.substring(prefix.length());
 		String[] tokens = buildEntry.getTokens();
 		if (tokens.length == 1) {
-			IApiTypeContainer container = getApiTypeContainer(tokens[0], this);
+			IApiTypeContainer container = getApiTypeContainer(tokens[0], project, outputLocationToContainer);
 			if (container != null) {
-				IApiTypeContainer existingContainer = this.fPathToOutputContainers.get(jar);
+				IApiTypeContainer existingContainer = pathToOutputContainers.get(jar);
 				if (existingContainer != null) {
 					// concat both containers
 					List<IApiTypeContainer> allContainers = new ArrayList<>();
 					allContainers.add(existingContainer);
 					allContainers.add(container);
-					IApiTypeContainer apiTypeContainer = new CompositeApiTypeContainer(this, allContainers);
-					fPathToOutputContainers.put(jar, apiTypeContainer);
+					IApiTypeContainer apiTypeContainer = new CompositeApiTypeContainer(project, allContainers);
+					pathToOutputContainers.put(jar, apiTypeContainer);
 				} else {
-					fPathToOutputContainers.put(jar, container);
+					pathToOutputContainers.put(jar, container);
 				}
 			}
 		} else {
 			List<IApiTypeContainer> containers = new ArrayList<>();
 			for (String currentToken : tokens) {
-				IApiTypeContainer container = getApiTypeContainer(currentToken, this);
+				IApiTypeContainer container = getApiTypeContainer(currentToken, project, outputLocationToContainer);
 				if (container != null && !containers.contains(container)) {
 					containers.add(container);
 				}
 			}
 			if (!containers.isEmpty()) {
-				IApiTypeContainer existingContainer = this.fPathToOutputContainers.get(jar);
+				IApiTypeContainer existingContainer = pathToOutputContainers.get(jar);
 				if (existingContainer != null) {
 					// concat both containers
 					containers.add(existingContainer);
@@ -320,21 +339,21 @@
 				if (containers.size() == 1) {
 					cfc = containers.get(0);
 				} else {
-					cfc = new CompositeApiTypeContainer(this, containers);
+					cfc = new CompositeApiTypeContainer(project, containers);
 				}
-				fPathToOutputContainers.put(jar, cfc);
+				pathToOutputContainers.put(jar, cfc);
 			}
 		}
 	}
 
 	@Override
 	protected IApiTypeContainer createApiTypeContainer(String path) throws CoreException {
-		if (this.fPathToOutputContainers == null) {
+		if (isDisposed() || this.fPathToOutputContainers == null) {
 			baselineDisposed(getBaseline());
 		}
 		IApiTypeContainer container = fPathToOutputContainers.get(path);
 		if (container == null) {
-			// could be a binary jar included in the plug-in, just look for it
+		// could be a binary jar included in the plug-in, just look for it
 			container = findApiTypeContainer(path);
 		}
 		return container;
@@ -368,22 +387,21 @@
 	 * @param location project relative path to the source folder
 	 * @return {@link IApiTypeContainer} or <code>null</code>
 	 */
-	private IApiTypeContainer getApiTypeContainer(String location, IApiComponent component) throws CoreException {
-		if (this.fOutputLocationToContainer == null) {
-			baselineDisposed(getBaseline());
-		}
-		IResource res = fProject.getProject().findMember(new Path(location));
+	private static IApiTypeContainer getApiTypeContainer(String location, ProjectComponent component,
+			Map<IPath, IApiTypeContainer> outputLocationToContainer) throws CoreException {
+		IJavaProject project = component.fProject;
+		IResource res = project.getProject().findMember(new Path(location));
 		if (res != null) {
-			IPackageFragmentRoot root = fProject.getPackageFragmentRoot(res);
+			IPackageFragmentRoot root = project.getPackageFragmentRoot(res);
 			if (root.exists()) {
 				if (root.getKind() == IPackageFragmentRoot.K_BINARY) {
 					if (res.getType() == IResource.FOLDER) {
 						// class file folder
 						IPath location2 = res.getLocation();
-						IApiTypeContainer cfc = fOutputLocationToContainer.get(location2);
+						IApiTypeContainer cfc = outputLocationToContainer.get(location2);
 						if (cfc == null) {
 							cfc = new ProjectTypeContainer(component, (IContainer) res);
-							fOutputLocationToContainer.put(location2, cfc);
+							outputLocationToContainer.put(location2, cfc);
 						}
 						return cfc;
 					}
@@ -391,20 +409,20 @@
 					IClasspathEntry entry = root.getRawClasspathEntry();
 					IPath outputLocation = entry.getOutputLocation();
 					if (outputLocation == null) {
-						outputLocation = fProject.getOutputLocation();
+						outputLocation = project.getOutputLocation();
 					}
-					IApiTypeContainer cfc = fOutputLocationToContainer.get(outputLocation);
+					IApiTypeContainer cfc = outputLocationToContainer.get(outputLocation);
 					if (cfc == null) {
-						IPath projectFullPath = fProject.getProject().getFullPath();
+						IPath projectFullPath = project.getProject().getFullPath();
 						IContainer container = null;
 						if (projectFullPath.equals(outputLocation)) {
 							// The project is its own output location
-							container = fProject.getProject();
+							container = project.getProject();
 						} else {
-							container = fProject.getProject().getWorkspace().getRoot().getFolder(outputLocation);
+							container = project.getProject().getWorkspace().getRoot().getFolder(outputLocation);
 						}
 						cfc = new ProjectTypeContainer(component, container);
-						fOutputLocationToContainer.put(outputLocation, cfc);
+						outputLocationToContainer.put(outputLocation, cfc);
 					}
 					return cfc;
 				}
@@ -435,11 +453,14 @@
 	 */
 	public IApiTypeContainer getTypeContainer(IPackageFragmentRoot root) throws CoreException {
 		if (root.getKind() == IPackageFragmentRoot.K_SOURCE) {
+			if (isDisposed()) {
+				baselineDisposed(getBaseline());
+			}
 			getApiTypeContainers(); // ensure initialized
 			IResource resource = root.getResource();
 			if (resource != null) {
 				String location = resource.getProjectRelativePath().toString();
-				return getApiTypeContainer(location, this);
+				return getApiTypeContainer(location, this, fOutputLocationToContainer);
 			}
 		}
 		return null;
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/WorkspaceBaseline.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/WorkspaceBaseline.java
index 9d173a2..046d62e 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/WorkspaceBaseline.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/WorkspaceBaseline.java
@@ -13,9 +13,12 @@
  *******************************************************************************/
 package org.eclipse.pde.api.tools.internal.model;
 
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Proxy;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.osgi.service.resolver.State;
@@ -35,7 +38,12 @@
 	// If workspace is changed, then WorkspaceBaseline is disposed and a
 	// new WorkspaceBaseline is created, hence mismatch problem can be stored
 	// with a workspace baseline
-	public HashMap<IApiBaseline, IApiProblem> mismatch = new HashMap<>();
+	public Map<IApiBaseline, IApiProblem> mismatch = new ConcurrentHashMap<>();
+
+	private static final IApiProblem NULL_PROBLEM = (IApiProblem) Proxy.newProxyInstance(
+			IApiProblem.class.getClassLoader(), new Class<?>[] { IApiProblem.class },
+			(InvocationHandler) (proxy, method, args) -> null);
+
 	/**
 	 * Constructor
 	 */
@@ -61,12 +69,16 @@
 
 	// can be null showing no problem
 	public IApiProblem getProblem(IApiBaseline b) {
-		return mismatch.get(b);
+		IApiProblem problem = mismatch.get(b);
+		return problem == NULL_PROBLEM ? null : problem;
 	}
 
 	public void putMismatchInfo(IApiBaseline baseline, IApiProblem problem) {
-		mismatch.put(baseline, problem);
-
+		if (problem == null) {
+			mismatch.put(baseline, NULL_PROBLEM);
+		} else {
+			mismatch.put(baseline, problem);
+		}
 	}
 
 	@Override